- Timestamp:
- Jun 7, 2011, 12:58:14 PM (13 years ago)
- Location:
- branches/fc15-dev
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/fc15-dev
- Property svn:mergeinfo changed
/trunk (added) merged: 1811,1813-1819,1821-1825,1838,1848-1856,1858-1872,1877
- Property svn:mergeinfo changed
-
branches/fc15-dev/server/common/oursrc/nss_nonlocal/nonlocal-group.c
r1553 r1878 34 34 #include <syslog.h> 35 35 #include <errno.h> 36 #include <pwd.h> 36 37 #include <grp.h> 37 38 #include <nss.h> … … 39 40 #include "nonlocal.h" 40 41 42 /* 43 * If the MAGIC_NONLOCAL_GROUPNAME local group exists, then nonlocal 44 * users will be automatically added to it. Furthermore, if a local 45 * user is added to this group, then that user will inherit any 46 * nonlocal gids from a nonlocal user of the same name, as 47 * supplementary gids. 48 */ 41 49 #define MAGIC_NONLOCAL_GROUPNAME "nss-nonlocal-users" 50 51 /* 52 * If the MAGIC_LOCAL_GROUPNAME local group exists, then local users 53 * will be automatically added to it. 54 */ 42 55 #define MAGIC_LOCAL_GROUPNAME "nss-local-users" 56 57 /* 58 * If the MAGIC_NONLOCAL_USERNAME local user is added to a local 59 * group, then the local group will inherit the nonlocal membership of 60 * a group of the same gid. 61 */ 62 #define MAGIC_NONLOCAL_USERNAME "nss-nonlocal-users" 43 63 44 64 … … 52 72 53 73 54 static service_user * 55 nss_group_nonlocal_database(void) 56 { 57 static service_user *nip = NULL; 58 if (nip == NULL) 59 __nss_database_lookup("group_nonlocal", NULL, "", &nip); 60 61 return nip; 62 } 63 64 65 enum nss_status 66 check_nonlocal_gid(const char *user, gid_t gid, int *errnop) 67 { 68 static const char *fct_name = "getgrgid_r"; 69 static service_user *startp = NULL; 70 static void *fct_start = NULL; 71 enum nss_status status; 72 service_user *nip; 73 union { 74 enum nss_status (*l)(gid_t gid, struct group *grp, 75 char *buffer, size_t buflen, int *errnop); 76 void *ptr; 77 } fct; 74 static service_user *__nss_group_nonlocal_database; 75 76 static int 77 internal_function 78 __nss_group_nonlocal_lookup(service_user **ni, const char *fct_name, 79 void **fctp) 80 { 81 if (__nss_group_nonlocal_database == NULL 82 && __nss_database_lookup("group_nonlocal", NULL, NULL, 83 &__nss_group_nonlocal_database) < 0) 84 return -1; 85 86 *ni = __nss_group_nonlocal_database; 87 88 *fctp = __nss_lookup_function(*ni, fct_name); 89 return 0; 90 } 91 92 93 enum nss_status 94 check_nonlocal_gid(const char *user, const char *group, gid_t gid, int *errnop) 95 { 96 enum nss_status status; 78 97 struct group gbuf; 79 int old_errno = errno; 80 98 char *buf; 81 99 size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); 82 char *buf = malloc(buflen); 83 if (buf == NULL) { 84 *errnop = ENOMEM; 85 errno = old_errno; 86 return NSS_STATUS_TRYAGAIN; 87 } 88 89 if (fct_start == NULL && 90 __nss_group_lookup(&startp, fct_name, &fct_start) != 0) { 91 free(buf); 92 return NSS_STATUS_UNAVAIL; 93 } 94 nip = startp; 95 fct.ptr = fct_start; 96 do { 97 morebuf: 98 if (fct.l == _nss_nonlocal_getgrgid_r) 99 status = NSS_STATUS_NOTFOUND; 100 else 101 status = DL_CALL_FCT(fct.l, (gid, &gbuf, buf, buflen, errnop)); 102 if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) { 103 free(buf); 104 buflen *= 2; 105 buf = malloc(buflen); 106 if (buf == NULL) { 107 *errnop = ENOMEM; 108 errno = old_errno; 109 return NSS_STATUS_TRYAGAIN; 100 const struct walk_nss w = { 101 .lookup = &__nss_group_lookup, .fct_name = "getgrgid_r", 102 .status = &status, .errnop = errnop, .buf = &buf, .buflen = &buflen 103 }; 104 const __typeof__(&_nss_nonlocal_getgrgid_r) self = &_nss_nonlocal_getgrgid_r; 105 #define args (gid, &gbuf, buf, buflen, errnop) 106 #include "walk_nss.h" 107 #undef args 108 109 if (status == NSS_STATUS_TRYAGAIN) 110 return status; 111 else if (status != NSS_STATUS_SUCCESS) 112 return NSS_STATUS_SUCCESS; 113 114 if (group == NULL || strcmp(gbuf.gr_name, group) == 0) { 115 char *const *mem; 116 for (mem = gbuf.gr_mem; *mem != NULL; mem++) 117 if (strcmp(*mem, MAGIC_NONLOCAL_USERNAME) == 0) { 118 status = check_nonlocal_user(*mem, errnop); 119 if (status == NSS_STATUS_TRYAGAIN) { 120 free(buf); 121 return status; 122 } else if (status == NSS_STATUS_NOTFOUND) { 123 free(buf); 124 return NSS_STATUS_SUCCESS; 125 } 126 break; 110 127 } 111 goto morebuf; 112 } 113 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 114 115 if (status == NSS_STATUS_SUCCESS) { 116 syslog(LOG_DEBUG, "nss_nonlocal: removing local group %u (%s) from non-local user %s\n", gbuf.gr_gid, gbuf.gr_name, user); 117 status = NSS_STATUS_NOTFOUND; 118 } else if (status != NSS_STATUS_TRYAGAIN) { 119 status = NSS_STATUS_SUCCESS; 120 } 121 128 } 129 130 syslog(LOG_DEBUG, "nss_nonlocal: removing local group %u (%s) from non-local user %s\n", gbuf.gr_gid, gbuf.gr_name, user); 122 131 free(buf); 123 return status;132 return NSS_STATUS_NOTFOUND; 124 133 } 125 134 … … 134 143 errno = 0; 135 144 gid = strtoul(grp->gr_name, &end, 10); 136 if (errno == 0 && *end == '\0' && (gid_t)gid == gid) 137 status = check_nonlocal_gid(user, gid, errnop); 138 errno = old_errno; 145 if (errno == 0 && *end == '\0' && (gid_t)gid == gid) { 146 errno = old_errno; 147 status = check_nonlocal_gid(user, grp->gr_name, gid, errnop); 148 } else 149 errno = old_errno; 139 150 if (status != NSS_STATUS_SUCCESS) 140 151 return status; 141 152 142 return check_nonlocal_gid(user, grp->gr_ gid, errnop);153 return check_nonlocal_gid(user, grp->gr_name, grp->gr_gid, errnop); 143 154 } 144 155 … … 146 157 get_local_group(const char *name, struct group *grp, char **buffer, int *errnop) 147 158 { 148 static const char *fct_name = "getgrnam_r"; 149 static service_user *startp = NULL; 150 static void *fct_start = NULL; 151 enum nss_status status; 152 service_user *nip; 153 union { 154 enum nss_status (*l)(const char *name, struct group *grp, 155 char *buffer, size_t buflen, int *errnop); 156 void *ptr; 157 } fct; 158 size_t buflen; 159 int old_errno = errno; 160 161 buflen = sysconf(_SC_GETGR_R_SIZE_MAX); 162 *buffer = malloc(buflen); 163 if (*buffer == NULL) { 164 *errnop = ENOMEM; 165 errno = old_errno; 166 return NSS_STATUS_TRYAGAIN; 167 } 168 169 if (fct_start == NULL && 170 __nss_group_lookup(&startp, fct_name, &fct_start) != 0) { 171 free(*buffer); 172 *buffer = NULL; 173 return NSS_STATUS_UNAVAIL; 174 } 175 nip = startp; 176 fct.ptr = fct_start; 177 do { 178 morebuf: 179 if (fct.l == _nss_nonlocal_getgrnam_r) 180 status = NSS_STATUS_NOTFOUND; 181 else 182 status = DL_CALL_FCT(fct.l, (name, grp, *buffer, buflen, errnop)); 183 if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) { 184 free(*buffer); 185 buflen *= 2; 186 *buffer = malloc(buflen); 187 if (*buffer == NULL) { 188 *errnop = ENOMEM; 189 errno = old_errno; 190 return NSS_STATUS_TRYAGAIN; 191 } 192 goto morebuf; 193 } 194 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 195 196 if (status != NSS_STATUS_SUCCESS) { 197 free(*buffer); 198 *buffer = NULL; 199 } 200 159 enum nss_status status; 160 size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); 161 const struct walk_nss w = { 162 .lookup = &__nss_group_lookup, .fct_name = "getgrnam_r", 163 .status = &status, .errnop = errnop, .buf = buffer, .buflen = &buflen 164 }; 165 const __typeof__(&_nss_nonlocal_getgrnam_r) self = &_nss_nonlocal_getgrnam_r; 166 #define args (name, grp, *buffer, buflen, errnop) 167 #include "walk_nss.h" 168 #undef args 201 169 return status; 202 170 } 203 171 204 static service_user *grent_ nip = NULL;172 static service_user *grent_startp, *grent_nip; 205 173 static void *grent_fct_start; 206 174 static union { … … 214 182 _nss_nonlocal_setgrent(int stayopen) 215 183 { 216 static const char *fct_name = "setgrent"; 217 static void *fct_start = NULL; 218 enum nss_status status; 219 service_user *nip; 220 union { 221 enum nss_status (*l)(int stayopen); 222 void *ptr; 223 } fct; 224 225 nip = nss_group_nonlocal_database(); 226 if (nip == NULL) 227 return NSS_STATUS_UNAVAIL; 228 if (fct_start == NULL) 229 fct_start = __nss_lookup_function(nip, fct_name); 230 fct.ptr = fct_start; 231 do { 232 if (fct.ptr == NULL) 233 status = NSS_STATUS_UNAVAIL; 234 else 235 status = DL_CALL_FCT(fct.l, (stayopen)); 236 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 184 enum nss_status status; 185 const struct walk_nss w = { 186 .lookup = &__nss_group_nonlocal_lookup, .fct_name = "setgrent", 187 .status = &status 188 }; 189 const __typeof__(&_nss_nonlocal_setgrent) self = NULL; 190 #define args (stayopen) 191 #include "walk_nss.h" 192 #undef args 237 193 if (status != NSS_STATUS_SUCCESS) 238 194 return status; 239 195 240 grent_nip = nip;241 196 if (grent_fct_start == NULL) 242 grent_fct_start = __nss_lookup_function(nip, grent_fct_name); 197 __nss_group_nonlocal_lookup(&grent_startp, grent_fct_name, 198 &grent_fct_start); 199 grent_nip = grent_startp; 243 200 grent_fct.ptr = grent_fct_start; 244 201 return NSS_STATUS_SUCCESS; … … 248 205 _nss_nonlocal_endgrent(void) 249 206 { 250 static const char *fct_name = "endgrent"; 251 static void *fct_start = NULL; 252 enum nss_status status; 253 service_user *nip; 254 union { 255 enum nss_status (*l)(void); 256 void *ptr; 257 } fct; 207 enum nss_status status; 208 const struct walk_nss w = { 209 .lookup = &__nss_group_nonlocal_lookup, .fct_name = "endgrent", 210 .status = &status 211 }; 212 const __typeof__(&_nss_nonlocal_endgrent) self = NULL; 258 213 259 214 grent_nip = NULL; 260 215 261 nip = nss_group_nonlocal_database(); 262 if (nip == NULL) 263 return NSS_STATUS_UNAVAIL; 264 if (fct_start == NULL) 265 fct_start = __nss_lookup_function(nip, fct_name); 266 fct.ptr = fct_start; 267 do { 268 if (fct.ptr == NULL) 269 status = NSS_STATUS_UNAVAIL; 270 else 271 status = DL_CALL_FCT(fct.l, ()); 272 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 216 #define args () 217 #include "walk_nss.h" 218 #undef args 273 219 return status; 274 220 } … … 315 261 char *buffer, size_t buflen, int *errnop) 316 262 { 317 static const char *fct_name = "getgrnam_r"; 318 static void *fct_start = NULL; 319 enum nss_status status; 320 service_user *nip; 321 union { 322 enum nss_status (*l)(const char *name, struct group *grp, 323 char *buffer, size_t buflen, int *errnop); 324 void *ptr; 325 } fct; 263 enum nss_status status; 264 const struct walk_nss w = { 265 .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrnam_r", 266 .status = &status, .errnop = errnop 267 }; 268 const __typeof__(&_nss_nonlocal_getgrnam_r) self = NULL; 326 269 327 270 char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV); … … 329 272 return NSS_STATUS_UNAVAIL; 330 273 331 nip = nss_group_nonlocal_database(); 332 if (nip == NULL) 333 return NSS_STATUS_UNAVAIL; 334 if (fct_start == NULL) 335 fct_start = __nss_lookup_function(nip, fct_name); 336 fct.ptr = fct_start; 337 do { 338 if (fct.ptr == NULL) 339 status = NSS_STATUS_UNAVAIL; 340 else 341 status = DL_CALL_FCT(fct.l, (name, grp, buffer, buflen, errnop)); 342 if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) 343 break; 344 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 274 #define args (name, grp, buffer, buflen, errnop) 275 #include "walk_nss.h" 276 #undef args 345 277 if (status != NSS_STATUS_SUCCESS) 346 278 return status; … … 358 290 char *buffer, size_t buflen, int *errnop) 359 291 { 360 static const char *fct_name = "getgrgid_r"; 361 static void *fct_start = NULL; 362 enum nss_status status; 363 service_user *nip; 364 union { 365 enum nss_status (*l)(gid_t gid, struct group *grp, 366 char *buffer, size_t buflen, int *errnop); 367 void *ptr; 368 } fct; 292 enum nss_status status; 293 const struct walk_nss w = { 294 .lookup = &__nss_group_nonlocal_lookup, .fct_name = "getgrgid_r", 295 .status = &status, .errnop = errnop 296 }; 297 const __typeof__(&_nss_nonlocal_getgrgid_r) self = NULL; 369 298 370 299 char *nonlocal_ignore = getenv(NONLOCAL_IGNORE_ENV); … … 372 301 return NSS_STATUS_UNAVAIL; 373 302 374 nip = nss_group_nonlocal_database(); 375 if (nip == NULL) 376 return NSS_STATUS_UNAVAIL; 377 if (fct_start == NULL) 378 fct_start = __nss_lookup_function(nip, fct_name); 379 fct.ptr = fct_start; 380 do { 381 if (fct.ptr == NULL) 382 status = NSS_STATUS_UNAVAIL; 383 else 384 status = DL_CALL_FCT(fct.l, (gid, grp, buffer, buflen, errnop)); 385 if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) 386 break; 387 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 303 #define args (gid, grp, buffer, buflen, errnop) 304 #include "walk_nss.h" 305 #undef args 388 306 if (status != NSS_STATUS_SUCCESS) 389 307 return status; … … 397 315 } 398 316 317 static bool 318 add_group(gid_t group, long int *start, long int *size, gid_t **groupsp, 319 long int limit, int *errnop, enum nss_status *status) 320 { 321 int i, old_errno = errno; 322 for (i = 0; i < *start; ++i) 323 if ((*groupsp)[i] == group) 324 return true; 325 if (*start + 1 > *size) { 326 gid_t *newgroups; 327 long int newsize = 2 * *size; 328 if (limit > 0) { 329 if (*size >= limit) { 330 *status = NSS_STATUS_SUCCESS; 331 return false; 332 } 333 if (newsize > limit) 334 newsize = limit; 335 } 336 newgroups = realloc(*groupsp, newsize * sizeof((*groupsp)[0])); 337 errno = old_errno; 338 if (newgroups == NULL) { 339 *errnop = ENOMEM; 340 *status = NSS_STATUS_TRYAGAIN; 341 return false; 342 } 343 *groupsp = newgroups; 344 *size = newsize; 345 } 346 (*groupsp)[(*start)++] = group; 347 return true; 348 } 349 399 350 enum nss_status 400 351 _nss_nonlocal_initgroups_dyn(const char *user, gid_t group, long int *start, … … 402 353 int *errnop) 403 354 { 404 static const char *fct_name = "initgroups_dyn"; 405 static void *fct_start = NULL; 406 enum nss_status status; 407 service_user *nip; 408 union { 409 enum nss_status (*l)(const char *user, gid_t group, long int *start, 410 long int *size, gid_t **groupsp, long int limit, 411 int *errnop); 412 void *ptr; 413 } fct; 355 enum nss_status status; 356 const struct walk_nss w = { 357 .lookup = &__nss_group_nonlocal_lookup, .fct_name = "initgroups_dyn", 358 .status = &status, .errnop = errnop 359 }; 360 const __typeof__(&_nss_nonlocal_initgroups_dyn) self = NULL; 414 361 415 362 struct group local_users_group, nonlocal_users_group; 416 gid_t local_users_gid, gid; 417 int is_local = 0; 363 bool is_nonlocal = true; 418 364 char *buffer; 419 int old_errno;420 365 int in, out, i; 421 366 422 /* Check that the user is a nonlocal user before adding any groups. */ 367 /* Check that the user is a nonlocal user, or a member of the 368 * MAGIC_NONLOCAL_GROUPNAME group, before adding any groups. */ 423 369 status = check_nonlocal_user(user, errnop); 424 if (status == NSS_STATUS_TRYAGAIN) 425 return status; 426 else if (status != NSS_STATUS_SUCCESS) 427 is_local = 1; 428 429 old_errno = errno; 430 431 status = get_local_group(MAGIC_LOCAL_GROUPNAME, 432 &local_users_group, &buffer, errnop); 433 if (status == NSS_STATUS_SUCCESS) { 434 local_users_gid = local_users_group.gr_gid; 435 free(buffer); 436 } else if (status == NSS_STATUS_TRYAGAIN) { 437 return status; 438 } else { 439 syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!", 440 MAGIC_LOCAL_GROUPNAME); 441 local_users_gid = -1; 442 } 443 444 if (is_local) { 445 gid = local_users_gid; 446 } else { 447 status = get_local_group(MAGIC_NONLOCAL_GROUPNAME, 448 &nonlocal_users_group, &buffer, errnop); 370 if (status == NSS_STATUS_TRYAGAIN) { 371 return status; 372 } else if (status != NSS_STATUS_SUCCESS) { 373 is_nonlocal = false; 374 375 status = get_local_group(MAGIC_LOCAL_GROUPNAME, 376 &local_users_group, &buffer, errnop); 449 377 if (status == NSS_STATUS_SUCCESS) { 450 gid = nonlocal_users_group.gr_gid;451 378 free(buffer); 379 if (!add_group(local_users_group.gr_gid, start, size, groupsp, 380 limit, errnop, &status)) 381 return status; 452 382 } else if (status == NSS_STATUS_TRYAGAIN) { 453 383 return status; 454 384 } else { 455 syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!",456 MAGIC_NONLOCAL_GROUPNAME);457 gid = -1;458 } 459 } 460 461 if (gid != -1) {462 int i;463 for (i = 0; i < *start; ++i) 464 if ((*groupsp)[i] == gid)465 break;466 if (i >= *start) {467 if (*start + 1 > *size) {468 gid_t *newgroups;469 long int newsize = 2 * *size;470 if (limit > 0) {471 if (*size >= limit)472 return NSS_STATUS_SUCCESS;473 i f (newsize > limit)474 newsize = limit;385 syslog(LOG_WARNING, 386 "nss_nonlocal: Group %s does not exist locally!", 387 MAGIC_LOCAL_GROUPNAME); 388 } 389 } 390 391 status = get_local_group(MAGIC_NONLOCAL_GROUPNAME, 392 &nonlocal_users_group, &buffer, errnop); 393 if (status == NSS_STATUS_SUCCESS) { 394 free(buffer); 395 if (is_nonlocal) { 396 if (!add_group(nonlocal_users_group.gr_gid, start, size, groupsp, 397 limit, errnop, &status)) 398 return status; 399 } else { 400 int i; 401 for (i = 0; i < *start; ++i) { 402 if ((*groupsp)[i] == nonlocal_users_group.gr_gid) { 403 is_nonlocal = true; 404 break; 475 405 } 476 newgroups = realloc(*groupsp, newsize * sizeof((*groupsp)[0])); 477 if (newgroups == NULL) { 478 *errnop = ENOMEM; 479 errno = old_errno; 480 return NSS_STATUS_TRYAGAIN; 406 } 407 408 if (is_nonlocal) { 409 struct passwd pwbuf; 410 char *buf; 411 int nonlocal_errno = *errnop; 412 status = get_nonlocal_passwd(user, &pwbuf, &buf, errnop); 413 414 if (status == NSS_STATUS_SUCCESS) { 415 nonlocal_errno = *errnop; 416 status = check_nonlocal_gid(user, NULL, pwbuf.pw_gid, 417 &nonlocal_errno); 418 free(buf); 481 419 } 482 *groupsp = newgroups; 483 *size = newsize; 420 421 if (status == NSS_STATUS_SUCCESS) { 422 if (!add_group(pwbuf.pw_gid, start, size, groupsp, limit, 423 errnop, &status)) 424 return status; 425 } else if (status == NSS_STATUS_TRYAGAIN) { 426 *errnop = nonlocal_errno; 427 return status; 428 } 484 429 } 485 (*groupsp)[(*start)++] = gid; 486 } 487 } 488 489 if (is_local) 430 } 431 } else if (status == NSS_STATUS_TRYAGAIN) { 432 if (is_nonlocal) 433 return status; 434 } else { 435 syslog(LOG_WARNING, "nss_nonlocal: Group %s does not exist locally!", 436 MAGIC_NONLOCAL_GROUPNAME); 437 } 438 439 if (!is_nonlocal) 490 440 return NSS_STATUS_SUCCESS; 491 441 492 442 in = out = *start; 493 443 494 nip = nss_group_nonlocal_database(); 495 if (nip == NULL) 496 return NSS_STATUS_UNAVAIL; 497 if (fct_start == NULL) 498 fct_start = __nss_lookup_function(nip, fct_name); 499 fct.ptr = fct_start; 500 501 do { 502 if (fct.ptr == NULL) 503 status = NSS_STATUS_UNAVAIL; 504 else 505 status = DL_CALL_FCT(fct.l, (user, group, start, size, groupsp, limit, errnop)); 506 if (status == NSS_STATUS_TRYAGAIN && *errnop == ERANGE) 507 break; 508 } while (__nss_next(&nip, fct_name, &fct.ptr, status, 0) == 0); 444 #define args (user, group, start, size, groupsp, limit, errnop) 445 #include "walk_nss.h" 446 #undef args 509 447 if (status != NSS_STATUS_SUCCESS) 510 448 return status; … … 519 457 continue; 520 458 521 /* Don't let users get into MAGIC_LOCAL_GROUPNAME from nonlocal reasons. */ 522 if (local_users_gid == (*groupsp)[in]) { 523 syslog(LOG_WARNING, "nss_nonlocal: Nonlocal user %s removed from special local users group %s", 524 user, MAGIC_LOCAL_GROUPNAME); 525 continue; 526 } 527 528 status = check_nonlocal_gid(user, (*groupsp)[in], &nonlocal_errno); 459 status = check_nonlocal_gid(user, NULL, (*groupsp)[in], 460 &nonlocal_errno); 529 461 if (status == NSS_STATUS_SUCCESS) { 530 462 (*groupsp)[out++] = (*groupsp)[in];
Note: See TracChangeset
for help on using the changeset viewer.