File Coverage

blib/lib/Unix/Groups/FFI.pm
Criterion Covered Total %
statement 21 21 100.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 28 28 100.0


line stmt bran cond sub pod time code
1             package Unix::Groups::FFI;
2              
3 1     1   78356 use strict;
  1         9  
  1         24  
4 1     1   5 use warnings;
  1         2  
  1         20  
5 1     1   5 use Carp 'croak';
  1         1  
  1         38  
6 1     1   366 use Errno qw(EINVAL ERANGE);
  1         1075  
  1         86  
7 1     1   6 use Exporter 'import';
  1         2  
  1         19  
8 1     1   580 use FFI::Platypus;
  1         6931  
  1         33  
9              
10             use constant {
11 1         663 MAX_NGROUPS_MAX => 65536,
12             MAX_GETGROUPLIST_TRIES => 5,
13             GETGROUPLIST_COUNT_LOW => 32,
14             GETGROUPLIST_COUNT_HIGH => 256,
15             GETGROUPLIST_COUNT_MAX => 65536,
16 1     1   6 };
  1         2  
17              
18             our $VERSION = '1.000';
19              
20             our @EXPORT_OK = qw(getgroups setgroups getgrouplist initgroups);
21              
22             our @CARP_NOT = qw(FFI::Platypus);
23              
24             my $ffi = FFI::Platypus->new(api => 1, lib => [undef], ignore_not_found => 1);
25              
26             $ffi->attach(getgroups => ['int', 'gid_t[]'] => 'int', sub {
27             my ($xsub) = @_;
28             my $count = $xsub->(0, []);
29             croak "$!" if $count < 0;
30             return () if $count == 0;
31             my @groups = (0)x$count;
32             my $rc = $xsub->($count, \@groups);
33             if ($rc < 0 and $! == EINVAL) {
34             @groups = (0)x(MAX_NGROUPS_MAX);
35             $rc = $xsub->(MAX_NGROUPS_MAX, \@groups);
36             }
37             croak "$!" if $rc < 0;
38             return @groups[0..$rc-1];
39             });
40              
41             $ffi->attach(setgroups => ['size_t', 'gid_t[]'] => 'int', sub {
42             my ($xsub, @groups) = @_;
43             my $rc = $xsub->(scalar(@groups), \@groups);
44             croak "$!" if $rc < 0;
45             return 0;
46             });
47              
48             $ffi->attach(getgrouplist => ['string', 'gid_t', 'gid_t[]', 'int*'] => 'int', sub {
49             my ($xsub, $user, $group) = @_;
50             $user = '' unless defined $user;
51             my $gid = (getpwnam $user)[3];
52             do { $! = EINVAL; croak "$!" } unless defined $gid;
53             $group = $gid unless defined $group;
54             my ($count, $last_count, @groups) = (1, 1, 0);
55             my $rc = $xsub->($user, $group, \@groups, \$count);
56             my $tries = 0;
57             while ($rc < 0 and $tries++ < MAX_GETGROUPLIST_TRIES) {
58             if ($count <= $last_count) {
59             # count too small, but didn't get a larger one
60             # some implementations short-circuit so we have to guess
61             if ($last_count < GETGROUPLIST_COUNT_LOW) {
62             $count = GETGROUPLIST_COUNT_LOW;
63             } elsif ($last_count < GETGROUPLIST_COUNT_HIGH) {
64             $count = GETGROUPLIST_COUNT_HIGH;
65             } else {
66             $count = GETGROUPLIST_COUNT_MAX;
67             }
68             }
69             @groups = (0)x$count;
70             $last_count = $count;
71             $rc = $xsub->($user, $group, \@groups, \$count);
72             }
73             do { $! = ERANGE; croak "$!" } if $rc < 0;
74             return @groups[0..$count-1];
75             });
76              
77             $ffi->attach(initgroups => ['string', 'gid_t'] => 'int', sub {
78             my ($xsub, $user, $group) = @_;
79             $user = '' unless defined $user;
80             my $gid = (getpwnam $user)[3];
81             do { $! = EINVAL; croak "$!" } unless defined $gid;
82             $group = $gid unless defined $group;
83             my $rc = $xsub->($user, $group);
84             croak "$!" if $rc < 0;
85             return 0;
86             });
87              
88             1;
89              
90             =head1 NAME
91              
92             Unix::Groups::FFI - Interface to Unix group syscalls
93              
94             =head1 SYNOPSIS
95              
96             use Unix::Groups::FFI qw(getgroups setgroups getgrouplist initgroups);
97              
98             my @gids = getgroups;
99             setgroups(@gids);
100             my @gids = getgrouplist($username, $gid);
101             initgroups($username, $gid);
102              
103             =head1 DESCRIPTION
104              
105             This module provides a L interface to several syscalls
106             related to Unix groups, including L, L,
107             L, and L. As such it will only work on
108             Unix-like operating systems.
109              
110             =head1 FUNCTIONS
111              
112             All functions are exported individually on demand. A function will not be
113             available for export if the system does not implement the corresponding
114             syscall.
115              
116             =head2 getgroups
117              
118             my @gids = getgroups;
119              
120             Returns the supplementary group IDs of the current process via L.
121              
122             =head2 setgroups
123              
124             setgroups(@gids);
125              
126             Sets the supplementary group IDs for the current process via L.
127             Attempting to set more than C groups (32 before Linux 2.6.4 or
128             65536 since Linux 2.6.4) will result in an C error. Passing an empty
129             list of group IDs may result in unspecified behavior. The C
130             L or equivalent privilege is required.
131              
132             =head2 getgrouplist
133              
134             my @gids = getgrouplist($username, $gid);
135             my @gids = getgrouplist($username);
136              
137             Returns the group IDs for all groups of which C<$username> is a member, also
138             including C<$gid> (without repetition), via L. If C<$username>
139             does not exist on the system, an C error will result.
140              
141             As a special case, the primary group ID of C<$username> is included if C<$gid>
142             is not passed.
143              
144             =head2 initgroups
145              
146             initgroups($username, $gid);
147             initgroups($username);
148              
149             Initializes the supplementary group access list for the current process to all
150             groups of which C<$username> is a member, also including C<$gid> (without
151             repetition), via L. If C<$username> does not exist on the
152             system, an C error will result. The C
153             L or equivalent privilege is required.
154              
155             As a special case, the primary group ID of C<$username> is included if C<$gid>
156             is not passed.
157              
158             =head1 ERROR HANDLING
159              
160             All functions will throw an exception containing the syscall error message in
161             the event of an error. L will also have been set by the syscall,
162             so you could check it after trapping the exception for finer exception
163             handling:
164              
165             use Unix::Groups::FFI 'setgroups';
166             use Syntax::Keyword::Try;
167             use Errno qw(EINVAL EPERM ENOMEM);
168              
169             try { setgroups((0)x2**16) }
170             catch {
171             if ($! == EINVAL) {
172             die 'Tried to set too many groups';
173             } elsif ($! == EPERM) {
174             die 'Insufficient privileges to set groups';
175             } elsif ($! == ENOMEM) {
176             die 'Out of memory';
177             } else {
178             die $@;
179             }
180             }
181              
182             See the documentation for each syscall for details on the possible error codes.
183              
184             =head1 BUGS
185              
186             Report any issues on the public bugtracker.
187              
188             =head1 AUTHOR
189              
190             Dan Book
191              
192             =head1 COPYRIGHT AND LICENSE
193              
194             This software is Copyright (c) 2018 by Dan Book.
195              
196             This is free software, licensed under:
197              
198             The Artistic License 2.0 (GPL Compatible)
199              
200             =head1 SEE ALSO
201              
202             L, L, L