2 * Copyright (c) 1999-2007 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.1 (the "License"). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
14 * The Original Code and all software distributed under the License are
15 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
22 * @APPLE_LICENSE_HEADER_END@
26 * Copyright (C) 1989 by NeXT, Inc.
30 #include <mach/mach.h>
34 #include <rpc/types.h>
38 #include <netinet/in.h>
39 #include <sys/param.h>
43 #include <servers/bootstrap.h>
45 #include "_lu_types.h"
48 #include "lu_overrides.h"
50 #define GROUP_CACHE_SIZE 10
51 #define DEFAULT_GROUP_CACHE_TTL 10
53 static pthread_mutex_t _group_cache_lock
= PTHREAD_MUTEX_INITIALIZER
;
54 static void *_group_cache
[GROUP_CACHE_SIZE
] = { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
55 static unsigned int _group_cache_best_before
[GROUP_CACHE_SIZE
] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
56 static unsigned int _group_cache_index
= 0;
57 static unsigned int _group_cache_ttl
= DEFAULT_GROUP_CACHE_TTL
;
59 static pthread_mutex_t _group_lock
= PTHREAD_MUTEX_INITIALIZER
;
62 * Support for memberd calls
64 #define MEMBERD_NAME "com.apple.memberd"
65 static mach_port_t mbr_port
= MACH_PORT_NULL
;
66 typedef uint32_t GIDArray
[16];
67 extern kern_return_t
_mbr_GetGroups(mach_port_t server
, uint32_t uid
, uint32_t *numGroups
, GIDArray gids
, security_token_t
*token
);
74 free_group_data(struct group
*g
)
78 if (g
== NULL
) return;
80 if (g
->gr_name
!= NULL
) free(g
->gr_name
);
81 if (g
->gr_passwd
!= NULL
) free(g
->gr_passwd
);
86 while (*mem
!= NULL
) free(*mem
++);
92 free_group(struct group
*g
)
94 if (g
== NULL
) return;
100 free_lu_thread_info_group(void *x
)
102 struct lu_thread_info
*tdata
;
104 if (x
== NULL
) return;
106 tdata
= (struct lu_thread_info
*)x
;
108 if (tdata
->lu_entry
!= NULL
)
110 free_group((struct group
*)tdata
->lu_entry
);
111 tdata
->lu_entry
= NULL
;
114 _lu_data_free_vm_xdr(tdata
);
119 static struct group
*
120 extract_group(XDR
*xdr
)
122 int i
, j
, nkeys
, nvals
, status
;
126 if (xdr
== NULL
) return NULL
;
128 if (!xdr_int(xdr
, &nkeys
)) return NULL
;
130 g
= (struct group
*)calloc(1, sizeof(struct group
));
133 for (i
= 0; i
< nkeys
; i
++)
139 status
= _lu_xdr_attribute(xdr
, &key
, &vals
, &nvals
);
154 if ((g
->gr_name
== NULL
) && (!strcmp("name", key
)))
156 g
->gr_name
= vals
[0];
159 else if ((g
->gr_passwd
== NULL
) && (!strcmp("passwd", key
)))
161 g
->gr_passwd
= vals
[0];
164 else if ((g
->gr_gid
== (gid_t
)-2) && (!strcmp("gid", key
)))
166 g
->gr_gid
= atoi(vals
[0]);
167 if ((g
->gr_gid
== 0) && (strcmp(vals
[0], "0"))) g
->gr_gid
= -2;
169 else if ((g
->gr_mem
== NULL
) && (!strcmp("users", key
)))
179 for (; j
< nvals
; j
++) free(vals
[j
]);
184 if (g
->gr_name
== NULL
) g
->gr_name
= strdup("");
185 if (g
->gr_passwd
== NULL
) g
->gr_passwd
= strdup("");
186 if (g
->gr_mem
== NULL
) g
->gr_mem
= (char **)calloc(1, sizeof(char *));
191 static struct group
*
192 copy_group(struct group
*in
)
197 if (in
== NULL
) return NULL
;
199 g
= (struct group
*)calloc(1, sizeof(struct group
));
201 g
->gr_name
= LU_COPY_STRING(in
->gr_name
);
202 g
->gr_passwd
= LU_COPY_STRING(in
->gr_passwd
);
203 g
->gr_gid
= in
->gr_gid
;
206 if (in
->gr_mem
!= NULL
)
208 for (len
= 0; in
->gr_mem
[len
] != NULL
; len
++);
211 g
->gr_mem
= (char **)calloc(len
+ 1, sizeof(char *));
212 for (i
= 0; i
< len
; i
++)
214 g
->gr_mem
[i
] = strdup(in
->gr_mem
[i
]);
221 copy_group_r(struct group
*in
, struct group
*out
, char *buffer
, int buflen
)
227 if (in
== NULL
) return -1;
228 if (out
== NULL
) return -1;
230 if (buffer
== NULL
) buflen
= 0;
232 /* Calculate size of input */
234 if (in
->gr_name
!= NULL
) hsize
+= (strlen(in
->gr_name
) + 1);
235 if (in
->gr_passwd
!= NULL
) hsize
+= (strlen(in
->gr_passwd
) + 1);
237 /* NULL pointer at end of list */
238 hsize
+= sizeof(char *);
241 if (in
->gr_mem
!= NULL
)
243 for (len
= 0; in
->gr_mem
[len
] != NULL
; len
++)
245 hsize
+= sizeof(char *);
246 hsize
+= (strlen(in
->gr_mem
[len
]) + 1);
250 /* Check buffer space */
251 if (hsize
> buflen
) return -1;
253 /* Copy result into caller's struct group, using buffer for memory */
257 if (in
->gr_name
!= NULL
)
260 hsize
= strlen(in
->gr_name
) + 1;
261 memmove(bp
, in
->gr_name
, hsize
);
265 out
->gr_passwd
= NULL
;
266 if (in
->gr_passwd
!= NULL
)
269 hsize
= strlen(in
->gr_passwd
) + 1;
270 memmove(bp
, in
->gr_passwd
, hsize
);
274 out
->gr_gid
= in
->gr_gid
;
277 ap
= bp
+ ((len
+ 1) * sizeof(char *));
279 if (in
->gr_mem
!= NULL
)
281 out
->gr_mem
= (char **)bp
;
282 for (i
= 0; i
< len
; i
++)
284 addr
= (unsigned long)ap
;
285 memmove(bp
, &addr
, sizeof(unsigned long));
286 bp
+= sizeof(unsigned long);
288 hsize
= strlen(in
->gr_mem
[i
]) + 1;
289 memmove(ap
, in
->gr_mem
[i
], hsize
);
294 memset(bp
, 0, sizeof(unsigned long));
301 recycle_group(struct lu_thread_info
*tdata
, struct group
*in
)
305 if (tdata
== NULL
) return;
306 g
= (struct group
*)tdata
->lu_entry
;
311 tdata
->lu_entry
= NULL
;
314 if (tdata
->lu_entry
== NULL
)
316 tdata
->lu_entry
= in
;
322 g
->gr_name
= in
->gr_name
;
323 g
->gr_passwd
= in
->gr_passwd
;
324 g
->gr_gid
= in
->gr_gid
;
325 g
->gr_mem
= in
->gr_mem
;
330 __private_extern__
unsigned int
331 get_group_cache_ttl()
333 return _group_cache_ttl
;
336 __private_extern__
void
337 set_group_cache_ttl(unsigned int ttl
)
341 pthread_mutex_lock(&_group_cache_lock
);
343 _group_cache_ttl
= ttl
;
347 for (i
= 0; i
< GROUP_CACHE_SIZE
; i
++)
349 if (_group_cache
[i
] == NULL
) continue;
351 free_group((struct group
*)_group_cache
[i
]);
352 _group_cache
[i
] = NULL
;
353 _group_cache_best_before
[i
] = 0;
357 pthread_mutex_unlock(&_group_cache_lock
);
361 cache_group(struct group
*gr
)
364 struct group
*grcache
;
366 if (_group_cache_ttl
== 0) return;
367 if (gr
== NULL
) return;
369 pthread_mutex_lock(&_group_cache_lock
);
371 grcache
= copy_group(gr
);
373 gettimeofday(&now
, NULL
);
375 if (_group_cache
[_group_cache_index
] != NULL
)
376 free_group((struct group
*)_group_cache
[_group_cache_index
]);
378 _group_cache
[_group_cache_index
] = grcache
;
379 _group_cache_best_before
[_group_cache_index
] = now
.tv_sec
+ _group_cache_ttl
;
380 _group_cache_index
= (_group_cache_index
+ 1) % GROUP_CACHE_SIZE
;
382 pthread_mutex_unlock(&_group_cache_lock
);
385 static struct group
*
386 cache_getgrnam(const char *name
)
389 struct group
*gr
, *res
;
392 if (_group_cache_ttl
== 0) return NULL
;
393 if (name
== NULL
) return NULL
;
395 pthread_mutex_lock(&_group_cache_lock
);
397 gettimeofday(&now
, NULL
);
399 for (i
= 0; i
< GROUP_CACHE_SIZE
; i
++)
401 if (_group_cache_best_before
[i
] == 0) continue;
402 if ((unsigned int)now
.tv_sec
> _group_cache_best_before
[i
]) continue;
404 gr
= (struct group
*)_group_cache
[i
];
406 if (gr
->gr_name
== NULL
) continue;
408 if (!strcmp(name
, gr
->gr_name
))
410 res
= copy_group(gr
);
411 pthread_mutex_unlock(&_group_cache_lock
);
416 pthread_mutex_unlock(&_group_cache_lock
);
420 static struct group
*
421 cache_getgrgid(int gid
)
424 struct group
*gr
, *res
;
427 if (_group_cache_ttl
== 0) return NULL
;
429 pthread_mutex_lock(&_group_cache_lock
);
431 gettimeofday(&now
, NULL
);
433 for (i
= 0; i
< GROUP_CACHE_SIZE
; i
++)
435 if (_group_cache_best_before
[i
] == 0) continue;
436 if ((unsigned int)now
.tv_sec
> _group_cache_best_before
[i
]) continue;
438 gr
= (struct group
*)_group_cache
[i
];
440 if ((gid_t
)gid
== gr
->gr_gid
)
442 res
= copy_group(gr
);
443 pthread_mutex_unlock(&_group_cache_lock
);
448 pthread_mutex_unlock(&_group_cache_lock
);
452 static struct group
*
456 unsigned int datalen
;
458 static int proc
= -1;
464 if (_lookup_link(_lu_port
, "getgrgid", &proc
) != KERN_SUCCESS
)
474 if (_lookup_all(_lu_port
, proc
, (unit
*)&gid
, 1, &lookup_buf
, &datalen
) != KERN_SUCCESS
)
479 datalen
*= BYTES_PER_XDR_UNIT
;
480 if ((lookup_buf
== NULL
) || (datalen
== 0)) return NULL
;
482 xdrmem_create(&inxdr
, lookup_buf
, datalen
, XDR_DECODE
);
485 if (!xdr_int(&inxdr
, &count
))
488 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
495 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
499 g
= extract_group(&inxdr
);
501 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
506 static struct group
*
507 lu_getgrnam(const char *name
)
510 unsigned int datalen
;
511 char namebuf
[_LU_MAXLUSTRLEN
+ BYTES_PER_XDR_UNIT
];
514 static int proc
= -1;
520 if (_lookup_link(_lu_port
, "getgrnam", &proc
) != KERN_SUCCESS
)
526 xdrmem_create(&outxdr
, namebuf
, sizeof(namebuf
), XDR_ENCODE
);
528 if (!xdr__lu_string(&outxdr
, (_lu_string
*)&name
))
530 xdr_destroy(&outxdr
);
537 if (_lookup_all(_lu_port
, proc
, (unit
*)namebuf
, xdr_getpos(&outxdr
) / BYTES_PER_XDR_UNIT
, &lookup_buf
, &datalen
) != KERN_SUCCESS
)
542 xdr_destroy(&outxdr
);
544 datalen
*= BYTES_PER_XDR_UNIT
;
545 if ((lookup_buf
== NULL
) || (datalen
== 0)) return NULL
;
547 xdrmem_create(&inxdr
, lookup_buf
, datalen
, XDR_DECODE
);
550 if (!xdr_int(&inxdr
, &count
))
553 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
560 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
564 g
= extract_group(&inxdr
);
566 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
572 * add a group to a list
574 * if dupok is non-zero, it's OK to add a duplicate entry
575 * if dupok is zero, we only add the gid if it is new
576 * (*listcount) is incremented if the gid was added
577 * returns -1 if adding the gid would overflow the list
581 _add_group(int gid
, int *list
, int *listcount
, int max
, int dupok
, int laststatus
)
583 int i
, n
, addit
, status
;
585 if (laststatus
!= 0) return laststatus
;
593 for (i
= 0; (i
< n
) && (addit
== 1); i
++)
595 if (list
[i
] == gid
) addit
= 0;
599 if (addit
== 0) return 0;
600 if (n
>= max
) return -1;
608 _old_getgrouplist(const char *uname
, int basegid
, int *groups
, int *grpcnt
)
611 int i
, status
, maxgroups
;
618 * When installing primary group, duplicate it;
619 * the first element of groups is the effective gid
620 * and will be overwritten when a setgid file is executed.
622 status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 0, status
);
623 status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 1, status
);
626 * Scan the group file to find additional groups.
630 while ((grp
= getgrent()))
632 if (grp
->gr_gid
== (gid_t
)basegid
) continue;
633 for (i
= 0; grp
->gr_mem
[i
]; i
++)
635 if (!strcmp(grp
->gr_mem
[i
], uname
))
637 status
= _add_group(grp
->gr_gid
, groups
, grpcnt
, maxgroups
, 0, status
);
650 kern_return_t status
;
652 status
= bootstrap_look_up(bootstrap_port
, MEMBERD_NAME
, &mbr_port
);
653 if (status
!= KERN_SUCCESS
) return 0;
654 if (mbr_port
== MACH_PORT_NULL
) return 0;
659 * Guess at the size of a password buffer for getpwnam_r
660 * pw_name can be MAXLOGNAME + 1 256 - sys/param.h
661 * pw_passwd can be _PASSWORD_LEN + 1 129 - pwd.h
662 * pw_dir can be MAXPATHLEN + 1 1025 - sys/syslimits.h
663 * pw_shell can be MAXPATHLEN + 1025 - sys/syslimits.h
664 * We allow pw_class and pw_gecos to take a maximum of 4098 bytes (there's no limit on these).
665 * This adds to 6533 bytes (until one of the constants changes)
667 #define MAXPWBUF (MAXLOGNAME + 1 + _PASSWORD_LEN + 1 + MAXPATHLEN + 1 + MAXPATHLEN + 1 + 4098)
669 mbr_getgrouplist(const char *name
, int basegid
, int *groups
, int *grpcnt
, int dupbase
)
671 struct passwd p
, *res
;
673 kern_return_t kstatus
;
677 int status
, maxgroups
;
678 security_token_t token
;
682 if (mbr_port
== MACH_PORT_NULL
) return status
;
683 if (name
== NULL
) return status
;
684 if (groups
== NULL
) return status
;
685 if (grpcnt
== NULL
) return status
;
690 status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 0, status
);
691 if (dupbase
!= 0) status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 1, status
);
693 if (status
!= 0) return status
;
695 memset(&p
, 0, sizeof(struct passwd
));
696 memset(buf
, 0, sizeof(buf
));
699 pwstatus
= getpwnam_r(name
, &p
, buf
, MAXPWBUF
, &res
);
700 if (pwstatus
!= 0) return status
;
701 if (res
== NULL
) return status
;
707 kstatus
= _mbr_GetGroups(mbr_port
, p
.pw_uid
, &count
, gids
, &token
);
708 if (kstatus
!= KERN_SUCCESS
) return status
;
709 if (token
.val
[0] != 0) return KERN_FAILURE
;
711 for (i
= 0; (i
< count
) && (status
== 0); i
++)
713 status
= _add_group(gids
[i
], groups
, grpcnt
, maxgroups
, 0, status
);
720 lu_getgrouplist(const char *name
, int basegid
, int *groups
, int *grpcnt
, int dupbase
)
722 unsigned int datalen
;
725 static int proc
= -1;
727 char namebuf
[_LU_MAXLUSTRLEN
+ BYTES_PER_XDR_UNIT
];
730 int status
, maxgroups
;
734 if (name
== NULL
) return status
;
735 if (groups
== NULL
) return status
;
736 if (grpcnt
== NULL
) return status
;
741 status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 0, status
);
742 if (dupbase
!= 0) status
= _add_group(basegid
, groups
, grpcnt
, maxgroups
, 1, status
);
744 if (status
!= 0) return status
;
748 if (_lookup_link(_lu_port
, "initgroups", &proc
) != KERN_SUCCESS
) return status
;
751 xdrmem_create(&outxdr
, namebuf
, sizeof(namebuf
), XDR_ENCODE
);
752 if (!xdr__lu_string(&outxdr
, (_lu_string
*)&name
))
754 xdr_destroy(&outxdr
);
761 if (_lookup_all(_lu_port
, proc
, (unit
*)namebuf
, xdr_getpos(&outxdr
) / BYTES_PER_XDR_UNIT
, &lookup_buf
, &datalen
) != KERN_SUCCESS
)
763 xdr_destroy(&outxdr
);
767 xdr_destroy(&outxdr
);
769 datalen
*= BYTES_PER_XDR_UNIT
;
770 if ((lookup_buf
== NULL
) || (datalen
== 0)) return 0;
772 xdrmem_create(&inxdr
, lookup_buf
, datalen
, XDR_DECODE
);
774 if (!xdr_int(&inxdr
, &count
))
777 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
781 for (i
= 0; (i
< count
) && (status
== 0); i
++)
783 if (!xdr_int(&inxdr
, &gid
)) break;
784 status
= _add_group(gid
, groups
, grpcnt
, maxgroups
, 0, status
);
788 vm_deallocate(mach_task_self(), (vm_address_t
)lookup_buf
, datalen
);
794 getgrouplist_internal(const char *name
, int basegid
, int *groups
, int *grpcnt
, int dupbase
)
798 return mbr_getgrouplist(name
, basegid
, groups
, grpcnt
, dupbase
);
803 return lu_getgrouplist(name
, basegid
, groups
, grpcnt
, dupbase
);
806 return _old_getgrouplist(name
, basegid
, groups
, grpcnt
);
810 getgrouplist(const char *uname
, int agroup
, int *groups
, int *grpcnt
)
812 return getgrouplist_internal(uname
, agroup
, groups
, grpcnt
, 1);
818 struct lu_thread_info
*tdata
;
820 tdata
= _lu_data_create_key(_lu_data_key_group
, free_lu_thread_info_group
);
821 _lu_data_free_vm_xdr(tdata
);
831 static struct group
*
835 static int proc
= -1;
836 struct lu_thread_info
*tdata
;
838 tdata
= _lu_data_create_key(_lu_data_key_group
, free_lu_thread_info_group
);
841 tdata
= (struct lu_thread_info
*)calloc(1, sizeof(struct lu_thread_info
));
842 _lu_data_set_key(_lu_data_key_group
, tdata
);
845 if (tdata
->lu_vm
== NULL
)
849 if (_lookup_link(_lu_port
, "getgrent", &proc
) != KERN_SUCCESS
)
856 if (_lookup_all(_lu_port
, proc
, NULL
, 0, &(tdata
->lu_vm
), &(tdata
->lu_vm_length
)) != KERN_SUCCESS
)
862 /* mig stubs measure size in words (4 bytes) */
863 tdata
->lu_vm_length
*= 4;
865 if (tdata
->lu_xdr
!= NULL
)
867 xdr_destroy(tdata
->lu_xdr
);
870 tdata
->lu_xdr
= (XDR
*)calloc(1, sizeof(XDR
));
872 xdrmem_create(tdata
->lu_xdr
, tdata
->lu_vm
, tdata
->lu_vm_length
, XDR_DECODE
);
873 if (!xdr_int(tdata
->lu_xdr
, &tdata
->lu_vm_cursor
))
880 if (tdata
->lu_vm_cursor
== 0)
886 g
= extract_group(tdata
->lu_xdr
);
893 tdata
->lu_vm_cursor
--;
898 static struct group
*
899 getgr_internal(const char *name
, gid_t gid
, int source
)
901 struct group
*res
= NULL
;
910 res
= cache_getgrnam(name
);
913 res
= cache_getgrgid(gid
);
922 else if (_lu_running())
927 res
= lu_getgrnam(name
);
930 res
= lu_getgrgid(gid
);
940 pthread_mutex_lock(&_group_lock
);
944 res
= copy_group(_old_getgrnam(name
));
947 res
= copy_group(_old_getgrgid(gid
));
950 res
= copy_group(_old_getgrent());
954 pthread_mutex_unlock(&_group_lock
);
957 if (from_cache
== 0) cache_group(res
);
962 static struct group
*
963 getgr(const char *name
, gid_t gid
, int source
)
965 struct group
*res
= NULL
;
966 struct lu_thread_info
*tdata
;
968 tdata
= _lu_data_create_key(_lu_data_key_group
, free_lu_thread_info_group
);
971 tdata
= (struct lu_thread_info
*)calloc(1, sizeof(struct lu_thread_info
));
972 _lu_data_set_key(_lu_data_key_group
, tdata
);
975 res
= getgr_internal(name
, gid
, source
);
977 recycle_group(tdata
, res
);
978 return (struct group
*)tdata
->lu_entry
;
982 getgr_r(const char *name
, gid_t gid
, int source
, struct group
*grp
, char *buffer
, size_t bufsize
, struct group
**result
)
984 struct group
*res
= NULL
;
990 res
= getgr_internal(name
, gid
, source
);
991 if (res
== NULL
) return -1;
993 status
= copy_group_r(res
, grp
, buffer
, bufsize
);
1007 initgroups(const char *name
, int basegid
)
1009 int status
, ngroups
, groups
[NGROUPS
];
1013 status
= getgrouplist_internal(name
, basegid
, groups
, &ngroups
, 0);
1014 if (status
< 0) return status
;
1016 return setgroups(ngroups
, groups
);
1020 getgrnam(const char *name
)
1022 return getgr(name
, -2, GR_GET_NAME
);
1028 return getgr(NULL
, gid
, GR_GET_GID
);
1034 return getgr(NULL
, -2, GR_GET_ENT
);
1040 if (_lu_running()) lu_setgrent();
1041 else _old_setgrent();
1048 if (_lu_running()) lu_endgrent();
1049 else _old_endgrent();
1053 getgrnam_r(const char *name
, struct group
*grp
, char *buffer
, size_t bufsize
, struct group
**result
)
1055 return getgr_r(name
, -2, GR_GET_NAME
, grp
, buffer
, bufsize
, result
);
1059 getgrgid_r(gid_t gid
, struct group
*grp
, char *buffer
, size_t bufsize
, struct group
**result
)
1061 return getgr_r(NULL
, gid
, GR_GET_GID
, grp
, buffer
, bufsize
, result
);