Cygwin: flock: Fix overlap handling in lf_setlock() and lf_clearlock()
[newlib-cygwin.git] / winsup / cygwin / grp.cc
blob5f80d7aa7ec56ad7c7aec5fa7ea99a90c626c2e3
1 /* grp.cc
3 Original stubs by Jason Molenda of Cygnus Support, crash@cygnus.com
4 First implementation by Gunther Ebert, gunther.ebert@ixos-leipzig.de
6 This file is part of Cygwin.
8 This software is a copyrighted work licensed under the terms of the
9 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
10 details. */
12 #include "winsup.h"
13 #include <lm.h>
14 #include <ntsecapi.h>
15 #include <assert.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include "cygerrno.h"
19 #include "pinfo.h"
20 #include "path.h"
21 #include "fhandler.h"
22 #include "dtable.h"
23 #include "cygheap.h"
24 #include "ntdll.h"
25 #include "miscfuncs.h"
26 #include "ldap.h"
27 #include "tls_pbuf.h"
29 static char * NO_COPY_RO null_ptr;
31 bool
32 pwdgrp::parse_group ()
34 pg_grp &grp = group ()[curr_lines];
35 grp.g.gr_name = next_str (':');
36 if (!*grp.g.gr_name)
37 return false;
38 grp.g.gr_passwd = next_str (':');
39 /* Note that lptr points to the first byte of the gr_gid field.
40 We deliberately ignore the gr_gid and gr_mem entries when copying
41 the buffer content since they are not referenced anymore. */
42 grp.len = lptr - grp.g.gr_name;
43 if (!next_num (grp.g.gr_gid))
44 return false;
45 /* Don't generate gr_mem entries. */
46 grp.g.gr_mem = &null_ptr;
47 cygsid csid;
48 if (csid.getfromgr_passwd (&grp.g))
49 RtlCopySid (SECURITY_MAX_SID_SIZE, grp.sid, csid);
50 return true;
53 muto NO_COPY pwdgrp::pglock;
55 void
56 pwdgrp::init_grp ()
58 pwdgrp_buf_elem_size = sizeof (pg_grp);
59 parse = &pwdgrp::parse_group;
62 struct group *
63 pwdgrp::find_group (cygpsid &sid)
65 for (ULONG i = 0; i < curr_lines; i++)
66 if (sid == group ()[i].sid)
67 return &group ()[i].g;
68 return NULL;
71 struct group *
72 pwdgrp::find_group (const char *name)
74 for (ULONG i = 0; i < curr_lines; i++)
75 if (strcasematch (group ()[i].g.gr_name, name))
76 return &group ()[i].g;
77 return NULL;
80 struct group *
81 pwdgrp::find_group (gid_t gid)
83 for (ULONG i = 0; i < curr_lines; i++)
84 if (gid == group ()[i].g.gr_gid)
85 return &group ()[i].g;
86 return NULL;
89 struct group *
90 internal_getgrsid (cygpsid &sid, cyg_ldap *pldap)
92 struct group *ret;
94 cygheap->pg.nss_init ();
95 /* Check caches first. */
96 if (cygheap->pg.nss_cygserver_caching ()
97 && (ret = cygheap->pg.grp_cache.cygserver.find_group (sid)))
98 return ret;
99 if (cygheap->pg.nss_grp_files ()
100 && (ret = cygheap->pg.grp_cache.file.find_group (sid)))
101 return ret;
102 if (cygheap->pg.nss_grp_db ()
103 && (ret = cygheap->pg.grp_cache.win.find_group (sid)))
104 return ret;
105 /* Ask sources afterwards. */
106 if (cygheap->pg.nss_cygserver_caching ()
107 && (ret = cygheap->pg.grp_cache.cygserver.add_group_from_cygserver (sid)))
108 return ret;
109 if (cygheap->pg.nss_grp_files ())
111 cygheap->pg.grp_cache.file.check_file ();
112 if ((ret = cygheap->pg.grp_cache.file.add_group_from_file (sid)))
113 return ret;
115 if (cygheap->pg.nss_grp_db ())
116 return cygheap->pg.grp_cache.win.add_group_from_windows (sid, pldap);
117 return NULL;
120 /* Like internal_getgrsid but return only already cached data,
121 NULL otherwise. */
122 static struct group *
123 internal_getgrsid_cachedonly (cygpsid &sid)
125 struct group *ret;
127 /* Check caches only. */
128 if (cygheap->pg.nss_cygserver_caching ()
129 && (ret = cygheap->pg.grp_cache.cygserver.find_group (sid)))
130 return ret;
131 if (cygheap->pg.nss_grp_files ()
132 && (ret = cygheap->pg.grp_cache.file.find_group (sid)))
133 return ret;
134 if (cygheap->pg.nss_grp_db ()
135 && (ret = cygheap->pg.grp_cache.win.find_group (sid)))
136 return ret;
137 return NULL;
140 /* Called from internal_getgroups. The full information required to create
141 a group account entry is already available from the LookupAccountSids
142 call. internal_getgrfull passes all available info into
143 pwdgrp::fetch_account_from_line, thus avoiding a LookupAccountSid call
144 for each group. This is quite a bit faster, especially in slower
145 environments. */
146 static struct group * __attribute__((used))
147 internal_getgrfull (fetch_acc_t &full_acc, cyg_ldap *pldap)
149 struct group *ret;
151 cygheap->pg.nss_init ();
152 /* Skip local caches, internal_getgroups already called
153 internal_getgrsid_cachedonly. */
154 if (cygheap->pg.nss_cygserver_caching ()
155 && (ret = cygheap->pg.grp_cache.cygserver.add_group_from_cygserver
156 (full_acc.sid)))
157 return ret;
158 if (cygheap->pg.nss_grp_files ())
160 cygheap->pg.grp_cache.file.check_file ();
161 if ((ret = cygheap->pg.grp_cache.file.add_group_from_file
162 (full_acc.sid)))
163 return ret;
165 if (cygheap->pg.nss_grp_db ())
166 return cygheap->pg.grp_cache.win.add_group_from_windows (full_acc, pldap);
167 return NULL;
170 /* This function gets only called from mkgroup via cygwin_internal. */
171 struct group *
172 internal_getgrsid_from_db (cygpsid &sid)
174 cygheap->pg.nss_init ();
175 return cygheap->pg.grp_cache.win.add_group_from_windows (sid);
178 struct group *
179 internal_getgrnam (const char *name, cyg_ldap *pldap)
181 struct group *ret;
183 cygheap->pg.nss_init ();
184 /* Check caches first. */
185 if (cygheap->pg.nss_cygserver_caching ()
186 && (ret = cygheap->pg.grp_cache.cygserver.find_group (name)))
187 return ret;
188 if (cygheap->pg.nss_grp_files ()
189 && (ret = cygheap->pg.grp_cache.file.find_group (name)))
190 return ret;
191 if (cygheap->pg.nss_grp_db ()
192 && (ret = cygheap->pg.grp_cache.win.find_group (name)))
193 return ret;
194 /* Ask sources afterwards. */
195 if (cygheap->pg.nss_cygserver_caching ()
196 && (ret = cygheap->pg.grp_cache.cygserver.add_group_from_cygserver (name)))
197 return ret;
198 if (cygheap->pg.nss_grp_files ())
200 cygheap->pg.grp_cache.file.check_file ();
201 if ((ret = cygheap->pg.grp_cache.file.add_group_from_file (name)))
202 return ret;
204 if (cygheap->pg.nss_grp_db ())
205 return cygheap->pg.grp_cache.win.add_group_from_windows (name, pldap);
206 return NULL;
209 struct group *
210 internal_getgrgid (gid_t gid, cyg_ldap *pldap)
212 struct group *ret;
214 cygheap->pg.nss_init ();
215 /* Check caches first. */
216 if (cygheap->pg.nss_cygserver_caching ()
217 && (ret = cygheap->pg.grp_cache.cygserver.find_group (gid)))
218 return ret;
219 if (cygheap->pg.nss_grp_files ()
220 && (ret = cygheap->pg.grp_cache.file.find_group (gid)))
221 return ret;
222 if (cygheap->pg.nss_grp_db ()
223 && (ret = cygheap->pg.grp_cache.win.find_group (gid)))
224 return ret;
225 /* Ask sources afterwards. */
226 if (cygheap->pg.nss_cygserver_caching ()
227 && (ret = cygheap->pg.grp_cache.cygserver.add_group_from_cygserver (gid)))
228 return ret;
229 if (cygheap->pg.nss_grp_files ())
231 cygheap->pg.grp_cache.file.check_file ();
232 if ((ret = cygheap->pg.grp_cache.file.add_group_from_file (gid)))
233 return ret;
235 if (cygheap->pg.nss_grp_db () || gid == ILLEGAL_GID)
236 return cygheap->pg.grp_cache.win.add_group_from_windows (gid, pldap);
237 return NULL;
240 extern "C" int
241 getgrgid_r (gid_t gid, struct group *grp, char *buffer, size_t bufsize,
242 struct group **result)
244 *result = NULL;
246 if (!grp || !buffer)
247 return ERANGE;
249 struct group *tempgr = internal_getgrgid (gid);
250 pthread_testcancel ();
251 if (!tempgr)
252 return 0;
254 /* Check needed buffer size. Deliberately ignore gr_mem. */
255 size_t needsize = strlen (tempgr->gr_name) + strlen (tempgr->gr_passwd)
256 + 2 + sizeof (char *);
257 if (needsize > bufsize)
258 return ERANGE;
260 /* Make a copy of tempgr. Deliberately ignore gr_mem. */
261 *result = grp;
262 grp->gr_gid = tempgr->gr_gid;
263 buffer = stpcpy (grp->gr_name = buffer, tempgr->gr_name);
264 buffer = stpcpy (grp->gr_passwd = buffer + 1, tempgr->gr_passwd);
265 grp->gr_mem = (char **) (buffer + 1);
266 grp->gr_mem[0] = NULL;
267 return 0;
270 /* getgrgid/getgrnam are not reentrant. */
271 static struct {
272 struct group g;
273 char *buf;
274 size_t bufsiz;
275 } app_gr;
277 static struct group *
278 getgr_cp (struct group *tempgr)
280 if (!tempgr)
281 return NULL;
282 pg_grp *gr = (pg_grp *) tempgr;
283 if (app_gr.bufsiz < gr->len)
285 char *newbuf = (char *) realloc (app_gr.buf, gr->len);
286 if (!newbuf)
288 set_errno (ENOMEM);
289 return NULL;
291 app_gr.buf = newbuf;
292 app_gr.bufsiz = gr->len;
294 memcpy (app_gr.buf, gr->g.gr_name, gr->len);
295 memcpy (&app_gr.g, &gr->g, sizeof gr->g);
296 ptrdiff_t diff = app_gr.buf - gr->g.gr_name;
297 app_gr.g.gr_name += diff;
298 app_gr.g.gr_passwd += diff;
299 return &app_gr.g;
302 extern "C" struct group *
303 getgrgid (gid_t gid)
305 struct group *tempgr = internal_getgrgid (gid);
306 pthread_testcancel ();
307 return getgr_cp (tempgr);
310 extern "C" int
311 getgrnam_r (const char *nam, struct group *grp, char *buffer,
312 size_t bufsize, struct group **result)
314 *result = NULL;
316 if (!grp || !buffer)
317 return ERANGE;
319 struct group *tempgr = internal_getgrnam (nam);
320 pthread_testcancel ();
321 if (!tempgr)
322 return 0;
324 /* Check needed buffer size. Deliberately ignore gr_mem. */
325 size_t needsize = strlen (tempgr->gr_name) + strlen (tempgr->gr_passwd)
326 + 2 + sizeof (char *);
327 if (needsize > bufsize)
328 return ERANGE;
330 /* Make a copy of tempgr. Deliberately ignore gr_mem. */
331 *result = grp;
332 grp->gr_gid = tempgr->gr_gid;
333 buffer = stpcpy (grp->gr_name = buffer, tempgr->gr_name);
334 buffer = stpcpy (grp->gr_passwd = buffer + 1, tempgr->gr_passwd);
335 grp->gr_mem = (char **) (buffer + 1);
336 grp->gr_mem[0] = NULL;
337 return 0;
340 extern "C" struct group *
341 getgrnam (const char *name)
343 struct group *tempgr = internal_getgrnam (name);
344 pthread_testcancel ();
345 return getgr_cp (tempgr);
348 /* getgrent functions are not reentrant. */
349 static gr_ent grent;
351 void *
352 gr_ent::enumerate_caches ()
354 switch (max)
356 case 0:
357 if (cygheap->pg.nss_cygserver_caching ())
359 pwdgrp &grc = cygheap->pg.grp_cache.cygserver;
360 if (cnt < grc.cached_groups ())
361 return &grc.group ()[cnt++].g;
363 cnt = 0;
364 max = 1;
365 fallthrough;
366 case 1:
367 if (from_files)
369 pwdgrp &grf = cygheap->pg.grp_cache.file;
370 grf.check_file ();
371 if (cnt < grf.cached_groups ())
372 return &grf.group ()[cnt++].g;
374 cnt = 0;
375 max = 2;
376 fallthrough;
377 case 2:
378 if (from_db)
380 pwdgrp &grw = cygheap->pg.grp_cache.win;
381 if (cnt < grw.cached_groups ())
382 return &grw.group ()[cnt++].g;
384 break;
386 cnt = max = 0;
387 return NULL;
390 void *
391 gr_ent::enumerate_local ()
393 while (true)
395 if (!cnt)
397 DWORD total;
398 NET_API_STATUS ret;
400 if (buf)
402 NetApiBufferFree (buf);
403 buf = NULL;
405 if (resume == ULONG_MAX)
406 ret = ERROR_NO_MORE_ITEMS;
407 else
408 ret = NetLocalGroupEnum (NULL, 0, (PBYTE *) &buf,
409 MAX_PREFERRED_LENGTH,
410 &max, &total, &resume);
411 if (ret == NERR_Success)
412 resume = ULONG_MAX;
413 else if (ret != ERROR_MORE_DATA)
415 cnt = max = resume = 0;
416 return NULL;
419 while (cnt < max)
421 cygsid sid;
422 DWORD slen = SECURITY_MAX_SID_SIZE;
423 WCHAR dom[DNLEN + 1];
424 DWORD dlen = DNLEN + 1;
425 SID_NAME_USE acc_type;
427 if (!LookupAccountNameW (NULL,
428 ((PLOCALGROUP_INFO_0) buf)[cnt++].lgrpi0_name,
429 sid, &slen, dom, &dlen, &acc_type))
430 continue;
431 /* Skip builtin groups if we're enumerating AD as well to avoid
432 duplication. Don't skip "Power Users" and "Device Owners"
433 accounts, they don't show up in AD enumeration. */
434 if (cygheap->dom.member_machine ()
435 && nss_db_enum_primary ()
436 && sid_id_auth (sid) == 5 /* SECURITY_NT_AUTHORITY */
437 && sid_sub_auth (sid, 0) == SECURITY_BUILTIN_DOMAIN_RID
438 && sid_sub_auth (sid, 1) != DOMAIN_ALIAS_RID_POWER_USERS
439 && sid_sub_auth (sid, 1) != DOMAIN_ALIAS_RID_DEVICE_OWNERS)
440 continue;
441 fetch_user_arg_t arg;
442 arg.type = SID_arg;
443 arg.sid = &sid;
444 char *line = pg.fetch_account_from_windows (arg);
445 if (line)
446 return pg.add_account_post_fetch (line, false);
448 cnt = 0;
452 struct group *
453 gr_ent::getgrent (void)
455 if (state == rewound)
456 setent (true);
457 else
458 clear_cache ();
459 return (struct group *) getent ();
462 extern "C" void
463 setgrent ()
465 grent.setgrent ();
468 extern "C" struct group *
469 getgrent (void)
471 return grent.getgrent ();
474 extern "C" void
475 endgrent (void)
477 grent.endgrent ();
480 /* *_filtered functions are called from mkgroup */
481 void *
482 setgrent_filtered (int enums, PCWSTR enum_tdoms)
484 gr_ent *gr = new gr_ent;
485 if (gr)
486 gr->setgrent (enums, enum_tdoms);
487 return (void *) gr;
490 void *
491 getgrent_filtered (void *gr)
493 return (void *) ((gr_ent *) gr)->getgrent ();
496 void
497 endgrent_filtered (void *gr)
499 ((gr_ent *) gr)->endgrent ();
503 internal_getgroups (int gidsetsize, gid_t *grouplist, cyg_ldap *pldap)
505 NTSTATUS status;
506 HANDLE tok;
507 ULONG size;
508 PTOKEN_GROUPS groups;
509 PSID *sidp_buf;
510 ULONG scnt;
511 PLSA_REFERENCED_DOMAIN_LIST dlst = NULL;
512 PLSA_TRANSLATED_NAME nlst = NULL;
514 tmp_pathbuf tp;
515 struct group *grp;
516 int cnt = 0;
518 if (cygheap->user.groups.issetgroups ())
520 for (int pg = 0; pg < cygheap->user.groups.sgsids.count (); ++pg)
521 if ((grp = internal_getgrsid (cygheap->user.groups.sgsids.sids[pg],
522 pldap)))
524 if (cnt < gidsetsize)
525 grouplist[cnt] = grp->gr_gid;
526 ++cnt;
527 if (gidsetsize && cnt > gidsetsize)
529 cnt = -1;
530 break;
533 goto out;
536 /* If impersonated, use impersonation token. */
537 tok = cygheap->user.issetuid () ? cygheap->user.primary_token ()
538 : hProcToken;
540 /* Fetch groups from user token. */
541 groups = (PTOKEN_GROUPS) tp.w_get ();
542 status = NtQueryInformationToken (tok, TokenGroups, groups, 2 * NT_MAX_PATH,
543 &size);
544 if (!NT_SUCCESS (status))
546 debug_printf ("NtQueryInformationToken(TokenGroups) %y", status);
547 goto out;
549 /* Iterate over the group list and check which of them are already cached.
550 Those are simply copied to grouplist. The non-cached ones are collected
551 in sidp_buf for a later call to LsaLookupSids. */
552 sidp_buf = (PSID *) tp.w_get ();
553 scnt = 0;
554 for (DWORD pg = 0; pg < groups->GroupCount; ++pg)
556 cygpsid sid = groups->Groups[pg].Sid;
557 if ((groups->Groups[pg].Attributes
558 & (SE_GROUP_ENABLED | SE_GROUP_INTEGRITY_ENABLED)) == 0
559 || sid == well_known_world_sid)
560 continue;
561 if ((grp = internal_getgrsid_cachedonly (sid)))
563 if (cnt < gidsetsize)
564 grouplist[cnt] = grp->gr_gid;
565 ++cnt;
566 if (gidsetsize && cnt > gidsetsize)
568 cnt = -1;
569 goto out;
572 else
573 sidp_buf[scnt++] = sid;
575 /* If there are non-cached groups left, try to fetch them. */
576 if (scnt > 0)
578 /* Don't call LsaLookupSids if we're not utilizing the Windows account
579 DBs. If we don't have access to the AD, which is one good reason to
580 disable passwd/group: db in nsswitch.conf, then the subsequent call
581 to LsaLookupSids will take 5 - 10 seconds in some environments. */
582 if (!cygheap->pg.nss_grp_db ())
584 for (DWORD pg = 0; pg < scnt; ++pg)
586 cygpsid sid = sidp_buf[pg];
587 if ((grp = internal_getgrsid (sid, NULL)))
589 if (cnt < gidsetsize)
590 grouplist[cnt] = grp->gr_gid;
591 ++cnt;
592 if (gidsetsize && cnt > gidsetsize)
594 cnt = -1;
595 break;
599 goto out;
601 /* Otherwise call LsaLookupSids and call internal_getgrfull on the
602 returned groups. This performs a lot better than calling
603 internal_getgrsid on each group. */
604 status = STATUS_ACCESS_DENIED;
605 HANDLE lsa = lsa_open_policy (NULL, POLICY_LOOKUP_NAMES);
606 if (!lsa)
608 debug_printf ("POLICY_LOOKUP_NAMES right not given?");
609 goto out;
611 status = LsaLookupSids (lsa, scnt, sidp_buf, &dlst, &nlst);
612 lsa_close_policy (lsa);
613 if (NT_SUCCESS (status))
615 for (ULONG ncnt = 0; ncnt < scnt; ++ncnt)
617 static UNICODE_STRING empty = { 0, 0, (PWSTR) L"" };
618 fetch_acc_t full_acc =
620 .sid = sidp_buf[ncnt],
621 .name = &nlst[ncnt].Name,
622 .dom = &empty,
623 .acc_type = nlst[ncnt].Use
626 if (nlst[ncnt].DomainIndex >= 0)
627 full_acc.dom = &dlst->Domains[nlst[ncnt].DomainIndex].Name;
628 if ((grp = internal_getgrfull (full_acc, pldap)))
630 if (cnt < gidsetsize)
631 grouplist[cnt] = grp->gr_gid;
632 ++cnt;
633 if (gidsetsize && cnt > gidsetsize)
635 cnt = -1;
636 break;
643 out:
644 if (dlst)
645 LsaFreeMemory (dlst);
646 if (nlst)
647 LsaFreeMemory (nlst);
648 if (cnt == -1)
649 set_errno (EINVAL);
650 return cnt;
653 extern "C" int
654 getgroups (int gidsetsize, gid_t *grouplist)
656 cyg_ldap cldap;
658 return internal_getgroups (gidsetsize, grouplist, &cldap);
661 /* Core functionality of initgroups and getgrouplist. */
662 static void
663 get_groups (const char *user, gid_t gid, cygsidlist &gsids)
665 cyg_ldap cldap;
667 cygheap->user.deimpersonate ();
668 struct passwd *pw = internal_getpwnam (user, &cldap);
669 struct group *grp = internal_getgrgid (gid, &cldap);
670 cygsid usersid, grpsid;
671 if (usersid.getfrompw (pw))
672 get_server_groups (gsids, usersid, NO_CHK_DISABLED);
673 if (gid != ILLEGAL_GID && grpsid.getfromgr (grp))
674 gsids += grpsid;
675 cygheap->user.reimpersonate ();
678 extern "C" int
679 initgroups (const char *user, gid_t gid)
681 assert (user != NULL);
682 cygsidlist tmp_gsids (cygsidlist_auto, 12);
683 get_groups (user, gid, tmp_gsids);
684 cygsidlist new_gsids (cygsidlist_alloc, tmp_gsids.count ());
685 for (int i = 0; i < tmp_gsids.count (); i++)
686 new_gsids.sids[i] = tmp_gsids.sids[i];
687 new_gsids.count (tmp_gsids.count ());
688 cygheap->user.groups.update_supp (new_gsids);
689 syscall_printf ( "0 = initgroups(%s, %u)", user, gid);
690 return 0;
693 extern "C" int
694 getgrouplist (const char *user, gid_t gid, gid_t *groups, int *ngroups)
696 int ret = 0;
697 int cnt = 0;
698 struct group *grp;
699 cyg_ldap cldap;
701 /* Note that it's not defined if groups or ngroups may be NULL!
702 GLibc does not check the pointers on entry and just uses them.
703 FreeBSD calls assert for ngroups and allows a NULL groups if
704 *ngroups is 0. We follow FreeBSD's lead here, but always allow
705 a NULL groups pointer. */
706 assert (user != NULL);
707 assert (ngroups != NULL);
709 cygsidlist tmp_gsids (cygsidlist_auto, 12);
710 get_groups (user, gid, tmp_gsids);
711 for (int i = 0; i < tmp_gsids.count (); i++)
712 if ((grp = internal_getgrsid (tmp_gsids.sids[i], &cldap)) != NULL)
714 if (groups && cnt < *ngroups)
715 groups[cnt] = grp->gr_gid;
716 ++cnt;
718 if (cnt > *ngroups)
719 ret = -1;
720 else
721 ret = cnt;
722 *ngroups = cnt;
724 syscall_printf ( "%d = getgrouplist(%s, %u, %p, %d)",
725 ret, user, gid, groups, *ngroups);
726 return ret;
729 /* setgroups: standards? */
730 extern "C" int
731 setgroups (int ngroups, const gid_t *grouplist)
733 syscall_printf ("setgroups (%d)", ngroups);
734 if (ngroups < 0 || (ngroups > 0 && !grouplist))
736 set_errno (EINVAL);
737 return -1;
740 cygsidlist gsids (cygsidlist_alloc, ngroups);
741 struct group *grp;
742 cyg_ldap cldap;
744 if (ngroups && !gsids.sids)
745 return -1;
747 for (int gidx = 0; gidx < ngroups; ++gidx)
749 if ((grp = internal_getgrgid (grouplist[gidx], &cldap))
750 && gsids.addfromgr (grp))
751 continue;
752 debug_printf ("No sid found for gid %u", grouplist[gidx]);
753 gsids.free_sids ();
754 set_errno (EINVAL);
755 return -1;
757 cygheap->user.groups.update_supp (gsids);
758 return 0;