Fix function for getting uquotas on a FS
[webmin.git] / quota / linux-lib.pl
1 =head1 linux-lib.pl
2
3 Quota functions for all linux version. See quota-lib.pl for summary
4 documentation for this module.
5
6 =cut
7
8 # Tell the mount module not to check which filesystems are supported,
9 # as we don't care for the calls made by this module
10 $mount::no_check_support = 1;
11
12 # Pass UIDs and GIDs to edquota instead of names
13 $edquota_use_ids = 1;
14
15 =head2 quotas_init
16
17 Returns an error message if some quota commands or functionality is missing
18 on this system, undef otherwise.
19
20 =cut
21 sub quotas_init
22 {
23 if (&has_command("quotaon") && &has_command("quotaoff")) {
24         return undef;
25         }
26 else {
27         return "The quotas package does not appear to be installed on ".
28                "your system\n";
29         }
30 }
31
32 =head2 quotas_supported
33
34 Checks what quota types this OS supports. Returns 1 for user quotas,
35 2 for group quotas or 3 for both.
36
37 =cut
38 sub quotas_supported
39 {
40 return 3;
41 }
42
43 =head2 free_space(filesystem, [blocksize])
44
45 Finds the amount of free disk space on some system. Returns an array
46 containing : blocks-total, blocks-free, files-total, files-free
47
48 =cut
49 sub free_space
50 {
51 local(@out, @rv);
52 $out = &backquote_command("df -k $_[0]");
53 $out =~ /Mounted on\n\S+\s+(\d+)\s+\d+\s+(\d+)/;
54 if ($_[1]) {
55         push(@rv, int($1*1024/$_[1]), int($2*1024/$_[1]));
56         }
57 else {
58         push(@rv, $1, $2);
59         }
60 $out = &backquote_command("df -i $_[0]");
61 $out =~ /Mounted on\n\S+\s+(\d+)\s+\d+\s+(\d+)/;
62 push(@rv, $1, $2);
63 return @rv;
64 }
65
66 =head2 quota_can(&mnttab, &fstab)
67
68 Can this filesystem type support quotas? Takes array refs from mounted and
69 mountable filesystems, and returns one of the following :
70
71 =item 0 - No quota support (or not turned on in /etc/fstab).
72
73 =item 1 - User quotas only.
74
75 =item 2 - Group quotas only.
76
77 =item 3 - User and group quotas.
78
79 =cut
80 sub quota_can
81 {
82 return ($_[1]->[3] =~ /usrquota|usrjquota/ ||
83         $_[0]->[3] =~ /usrquota|usrjquota/ ? 1 : 0) +
84        ($_[1]->[3] =~ /grpquota|grpjquota/ ||
85         $_[0]->[3] =~ /grpquota|grpjquota/ ? 2 : 0);
86 }
87
88 =head2 quota_now(&mnttab, &fstab)
89
90 Are quotas currently active? Takes array refs from mounted and mountable
91 filesystems, and returns one of the following :
92
93 =item 0 - Not active.
94 =item 1 - User quotas active.
95 =item 2 - Group quotas active.
96 =item 3 - Both active.
97
98 Adding 4 means they cannot be turned off (such as for XFS)
99
100 =cut
101 sub quota_now
102 {
103 local $rv = 0;
104 local $dir = $_[0]->[0];
105 local %opts = map { $_, 1 } split(/,/, $_[0]->[3]);
106 if ($_[0]->[2] eq "xfs") {
107         # For XFS, assume enabled if setup in fstab
108         $rv += 1 if ($opts{'quota'} || $opts{'usrquota'} ||
109                      $opts{'uqnoenforce'});
110         $rv += 2 if ($opts{'grpquota'} || $opts{'gqnoenforce'});
111         return $rv + 4;
112         }
113 if ($_[0]->[4]%2 == 1) {
114         # test user quotas
115         if (-r "$dir/quota.user" || -r "$dir/aquota.user") {
116                 local $stout = &supports_status($dir, "user");
117                 if ($stout =~ /is\s+(on|off|enabled|disabled)/) {
118                         # Can use output from -p mode
119                         if ($stout =~ /is\s+(on|enabled)/) {
120                                 $rv += 1;
121                                 }
122                         }
123                 else {
124                         # Fall back to testing by running quotaon
125                         &clean_language();
126                         $out = &backquote_command(
127                                 "$config{'user_quotaon_command'} $dir 2>&1");
128                         &reset_environment();
129                         if ($out =~ /Device or resource busy/i) {
130                                 # already on..
131                                 $rv += 1;
132                                 }
133                         elsif ($out =~ /Package not installed/i) {
134                                 # No quota support!
135                                 return 0;
136                                 }
137                         else {
138                                 # was off.. need to turn on again
139                                 &execute_command(
140                                   "$config{'user_quotaoff_command'} $dir 2>&1");
141                                 }
142                         }
143                 }
144         }
145 if ($_[0]->[4] > 1) {
146         # test group quotas
147         if (-r "$dir/quota.group" || -r "$dir/aquota.group") {
148                 local $stout = &supports_status($dir, "group");
149                 if ($stout =~ /is\s+(on|off|enabled|disabled)/) {
150                         # Can use output from -p mode
151                         if ($stout =~ /is\s+(on|enabled)/) {
152                                 $rv += 2;
153                                 }
154                         }
155                 else {
156                         # Fall back to testing by running quotaon
157                         &clean_language();
158                         $out = &backquote_command(
159                                 "$config{'group_quotaon_command'} $dir 2>&1");
160                         &reset_environment();
161                         if ($out =~ /Device or resource busy/i) {
162                                 # already on..
163                                 $rv += 2;
164                                 }
165                         elsif ($out =~ /Package not installed/i) {
166                                 # No quota support!
167                                 return 0;
168                                 }
169                         else {
170                                 # was off.. need to turn on again
171                                 &execute_command(
172                                  "$config{'group_quotaoff_command'} $dir 2>&1");
173                                 }
174                         }
175                 }
176         }
177 return $rv;
178 }
179
180 =head2 supports_status(dir, mode)
181
182 Internal function to check if the quotaon -p flag is supported.
183
184 =cut
185 sub supports_status
186 {
187 if (!defined($supports_status_cache{$_[0],$_[1]})) {
188         &clean_language();
189         local $stout = &backquote_command(
190                 "$config{$_[1].'_quotaon_command'} -p $_[0] 2>&1");
191         &reset_environment();
192         $supports_status_cache{$_[0],$_[1]} =
193                 $stout =~ /is\s+(on|off|enabled|disabled)/ ? $stout : 0;
194         }
195 return $supports_status_cache{$_[0],$_[1]};
196 }
197
198 =head2 quotaon(filesystem, mode)
199
200 Activate quotas and create quota files for some filesystem. The mode can
201 be one of :
202
203 =item 1 - User only.
204
205 =item 2 - Group only.
206
207 =item 3 - User and group.
208
209 =cut
210 sub quotaon
211 {
212 local($out, $qf, @qfile, $flags, $version);
213 return if (&is_readonly_mode());
214
215 # Check which version of quota is being used
216 $out = &backquote_command("quota -V 2>&1");
217 if ($out =~ /\s(\d+\.\d+)/) {
218         $version = $1;
219         }
220
221 # Force load of quota kernel modules
222 &system_logged("modprobe quota_v2 >/dev/null 2>&1");
223
224 local $fmt = $version >= 2 ? "vfsv0" : "vfsold";
225 if ($_[1]%2 == 1) {
226         # turn on user quotas
227         local $qf = $version >= 2 ? "aquota.user" : "quota.user";
228         if (!-s "$_[0]/$qf") {
229                 # Setting up for the first time
230                 local $ok = 0;
231                 if (&has_command("convertquota") && $version >= 2) {
232                         # Try creating a quota.user file and converting it
233                         &open_tempfile(QUOTAFILE, ">>$_[0]/quota.user", 0, 1);
234                         &close_tempfile(QUOTAFILE);
235                         &set_ownership_permissions(undef, undef, 0600,
236                                                    "$_[0]/quota.user");
237                         &system_logged("convertquota -u $_[0] 2>&1");
238                         $ok = 1 if (!$?);
239                         &unlink_file("$_[0]/quota.user");
240                         }
241                 if (!$ok) {
242                         # Try to create an [a]quota.user file
243                         if ($version < 4) {
244                                 &open_tempfile(QUOTAFILE, ">>$_[0]/$qf", 0, 1);
245                                 &close_tempfile(QUOTAFILE);
246                                 &set_ownership_permissions(undef, undef, 0600,
247                                                            "$_[0]/$qf");
248                                 }
249                         &run_quotacheck($_[0]) ||
250                                 &run_quotacheck($_[0], "-u -f") ||
251                                 &run_quotacheck($_[0], "-u -f -m") ||
252                                 &run_quotacheck($_[0], "-u -f -m -c");
253                                 &run_quotacheck($_[0], "-u -f -m -c -F $fmt");
254                         }
255                 }
256         $out = &backquote_logged("$config{'user_quotaon_command'} $_[0] 2>&1");
257         if ($?) { return $out; }
258         }
259 if ($_[1] > 1) {
260         # turn on group quotas
261         local $qf = $version >= 2 ? "aquota.group" : "quota.group";
262         if (!-s "$_[0]/$qf") {
263                 # Setting up for the first time
264                 local $ok = 0;
265                 if (!$ok && &has_command("convertquota") && $version >= 2) {
266                         # Try creating a quota.group file and converting it
267                         &open_tempfile(QUOTAFILE, ">>$_[0]/quota.group", 0, 1);
268                         &close_tempfile(QUOTAFILE);
269                         &set_ownership_permissions(undef, undef, 0600,
270                                                    "$_[0]/quota.group");
271                         &system_logged("convertquota -g $_[0] 2>&1");
272                         $ok = 1 if (!$?);
273                         &unlink_file("$_[0]/quota.group");
274                         }
275                 if (!$ok) {
276                         # Try to create an [a]quota.group file
277                         if ($version < 4) {
278                                 &open_tempfile(QUOTAFILE, ">>$_[0]/$qf", 0, 1);
279                                 &close_tempfile(QUOTAFILE);
280                                 &set_ownership_permissions(undef, undef, 0600,
281                                                            "$_[0]/$qf");
282                                 }
283                         &run_quotacheck($_[0]) ||
284                                 &run_quotacheck($_[0], "-g -f") ||
285                                 &run_quotacheck($_[0], "-g -f -m") ||
286                                 &run_quotacheck($_[0], "-g -f -m -c") ||
287                                 &run_quotacheck($_[0], "-g -f -m -c -F $fmt");
288                         }
289                 }
290         $out = &backquote_logged("$config{'group_quotaon_command'} $_[0] 2>&1");
291         if ($?) { return $out; }
292         }
293 return undef;
294 }
295
296 =head2 run_quotacheck(filesys, args)
297
298 Runs the quotacheck command on some filesytem, and returns 1 on success or
299 0 on failure. Mainly for internal use when enabling quotas.
300
301 =cut
302 sub run_quotacheck
303 {
304 &clean_language();
305 local $out =&backquote_logged(
306         "$config{'quotacheck_command'} $_[1] $_[0] 2>&1");
307 &reset_environment();
308 return $? || $out =~ /cannot guess|cannot remount|cannot find|please stop/i ? 0 : 1;
309 }
310
311 =head2 quotaoff(filesystem, mode)
312
313 Turn off quotas for some filesystem. Mode must be 0 for users only, 1 for
314 groups only, or 2 for both.
315
316 =cut
317 sub quotaoff
318 {
319 return if (&is_readonly_mode());
320 local($out);
321 if ($_[1]%2 == 1) {
322         $out = &backquote_logged("$config{'user_quotaoff_command'} $_[0] 2>&1");
323         if ($?) { return $out; }
324         }
325 if ($_[1] > 1) {
326         $out = &backquote_logged("$config{'group_quotaoff_command'} $_[0] 2>&1");
327         if ($?) { return $out; }
328         }
329 return undef;
330 }
331
332 =head2 user_filesystems(user)
333
334 Fills the global hash %filesys with details of all filesystem some user has
335 quotas on, and returns a count of the number of filesystems. Some example code
336 best demonstrates how this function should be used:
337
338  foreign_require('quota', 'quota-lib.pl');
339  $n = quota::user_filesystems('joe');
340  for($i=0; $i<$n; $i++) {
341    print "filesystem=",$filesys{$i,'filesys'}," ",
342          "block quota=",$filesys{$i,'hblocks'}," ",
343          "blocks used=",$filesys{$i,'ublocks'},"\n";
344  }
345
346 =cut
347 sub user_filesystems
348 {
349 return &parse_quota_output("$config{'user_quota_command'} ".quotemeta($_[0]));
350 }
351
352 =head2 group_filesystems(user)
353
354 Fills the array %filesys with details of all filesystem some group has
355 quotas on, and returns the filesystem count. The format of %filesys is the same
356 as documented in the user_filesystems function.
357
358 =cut
359 sub group_filesystems
360 {
361 return &parse_quota_output("$config{'group_quota_command'} ".quotemeta($_[0]));
362 }
363
364 =head2 parse_quota_output(command)
365
366 Internal function to parse the output of the quota command.
367
368 =cut
369 sub parse_quota_output
370 {
371 local($n, $_, %mtab);
372 %mtab = &get_mtab_map();
373 open(QUOTA, "$_[0] 2>/dev/null |");
374 $n=0; while(<QUOTA>) {
375         chop;
376         if (/^(Disk|\s+Filesystem)/) { next; }
377         if (/^(\S+)$/) {
378                 # Bogus wrapped line
379                 my $dev = $1;
380                 $filesys{$n,'filesys'} = $mtab{&resolve_and_simplify($dev)};
381                 local $nl = <QUOTA>;
382                 $nl =~/^\s+(\S+)\s+(\S+)\s+(\S+)(.{8}\s+)(\S+)\s+(\S+)\s+(\S+)(.*)/ ||
383                       $nl =~ /^.{15}.(.{7}).(.{7}).(.{7})(.{8}.)(.{7}).(.{7}).(.{7})(.*)/;
384                 $filesys{$n,'ublocks'} = int($1);
385                 $filesys{$n,'sblocks'} = int($2);
386                 $filesys{$n,'hblocks'} = int($3);
387                 $filesys{$n,'gblocks'} = $4;
388                 $filesys{$n,'ufiles'} = int($5);
389                 $filesys{$n,'sfiles'} = int($6);
390                 $filesys{$n,'hfiles'} = int($7);
391                 $filesys{$n,'gfiles'} = $8;
392                 $filesys{$n,'gblocks'} = &trunc_space($filesys{$n,'gblocks'});
393                 $filesys{$n,'gfiles'} = &trunc_space($filesys{$n,'gfiles'});
394                 $n++;
395                 }
396         elsif (/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)(.{8}\s+)(\S+)\s+(\S+)\s+(\S+)(.*)/ ||
397                /^(.{15}).(.{7}).(.{7}).(.{7})(.{8}.)(.{7}).(.{7}).(.{7})(.*)/) {
398                 # Single quota line
399                 $filesys{$n,'ublocks'} = int($2);
400                 $filesys{$n,'sblocks'} = int($3);
401                 $filesys{$n,'hblocks'} = int($4);
402                 $filesys{$n,'gblocks'} = $5;
403                 $filesys{$n,'ufiles'} = int($6);
404                 $filesys{$n,'sfiles'} = int($7);
405                 $filesys{$n,'hfiles'} = int($8);
406                 $filesys{$n,'gfiles'} = $9;
407                 my $dev = $1; $dev =~ s/\s+$//g; $dev =~ s/^\s+//g;
408                 $filesys{$n,'filesys'} = $mtab{&resolve_and_simplify($dev)};
409                 $filesys{$n,'gblocks'} = &trunc_space($filesys{$n,'gblocks'});
410                 $filesys{$n,'gfiles'} = &trunc_space($filesys{$n,'gfiles'});
411                 $n++;
412                 }
413         }
414 close(QUOTA);
415 return $n;
416 }
417
418 =head2 filesystem_users(filesystem)
419
420 Fills the array %user with information about all users with quotas
421 on this filesystem, and returns the number of users. Some example code shows
422 how this can be used :
423
424  foreign_require('quota', 'quota-lib.pl');
425  $n = quota::filesystem_users('/home');
426  for($i=0; $i<$n; $i++) {
427    print "user=",$user{$i,'user'}," ",
428          "block quota=",$user{$i,'hblocks'}," ",
429          "blocks used=",$user{$i,'ublocks'},"\n";
430  }
431
432 =cut
433 sub filesystem_users
434 {
435 return &parse_repquota_output(
436         $config{'user_repquota_command'}, \%user, "user", $_[0]);
437 }
438
439 =head2 filesystem_groups(filesystem)
440
441 Fills the array %group with information about all groups  with quotas on some
442 filesystem, and returns the group count. The format of %group is the same as
443 documented in the filesystem_users function.
444
445 =cut
446 sub filesystem_groups
447 {
448 return &parse_repquota_output(
449         $config{'group_repquota_command'}, \%group, "group", $_[0]);
450 }
451
452 =head2 parse_repquota_output(command, hashname, dir)
453
454 Internal function to parse the output of the repquota command.
455
456 =cut
457 sub parse_repquota_output
458 {
459 local ($cmd, $what, $mode, $dir) = @_;
460 local($rep, @rep, $n, $u, @uinfo);
461 %$what = ( );
462 $rep = &backquote_command("$cmd $dir 2>&1");
463 if ($?) { return -1; }
464 local $st = &supports_status($dir, $mode);
465 if (!$st) {
466         # Older system, need to build username map to identify truncation
467         if ($mode eq 'user') {
468                 setpwent();
469                 while(@uinfo = getpwent()) {
470                         $hasu{$uinfo[0]}++;
471                         }
472                 endpwent();
473                 }
474         else {
475                 setgrent();
476                 while(@uinfo = getgrent()) {
477                         $hasu{$uinfo[0]}++;
478                         }
479                 endgrent();
480                 }
481         }
482 @rep = split(/\n/, $rep); @rep = @rep[3..$#rep];
483 local $nn = 0;
484 local %already;
485 for($n=0; $n<@rep; $n++) {
486         if ($rep[$n] =~ /^\s*(\S.*\S|\S)\s+[\-\+]{2}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)(.*)/ ||
487             $rep[$n] =~ /^\s*(\S.*\S|\S)\s+[\-\+]{2}\s+(\S+)\s+(\S+)\s+(\S+)(.{7})\s+(\S+)\s+(\S+)\s+(\S+)(.*)/ ||
488             $rep[$n] =~ /([^\-\s]\S*)\s*[\-\+]{2}(.{8})(.{8})(.{8})(.{7})(.{8})(.{6})(.{6})(.*)/) {
489                 $what->{$nn,$mode} = $1;
490                 $what->{$nn,'ublocks'} = int($2);
491                 $what->{$nn,'sblocks'} = int($3);
492                 $what->{$nn,'hblocks'} = int($4);
493                 $what->{$nn,'gblocks'} = $5;
494                 $what->{$nn,'ufiles'} = int($6);
495                 $what->{$nn,'sfiles'} = int($7);
496                 $what->{$nn,'hfiles'} = int($8);
497                 $what->{$nn,'gfiles'} = $9;
498                 if (!$st && $what->{$nn,$mode} !~ /^\d+$/ &&
499                             !$hasu{$what->{$nn,$mode}}) {
500                         # User/group name was truncated! Try to find him..
501                         foreach $u (keys %hasu) {
502                                 if (substr($u, 0, length($what->{$nn,$mode})) eq
503                                     $what->{$nn,$what}) {
504                                         # found him..
505                                         $what->{$nn,$mode} = $u;
506                                         last;
507                                         }
508                                 }
509                         }
510                 next if ($already{$what->{$nn,$mode}}++); # skip dupe users
511                 $what->{$nn,'gblocks'} = &trunc_space($what->{$nn,'gblocks'});
512                 $what->{$nn,'gfiles'} = &trunc_space($what->{$nn,'gfiles'});
513                 $nn++;
514                 }
515         }
516 return $nn;
517 }
518
519 =head2 edit_quota_file(data, filesys, sblocks, hblocks, sfiles, hfiles)
520
521 Internal function that is called indirectly by the 'edquota' command to
522 modify a user's quotas on one filesystem, by editing a file.
523
524 =cut
525 sub edit_quota_file
526 {
527 local($rv, $line, %mtab, @m, @line);
528 %mtab = &get_mtab_map();
529 @line = split(/\n/, $_[0]);
530 for(my $i=0; $i<@line; $i++) {
531         if ($line[$i] =~ /^(\S+): blocks in use: (\d+), limits \(soft = (\d+), hard = (\d+)\)$/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
532                 # Found old-style lines to change
533                 $rv .= "$1: blocks in use: $2, limits (soft = $_[2], hard = $_[3])\n";
534                 $line[++$i] =~ /^\s*inodes in use: (\d+), limits \(soft = (\d+), hard = (\d+)\)$/;
535                 $rv .= "\tinodes in use: $1, limits (soft = $_[4], hard = $_[5])\n";
536                 }
537         elsif ($line[$i] =~ /^device\s+(\S+)\s+\((\S+)\):/i && $2 eq $_[1]) {
538                 # Even newer-style line to change
539                 $rv .= "$line[$i]\n";
540                 $line[++$i] =~ /^used\s+(\S+),\s+limits:\s+soft=(\d+)\s+hard=(\d+)/i;
541                 $rv .= "Used $1, limits: soft=$_[2] hard=$_[3]\n";
542                 $line[++$i] =~ /^used\s+(\S+) inodes,\s+limits:\s+soft=(\d+)\s+hard=(\d+)/i;
543                 $rv .= "Used $1 inodes, limits: soft=$_[4] hard=$_[5]\n";
544                 }
545         elsif ($line[$i] =~ /^\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
546                 # New-style line to change
547                 $rv .= "  $1 $2 $_[2] $_[3] $5 $_[4] $_[5]\n";
548                 }
549         else {
550                 # Leave this line alone
551                 $rv .= "$line[$i]\n";
552                 }
553         }
554 return $rv;
555 }
556
557 =head2 quotacheck(filesystem, mode)
558
559 Runs quotacheck on some filesystem, and returns the output in case of error,
560 or undef on failure. The mode must be one of :
561
562 =item 0 - Users and groups.
563
564 =item 1 - Users only.
565
566 =item 2 - Groups only.
567
568 =cut
569 sub quotacheck
570 {
571 local $out;
572 if ($_[1] == 0 || $_[1] == 1) {
573         &unlink_file("$_[0]/aquota.user.new");
574         }
575 if ($_[1] == 0 || $_[1] == 2) {
576         &unlink_file("$_[0]/aquota.group.new");
577         }
578 local $cmd = $config{'quotacheck_command'};
579 $cmd =~ s/\s+-[ug]//g;
580 local $flag = $_[1] == 1 ? "-u" : $_[1] == 2 ? "-g" : "-u -g";
581 $out = &backquote_logged("$cmd $flag $_[0] 2>&1");
582 if ($?) {
583         # Try with the -f and -m options
584         $out = &backquote_logged("$cmd $flag -f -m $_[0] 2>&1");
585         if ($?) {
586                 # Try with the -F option
587                 $out = &backquote_logged("$config{'quotacheck_command'} $flag -F $_[0] 2>&1");
588                 }
589         return $out if ($?);
590         }
591 return undef;
592 }
593
594 =head2 copy_user_quota(user, [user]+)
595
596 Copy the quotas for some user (the first parameter) to many others (named by
597 the remaining parameters). Returns undef on success, or an error message on
598 failure.
599
600 =cut
601 sub copy_user_quota
602 {
603 for($i=1; $i<@_; $i++) {
604         $out = &backquote_logged("$config{'user_copy_command'} ".
605                                 quotemeta($_[0])." ".quotemeta($_[$i])." 2>&1");
606         if ($?) { return $out; }
607         }
608 return undef;
609 }
610
611 =head2 copy_group_quota(group, [group]+)
612
613 Copy the quotas for some group (the first parameter) to many others (named by
614 the remaining parameters). Returns undef on success, or an error message on
615 failure.
616
617 =cut
618 sub copy_group_quota
619 {
620 for($i=1; $i<@_; $i++) {
621         $out = &backquote_logged("$config{'group_copy_command'} ".
622                                 quotemeta($_[0])." ".quotemeta($_[$i])." 2>&1");
623         if ($?) { return $out; }
624         }
625 return undef;
626 }
627
628 =head2 get_user_grace(filesystem)
629
630 Returns an array containing information about grace times on some filesystem,
631 which is the amount of time a user can exceed his soft quota before it becomes
632 hard. The elements of the array are :
633
634 =item Grace time for block quota, in units below.
635
636 =item Units for block quota grace time, where 0=sec, 1=min, 2=hour, 3=day.
637
638 =item Grace time for files quota, in units below.
639
640 =item Units for files quota grace time, where 0=sec, 1=min, 2=hour, 3=day.
641
642 =cut
643 sub get_user_grace
644 {
645 return &parse_grace_output($config{'user_grace_command'}, $_[0]);
646 }
647
648 =head2 get_group_grace(filesystem)
649
650 Returns an array containing information about grace times on some filesystem,
651 which is the amount of time a group can exceed its soft quota before it becomes
652 hard. The elements of the array are :
653
654 =item Grace time for block quota, in units below.
655
656 =item Units for block quota grace time, where 0=sec, 1=min, 2=hour, 3=day.
657
658 =item Grace time for files quota, in units below.
659
660 =item Units for files quota grace time, where 0=sec, 1=min, 2=hour, 3=day.
661
662 =cut
663 sub get_group_grace
664 {
665 return &parse_grace_output($config{'group_grace_command'}, $_[0]);
666 }
667
668 =head2 default_grace
669
670 Returns 0 if grace time can be 0, 1 if zero grace means default.
671
672 =cut
673 sub default_grace
674 {
675 return 0;
676 }
677
678 =head2 parse_grace_output(command)
679
680 Internal function to parse output from the quota -t command.
681
682 =cut
683 sub parse_grace_output
684 {
685 local(@rv, %mtab, @m);
686 %mtab = &get_mtab_map();
687 $ENV{'EDITOR'} = $ENV{'VISUAL'} = "cat";
688 open(GRACE, "$_[0] 2>&1 |");
689 while(<GRACE>) {
690         if (/^(\S+): block grace period: (\d+) (\S+), file grace period: (\d+) (\S+)/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
691                 @rv = ($2, $name_to_unit{$3}, $4, $name_to_unit{$5});
692                 }
693         elsif (/^\s+(\S+)\s+(\d+)(\S+)\s+(\d+)(\S+)/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
694                 @rv = ($2, $name_to_unit{$3}, $4, $name_to_unit{$5});
695                 }
696         elsif (/^device\s+(\S+)\s+\((\S+)\):/i && $2 eq $_[1]) {
697                 if (<GRACE> =~ /^block\s+grace:\s+(\S+)\s+(\S+)\s+inode\s+grace:\s+(\S+)\s+(\S+)/i) {
698                         @rv = ($1, $name_to_unit{$2}, $3, $name_to_unit{$4});
699                         last;
700                         }
701                 }
702         }
703 close(GRACE);
704 return @rv;
705 }
706
707 =head2 edit_grace_file(data, filesystem, btime, bunits, ftime, funits)
708
709 Internal function called by edquota -t to set grace times on some filesystem,
710 by editing a file.
711
712 =cut
713 sub edit_grace_file
714 {
715 local($rv, $line, @m, %mtab, @line);
716 %mtab = &get_mtab_map();
717 @line = split(/\n/, $_[0]);
718 for(my $i=0; $i<@line; $i++) {
719         $line = $line[$i];
720         if ($line =~ /^(\S+): block grace period: (\d+) (\S+), file grace period: (\d+) (\S+)/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
721                 # replace this line
722                 $line = "$1: block grace period: $_[2] $unit_to_name{$_[3]}, file grace period: $_[4] $unit_to_name{$_[5]}";
723                 }
724         elsif ($line =~ /^\s+(\S+)\s+(\d+)(\S+)\s+(\d+)(\S+)/ && $mtab{&resolve_and_simplify("$1")} eq $_[1]) {
725                 # replace new-style line
726                 $line = "  $1 $_[2]$unit_to_name{$_[3]} $_[4]$unit_to_name{$_[5]}";
727                 }
728         elsif ($line =~ /^device\s+(\S+)\s+\((\S+)\):/i && $2 eq $_[1]) {
729                 # replace even newer-style line
730                 $rv .= "$line\n";
731                 $line = "Block grace: $_[2] $unit_to_name{$_[3]} Inode grace: $_[4] $unit_to_name{$_[5]}";
732                 $i++;
733                 }
734         $rv .= "$line\n";
735         }
736 return $rv;
737 }
738
739 =head2 grace_units
740
741 Returns an array of possible units for grace periods, in human-readable format.
742
743 =cut
744 sub grace_units
745 {
746 return ($text{'grace_seconds'}, $text{'grace_minutes'}, $text{'grace_hours'},
747         $text{'grace_days'});
748 }
749
750 =head2 fs_block_size(dir, device, filesystem)
751
752 Returns the size of quota blocks on some filesystem, or undef if unknown.
753 Consult the dumpe2fs command where possible.
754
755 =cut
756 sub fs_block_size
757 {
758 if ($_[2] =~ /^ext\d+$/) {
759         return 1024;
760         # This code isn't needed, because the quota block size is
761         # not the same as the filesystem block size!!
762         #if (&has_command("dumpe2fs")) {
763         #       local $out = `dumpe2fs -h $_[1] 2>&1`;
764         #       if (!$? && $out =~ /block size:\s+(\d+)/i) {
765         #               return $1;
766         #               }
767         #       }
768         }
769 elsif ($_[0] eq "xfs") {
770         return 1024;
771         }
772 return undef;
773 }
774
775 %name_to_unit = ( "second", 0, "seconds", 0,
776                   "minute", 1, "minutes", 1,
777                   "hour", 2, "hours", 2,
778                   "day", 3, "days", 3,
779                 );
780 foreach $k (keys %name_to_unit) {
781         $unit_to_name{$name_to_unit{$k}} = $k;
782         }
783
784 =head2 get_mtab_map
785
786 Returns a hash mapping mount points to devices. For internal use.
787
788 =cut
789 sub get_mtab_map
790 {
791 local $mm = $module_info{'usermin'} ? "usermount" : "mount";
792 &foreign_require($mm, "$mm-lib.pl");
793 local ($m, %mtab);
794 foreach $m (&foreign_call($mm, "list_mounted", 1)) {
795         if ($m->[3] =~ /loop=([^,]+)/) {
796                 $mtab{&resolve_and_simplify("$1")} ||= $m->[0];
797                 }
798         else {
799                 $mtab{&resolve_and_simplify($m->[1])} ||= $m->[0];
800                 }
801         }
802 return %mtab;
803 }
804
805 1;
806