Skip to content

Commit a69cce6

Browse files
Add user DB lookup functions
1 parent f1cb5d3 commit a69cce6

1 file changed

Lines changed: 94 additions & 9 deletions

File tree

  • src/Streamly/Coreutils

src/Streamly/Coreutils/Id.hs

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# OPTIONS_GHC -Wno-missing-signatures #-}
12
-- |
23
-- Module : Streamly.Coreutils.Id
34
-- Copyright : (c) 2022 Composewell Technologies
@@ -32,6 +33,14 @@ module Streamly.Coreutils.Id
3233
-- * Login name of the current process user
3334
, loginName
3435

36+
-- * Lookups by user id
37+
, userNameFromId
38+
, groupNameFromId
39+
, primaryGroupIdFromUserId
40+
, primaryGroupNameFromUserId
41+
, groupIdsFromUserId
42+
, groupNamesFromUserId
43+
3544
{-
3645
-- * Numeric ids of the current process
3746
, realUserId
@@ -108,32 +117,108 @@ import qualified System.Posix.User as Posix
108117
-- router's return type and are kept as standalone functions rather than
109118
-- forced into a polymorphic or awkwardly-typed router.
110119
--
120+
111121
------------------------------------------------------------------------------
112-
-- Internal helpers
122+
-- Lookups by user / group id
113123
------------------------------------------------------------------------------
114124

115-
-- Look up the user name for a uid, returning Nothing if no entry exists
116-
-- rather than throwing. See "Maybe for DB lookups" in the module header.
117-
--
118-
-- Note: this is a user-DB lookup and logically belongs in a future
119-
-- Streamly.Coreutils.UserDB module. It is kept internal here so the
120-
-- migration is not a breaking change for callers.
125+
-- These functions are user/group-DB lookups. They logically belong in a
126+
-- future Streamly.Coreutils.UserDB module and will likely move there; a
127+
-- re-export from here may be kept for backwards compatibility when that
128+
-- happens.
129+
130+
-- | Look up the user name for a uid. Returns 'Nothing' if no matching
131+
-- user-db entry exists.
121132
userNameFromId :: Int -> IO (Maybe String)
122133
userNameFromId i = do
123134
r <- try (Posix.getUserEntryForID (fromIntegral i))
124135
return $ case r of
125136
Left (_ :: SomeException) -> Nothing
126137
Right ue -> Just (Posix.userName ue)
127138

128-
-- Look up the group name for a gid, returning Nothing if no entry exists
129-
-- rather than throwing. Same migration note as 'userNameFromId'.
139+
-- | Look up the group name for a gid. Returns 'Nothing' if no matching
140+
-- group-db entry exists.
130141
groupNameFromId :: Int -> IO (Maybe String)
131142
groupNameFromId i = do
132143
r <- try (Posix.getGroupEntryForID (fromIntegral i))
133144
return $ case r of
134145
Left (_ :: SomeException) -> Nothing
135146
Right ge -> Just (Posix.groupName ge)
136147

148+
-- Internal: look up the full passwd entry for a uid, returning Nothing on
149+
-- miss rather than throwing.
150+
userEntryFromId i = do
151+
r <- try (Posix.getUserEntryForID (fromIntegral (i :: Int)))
152+
return $ case r of
153+
Left (_ :: SomeException) -> Nothing
154+
Right ue -> Just ue
155+
156+
-- | Primary group id of the user with the given uid. This is @pw_gid@
157+
-- from the user's passwd entry. Corresponds to @id -g \<uid\>@.
158+
--
159+
-- Returns 'Nothing' if no passwd entry exists for the uid.
160+
primaryGroupIdFromUserId :: Int -> IO (Maybe Int)
161+
primaryGroupIdFromUserId i =
162+
fmap (fromIntegral . Posix.userGroupID) <$> userEntryFromId i
163+
164+
-- | Primary group name of the user with the given uid. Corresponds to
165+
-- @id -gn \<uid\>@.
166+
--
167+
-- Returns 'Nothing' if no passwd entry exists for the uid, or if the
168+
-- primary gid has no matching group-db entry.
169+
primaryGroupNameFromUserId :: Int -> IO (Maybe String)
170+
primaryGroupNameFromUserId i = do
171+
mGid <- primaryGroupIdFromUserId i
172+
case mGid of
173+
Nothing -> return Nothing
174+
Just gid -> groupNameFromId gid
175+
176+
-- | All group ids the user with the given uid belongs to: the primary
177+
-- group from the passwd entry, plus every group in the group database
178+
-- whose member list contains the user name. Deduplicated. Corresponds
179+
-- to @id -G \<uid\>@.
180+
--
181+
-- Returns 'Nothing' if no passwd entry exists for the uid.
182+
--
183+
-- Note: group membership lookup walks 'Posix.getAllGroupEntries', which
184+
-- reflects only @/etc/group@. Groups provided exclusively via NSS
185+
-- backends (LDAP, SSSD, etc.) will not be seen. A future version could
186+
-- FFI to @getgrouplist(3)@ for full NSS coverage.
187+
--
188+
-- Note: in contrast to 'groupIds' (current process, which cannot fail),
189+
-- this returns 'Maybe' to distinguish a non-existent user from a user
190+
-- whose only group is the primary.
191+
groupIdsFromUserId :: Int -> IO (Maybe [Int])
192+
groupIdsFromUserId i = do
193+
mUe <- userEntryFromId i
194+
case mUe of
195+
Nothing -> return Nothing
196+
Just ue -> do
197+
let uname = Posix.userName ue
198+
primary = fromIntegral (Posix.userGroupID ue)
199+
allGroups <- Posix.getAllGroupEntries
200+
let supp = [ fromIntegral (Posix.groupID g)
201+
| g <- allGroups
202+
, uname `elem` Posix.groupMembers g
203+
]
204+
return $ Just (nub (primary : supp))
205+
206+
-- | Names of all groups the user with the given uid belongs to, in the
207+
-- same order as 'groupIdsFromUserId'. Corresponds to @id -Gn \<uid\>@.
208+
--
209+
-- Returns 'Nothing' if no passwd entry exists for the uid. Entries for
210+
-- which no group-db record exists are silently dropped.
211+
--
212+
-- See 'groupIdsFromUserId' for the note on NSS coverage.
213+
groupNamesFromUserId :: Int -> IO (Maybe [String])
214+
groupNamesFromUserId i = do
215+
mGids <- groupIdsFromUserId i
216+
case mGids of
217+
Nothing -> return Nothing
218+
Just gids -> do
219+
mNames <- mapM groupNameFromId gids
220+
return $ Just [ n | Just n <- mNames ]
221+
137222
------------------------------------------------------------------------------
138223
-- Current process: numeric ids
139224
------------------------------------------------------------------------------

0 commit comments

Comments
 (0)