diff --git a/Sources/FoundationEssentials/Platform.swift b/Sources/FoundationEssentials/Platform.swift index a7aad439e6..1687553f81 100644 --- a/Sources/FoundationEssentials/Platform.swift +++ b/Sources/FoundationEssentials/Platform.swift @@ -164,7 +164,7 @@ extension Platform { } static func gid(forName name: String) -> gid_t? { - withUserGroupBuffer(name, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: getgrnam_r) { + withUserGroupBuffer(name, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: _filemanager_shims_getgrnam_r) { $0.gr_gid } } @@ -193,7 +193,7 @@ extension Platform { } static func name(forGID gid: gid_t) -> String? { - withUserGroupBuffer(gid, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: getgrgid_r) { + withUserGroupBuffer(gid, group(), sizeProperty: Int32(_SC_GETGR_R_SIZE_MAX), operation: _filemanager_shims_getgrgid_r) { // Android's gr_name `char *`` is nullable when it should be non-null. // FIXME: avoid the coerce cast workaround once https://github.com/android/ndk/issues/2098 is fixed. let gr_name: UnsafeMutablePointer? = $0.gr_name diff --git a/Sources/_FoundationCShims/include/filemanager_shims.h b/Sources/_FoundationCShims/include/filemanager_shims.h index 6fd8a7cc88..9b45a05608 100644 --- a/Sources/_FoundationCShims/include/filemanager_shims.h +++ b/Sources/_FoundationCShims/include/filemanager_shims.h @@ -14,6 +14,7 @@ #define CSHIMS_FILEMANAGER_H #include "_CShimsMacros.h" +#include "_CShimsTargetConditionals.h" #if __has_include() #include @@ -46,4 +47,92 @@ extern int _mkpath_np(const char *path, mode_t omode, const char **firstdir); #endif +#if TARGET_OS_ANDROID && __ANDROID_API__ <= 23 +#include +#include +#include +#include + +static inline int _filemanager_shims_getgrgid_r(gid_t gid, struct group *grp, + char *buf, size_t buflen, struct group **result) { + errno = 0; + + // Call the non-reentrant version. + // On Android, this uses Thread Local Storage (TLS), + // so it is safe from race conditions with other threads. + struct group *p = getgrgid(gid); + + if (p == NULL) { + *result = NULL; + return errno; + } + + if (strlcpy(buf, p->gr_name, buflen) >= buflen) { + *result = NULL; + return ERANGE; + } + + grp->gr_name = buf; + grp->gr_gid = p->gr_gid; + + // Android Bionic leaves this as NULL. + grp->gr_passwd = NULL; + + // Android Bionic generates a synthetic list ["groupname", NULL]. + // Replicating that here would require deep copying the strings array. + // Foundation does not use this either, so NULL is sufficient and avoids complexity. + grp->gr_mem = NULL; + + *result = grp; + return 0; +} + +static inline int _filemanager_shims_getgrnam_r(const char *name, struct group *grp, + char *buf, size_t buflen, struct group **result) { + errno = 0; + + // Call the non-reentrant version. + // On Android, this uses Thread Local Storage (TLS), + // so it is safe from race conditions with other threads. + struct group *p = getgrnam(name); + + if (p == NULL) { + *result = NULL; + return errno; + } + + if (strlcpy(buf, p->gr_name, buflen) >= buflen) { + *result = NULL; + return ERANGE; + } + + grp->gr_name = buf; + grp->gr_gid = p->gr_gid; + + // Android Bionic leaves this as NULL. + grp->gr_passwd = NULL; + + // Android Bionic generates a synthetic list ["groupname", NULL]. + // Replicating that here would require deep copying the strings array. + // Foundation does not use this either, so NULL is sufficient and avoids complexity. + grp->gr_mem = NULL; + + *result = grp; + return 0; +} + +#elif __has_include() +#include + +static inline int _filemanager_shims_getgrgid_r(gid_t gid, struct group *grp, + char *buf, size_t buflen, struct group **result) { + return getgrgid_r(gid, grp, buf, buflen, result); +} + +static inline int _filemanager_shims_getgrnam_r(const char *name, struct group *grp, + char *buf, size_t buflen, struct group **result) { + return getgrnam_r(name, grp, buf, buflen, result); +} +#endif + #endif // CSHIMS_FILEMANAGER_H