diff --git a/src/libnss.zig b/src/libnss.zig index 27e41a9..97dadb4 100644 --- a/src/libnss.zig +++ b/src/libnss.zig @@ -481,46 +481,43 @@ fn initgroups_dyn( // TODO: use db.userGids() const offset = user.additional_gids_offset; var vit = compress.varintSliceIteratorMust(db.additional_gids[offset..]); - const remaining = vit.remaining; - var gids = compress.deltaDecompressionIterator(&vit); - if (size.* < remaining) { - const oldsize = @intCast(usize, size.*); - const newsize = if (limit <= 0) - oldsize + gids.remaining() - else - math.min(@intCast(usize, limit), oldsize + gids.remaining()); - var buf = groupsp.*[0..oldsize]; - const new_groups = state.initgroups_dyn_allocator.realloc(buf, newsize); - if (new_groups) |newgroups| { - groupsp.* = newgroups.ptr; - size.* = @intCast(c_long, newsize); - } else |err| switch (err) { - error.OutOfMemory => { - errnop.* = @enumToInt(os.E.NOMEM); - return c.NSS_STATUS_TRYAGAIN; - }, - } - } - - // I was not able to understand the expected behavior of limit. In glibc - // compat-initgroups.c limit is used to malloc the buffer. Something like - // this is all over the place: - // if (limit > 0 && *size == limit) /* We reached the maximum. */ - // return; - // Our implementation will thus limit the number of *added* entries. - - var added: usize = 0; + // the implementation below is ported from glibc's db-initgroups.c + // even though we know the size of the groups upfront, I found it too difficult + // to preallocate and juggle size, start and limit while keeping glibc happy. + var any: bool = false; while (gids.nextMust()) |gid| { - if (limit > 0 and added == limit) break; + if (start.* == size.*) { + if (limit > 0 and size.* == limit) + return c.NSS_STATUS_SUCCESS; - added += 1; + const oldsize = @intCast(usize, size.*); + const newsize: usize = if (limit <= 0) + 2 * oldsize + else + math.min(@intCast(usize, limit), 2 * oldsize); + + var buf = groupsp.*[0..oldsize]; + const new_groups = state.initgroups_dyn_allocator.realloc(buf, newsize); + + if (new_groups) |newgroups| { + groupsp.* = newgroups.ptr; + size.* = @intCast(c_long, newsize); + } else |err| switch (err) { + error.OutOfMemory => { + errnop.* = @enumToInt(os.E.NOMEM); + return c.NSS_STATUS_TRYAGAIN; + }, + } + } + + any = true; groupsp.*[@intCast(usize, start.*)] = @intCast(u32, gid); start.* += 1; } - return if (added > 0) c.NSS_STATUS_SUCCESS else c.NSS_STATUS_NOTFOUND; + return if (any) c.NSS_STATUS_SUCCESS else c.NSS_STATUS_NOTFOUND; } fn getDBErrno(errnop: *c_int) ?*const DB {