4 my $p = "/bin:/usr/bin:/usr/xpg4/bin:/usr/ucb";
10 $ENV{LC_ALL} = "C"; # so that external utilities speak English
11 $ENV{LANGUAGE} = 'C'; # GNU locale extension
15 set_up_inc( '../lib' );
16 skip_all_if_miniperl("no dynamic loading on miniperl, no POSIX");
24 skip_all('getgrgid() not implemented')
25 unless eval { my($foo) = getgrgid(0); 1 };
27 skip_all("No 'id' or 'groups'") if
28 $^O eq 'MSWin32' || $^O eq 'NetWare' || $^O eq 'VMS' || $^O =~ /lynxos/i;
37 # Get our supplementary groups from the system by running commands
39 my ( $groups_command, $groups_string ) = system_groups()
40 or skip_all("No 'id' or 'groups'");
41 my @extracted_groups = extract_system_groups( $groups_string )
42 or skip_all("Can't parse '${groups_command}'");
45 my ($pwgnam) = getgrgid($pwgid);
47 print "# pwgid=$pwgid pwgnam=$pwgnam \$(=$(\n";
49 # Get perl's supplementary groups by looking at $(
50 my ( $gid_count, $all_perl_groups ) = perl_groups();
51 my %basegroup = basegroups( $pwgid, $pwgnam );
52 my @extracted_supplementary_groups = remove_basegroup( \ %basegroup, \ @extracted_groups );
57 # Test: The supplementary groups in $( should match the
58 # getgroups(2) kernal API call.
60 my $ngroups_max = posix_ngroups_max();
61 if ( defined $ngroups_max && $ngroups_max < @extracted_groups ) {
62 # Some OSes (like darwin)but conceivably others might return
63 # more groups from `id -a' than can be handled by the
64 # kernel. On darwin, NGROUPS_MAX is 16 and 12 are taken up for
67 # There is more fall-out from this than just Perl's unit
68 # tests. You may be a member of a group according to Active
69 # Directory (or whatever) but the OS won't respect it because
70 # it's the 17th (or higher) group and there's no space to
71 # store your membership.
72 print "ok 1 # SKIP Your platform's `$groups_command' is broken\n";
76 # darwin uses getgrouplist(3) or an Open Directory API within
77 # /usr/bin/id and /usr/bin/groups which while "nice" isn't
78 # accurate for this test. The hard, real, list of groups we're
79 # running in derives from getgroups(2) and is not dynamic but
80 # the Libc API getgrouplist(3) is.
82 # In practical terms, this meant that while `id -a' can be
83 # relied on in other OSes to purely use getgroups(2) and show
84 # us what's real, darwin will use getgrouplist(3) to show us
85 # what might be real if only we'd open a new console.
87 print "ok 1 # SKIP darwin's `${groups_command}' can't be trusted\n";
92 # Read $( but ignore any groups in $( that we failed to parse
93 # successfully out of the `id -a` mess.
95 my @perl_groups = remove_unparsed_entries( \ @extracted_groups,
96 \ @$all_perl_groups );
97 my @supplementary_groups = remove_basegroup( \ %basegroup,
101 if ( match_groups( \ @supplementary_groups,
102 \ @extracted_supplementary_groups,
107 elsif ( cygwin_nt() ) {
108 %basegroup = unixy_cygwin_basegroups();
109 @extracted_supplementary_groups = remove_basegroup( \ %basegroup, \ @extracted_groups );
111 if ( match_groups( \ @supplementary_groups,
112 \ @extracted_supplementary_groups,
114 print "ok 1 # This Cygwin behaves like Unix (Win2k?)\n";
120 print "nok 1 (perl's `\$(' disagrees with `${groups_command}'\n";
124 # multiple 0's indicate GROUPSTYPE is currently long but should be short
125 $gid_count->{0} //= 0;
126 if ( 0 == $pwgid || $gid_count->{0} < 2 ) {
130 print "not ok 2 (groupstype should be type short, not long)\n";
136 # Get the system groups and the command used to fetch them.
139 my ( $cmd, $groups_string ) = _system_groups();
141 if ( $groups_string ) {
142 chomp $groups_string;
143 diag_variable( groups => $groups_string );
146 return ( $cmd, $groups_string );
149 # We have to find a command that prints all (effective
150 # and real) group names (not ids). The known commands are:
154 # Beware 1: some systems do just 'id -G' even when 'id -Gn' is used.
155 # Beware 2: id -Gn or id -a format might be id(name) or name(id).
156 # Beware 3: the groups= might be anywhere in the id output.
157 # Beware 4: groups can have spaces ('id -a' being the only defense against this)
158 # Beware 5: id -a might not contain the groups= part.
160 # That is, we might meet the following:
162 # foo bar zot # accept
163 # foo 22 42 bar zot # accept
164 # 1 22 42 2 3 # reject
165 # groups=(42),foo(1),bar(2),zot me(3) # parsed by $GROUP_RX1
166 # groups=22,42,1(foo),2(bar),3(zot(me)) # parsed by $GROUP_RX2
168 # and the groups= might be after, before, or between uid=... and gid=...
169 use constant GROUP_RX1 => qr/
177 use constant GROUP_RX2 => qr/
189 # prefer 'id' over 'groups' (is this ever wrong anywhere?)
190 # and 'id -a' over 'id -Gn' (the former is good about spaces in group names)
192 $cmd = 'id -a 2>/dev/null || id 2>/dev/null';
194 if ( $str && $str =~ /groups=/ ) {
195 # $str is of the form:
196 # uid=39957(gsar) gid=22(users) groups=33536,39181,22(users),0(root),1067(dev)
197 # FreeBSD since 6.2 has a fake id -a:
198 # uid=1001(tobez) gid=20(staff) groups=20(staff), 0(wheel), 68(dialer)
201 # Linux may also have a context= field
203 return ( $cmd, $str );
206 $cmd = 'id -Gn 2>/dev/null';
208 if ( $str && $str !~ /^[\d\s]$/ ) {
209 # $str could be of the form:
210 # users 33536 39181 root dev
211 return ( $cmd, $str );
214 $cmd = 'groups 2>/dev/null';
217 # may not reflect all groups in some places, so do a sanity check
220 # These test results *may* be bogus, as you appear to have AFS,
221 # and I can't find a working 'id' in your PATH (which I have set
224 # If these tests fail, report the particular incantation you use
225 # on this platform to find *all* the groups that an arbitrary
226 # user may belong to, using the 'perlbug' program.
229 return ( $cmd, $str );
235 # Convert the strings produced by parsing `id -a' into a list of group
237 sub extract_system_groups {
238 my ( $groups_string ) = @_;
240 # Remember that group names can contain whitespace, '-', '(parens)',
241 # et cetera. That is: do not \w, do not \S.
244 my @fields = split /\b(\w+=)/, $groups_string;
246 for my $i (0..@fields-2) {
247 if ($fields[$i] eq 'groups=') {
254 my @g = split m{, ?}, $gr;
255 # prefer names over numbers
257 if ( $_ =~ GROUP_RX1() || $_ =~ GROUP_RX2() ) {
258 push @extracted, $+{gr_name} || $+{gid};
261 print "# ignoring group entry [$_]\n";
265 diag_variable( gr => $gr );
266 diag_variable( g => join ',', @g );
267 diag_variable( ex_gr => join ',', @extracted );
273 # Get the POSIX value NGROUPS_MAX.
274 sub posix_ngroups_max {
276 POSIX::NGROUPS_MAX();
280 # Test if this is Apple's darwin
282 # Observed 'darwin-2level'
283 return $Config::Config{myuname} =~ /^darwin/;
286 # Test if this is Cygwin
288 return $Config::Config{myuname} =~ /^cygwin_nt/i;
291 # Get perl's supplementary groups and the number of times each gid
294 # Lookup perl's own groups from $(
295 my @gids = split ' ', $(;
298 for my $gid ( @gids ) {
301 my ($group) = getgrgid $gid;
303 # Why does this test prefer to not test groups which we don't have
304 # a name for? One possible answer is that my primary group comes
305 # from from my entry in the user database but isn't mentioned in
306 # the group database. Are there more reasons?
307 next if ! defined $group;
310 push @gr_name, $group;
313 diag_variable( gr_name => join ',', @gr_name );
315 return ( \ %gid_count, \ @gr_name );
318 # Remove entries from our parsing of $( that don't appear in our
319 # parsing of `id -a`.
320 sub remove_unparsed_entries {
321 my ( $extracted_groups, $perl_groups ) = @_;
328 grep { $was_extracted{$_} }
332 # Get a list of base groups. I'm not sure why cygwin by default is
335 my ( $pwgid, $pwgnam ) = @_;
348 # Cygwin might have another form of basegroup which we should actually use
349 sub unixy_cygwin_basegroups {
350 my ( $pwgid, $pwgnam ) = @_;
357 # Filter a full list of groups and return only the supplementary
359 sub remove_basegroup {
360 my ( $basegroups, $groups ) = @_;
363 grep { ! $basegroups->{$_} }
367 # Test supplementary groups to see if they're a close enough match or
368 # if there aren't any supplementary groups then validate the current
371 my ( $supplementary_groups, $extracted_supplementary_groups, $pwgid ) = @_;
373 # Compare perl vs system groups
375 $g{$_}[0] = 1 for @$supplementary_groups;
376 $g{$_}[1] = 1 for @$extracted_supplementary_groups;
378 # Find any mismatches
380 grep { ! ( $g{$_}[0] && $g{$_}[1] ) }
385 || ( ! @$supplementary_groups
386 && 1 == @$extracted_supplementary_groups
387 && $pwgid == $extracted_supplementary_groups->[0] );
390 # Print a nice little diagnostic.
392 my ( $label, $content ) = @_;
394 printf "# %-11s=%s\n", $label, $content;
398 # Removes duplicates from a list
402 grep { ! $seen{$_}++ }
406 # ex: set ts=8 sts=4 sw=4 et: