Handle hostnames with upper-case letters
[webmin.git] / quota / quota-lib.pl
1 =head1 quota-lib.pl
2
3 Functions for Unix user and group quota management. Some of the functionality
4 is implemented in OS-specific library files which get automatically included
5 into this one, like linux-lib.pl. Check the documentation on that file for
6 more functions.
7
8 Example code:
9
10  foreign_require('quota', 'quota-lib.pl');
11  quota::edit_user_quota('joe', '/home', 1000000, 1200000, 1000, 1200);
12  $n = quota::user_filesystems('joe');
13  for($i=0; $i<$n; $i++) {
14    print "filesystem=",$filesys{$i,'filesys'}," ",
15          "block quota=",$filesys{$i,'hblocks'}," ",
16          "blocks used=",$filesys{$i,'ublocks'},"\n";
17  }
18
19 =cut
20
21 BEGIN { push(@INC, ".."); };
22 use WebminCore;
23 &init_config();
24 if ($gconfig{'os_type'} =~ /^\S+\-linux$/) {
25         do "linux-lib.pl";
26         }
27 else {
28         do "$gconfig{'os_type'}-lib.pl";
29         }
30 if ($module_info{'usermin'}) {
31         &switch_to_remote_user();
32         }
33 else {
34         %access = &get_module_acl();
35         &foreign_require("mount", "mount-lib.pl");
36         }
37
38 $email_cmd = "$module_config_directory/email.pl";
39
40 =head2 list_filesystems
41
42 Returns a list of details of local filesystems on which quotas are supported.
43 Each is an array ref whose values are :
44
45 =item directory - Mount point, like /home
46
47 =item device - Source device, like /dev/hda1
48
49 =item type - Filesystem type, like ext3
50
51 =item options - Mount options, like rw,usrquota,grpquota
52
53 =item quotacan - Can this filesystem type support quotas?
54
55 =item quotanow - Are quotas enabled right now?
56
57 The values of quotacan and quotanow are :
58
59 =item 0 - No quotas
60
61 =item 1 - User quotas only
62
63 =item 2 - Group quotas only
64
65 =item 3 - User and group quotas
66
67 =cut
68 sub list_filesystems
69 {
70 local $f;
71 local @mtab = &mount::list_mounted();
72 foreach $f (&mount::list_mounts()) {
73         $fmap{$f->[0],$f->[1]} = $f;
74         }
75 map { $_->[4] = &quota_can($_, $fmap{$_->[0],$_->[1]}) } @mtab;
76 map { $_->[5] = &quota_now($_, $fmap{$_->[0],$_->[1]}) } @mtab;
77 return grep { $_->[4] } @mtab;
78 }
79
80 =head2 parse_options(type, options)
81
82 Convert an options string for some filesystem into the global hash %options.
83
84 =cut
85 sub parse_options
86 {
87 local($_);
88 undef(%options);
89 if ($_[0] ne "-") {
90         foreach (split(/,/, $_[0])) {
91                 if (/^([^=]+)=(.*)$/) { $options{$1} = $2; }
92                 else { $options{$_} = ""; }
93                 }
94         }
95 }
96
97 =head2 user_quota(user, filesystem)
98
99 Returns an array of quotas and usage information for some user on some
100 filesystem, or an empty array if no quota has been assigned. The array
101 elements are :
102
103 =item Number of blocks used.
104
105 =item Soft block quota.
106
107 =item Hard block quota.
108
109 =item Number of files used.
110
111 =item Soft file quota.
112
113 =item Hard file quota.
114
115 =cut
116 sub user_quota
117 {
118 local (%user, $n, $i);
119 $n = &filesystem_users($_[1]);
120 for($i=0; $i<$n; $i++) {
121         if ($user{$i,'user'} eq $_[0]) {
122                 return ( $user{$i,'ublocks'}, $user{$i,'sblocks'},
123                          $user{$i,'hblocks'}, $user{$i,'ufiles'},
124                          $user{$i,'sfiles'},  $user{$i,'hfiles'} );
125                 }
126         }
127 return ();
128 }
129
130 =head2 group_quota(group, filesystem)
131
132 Returns an array of  ublocks, sblocks, hblocks, ufiles, sfiles, hfiles
133 for some group on some filesystem, or an empty array if no quota has been
134 assigned.
135
136 =cut
137 sub group_quota
138 {
139 local (%group, $n, $i);
140 $n = &filesystem_groups($_[1]);
141 for($i=0; $i<$n; $i++) {
142         if ($group{$i,'group'} eq $_[0]) {
143                 return ( $group{$i,'ublocks'}, $group{$i,'sblocks'},
144                          $group{$i,'hblocks'}, $group{$i,'ufiles'},
145                          $group{$i,'sfiles'},  $group{$i,'hfiles'} );
146                 }
147         }
148 return ();
149 }
150
151 =head2 edit_user_quota(user, filesys, sblocks, hblocks, sfiles, hfiles)
152
153 Sets the disk quota for some user. The parameters are :
154
155 =item user - Unix username.
156
157 =item filesys - Filesystem on which to change quotas.
158
159 =item sblocks - Soft block limit.
160
161 =item hblocks - Hard block limit.
162
163 =item sfiles - Sort files limit.
164
165 =item hfiles - Hard files limit.
166
167 =cut
168 sub edit_user_quota
169 {
170 if ($config{'user_setquota_command'} &&
171     &has_command((split(/\s+/, $config{'user_setquota_command'}))[0])) {
172         # Use quota setting command
173         local $user = $_[0];
174         if ($user =~ /^#(\d+)$/) {
175                 # Pass numeric UID
176                 $user = $1;
177                 }
178         elsif ($user =~ /^\d+$/) {
179                 # Username is numeric .. convert to UID
180                 local $uid = getpwnam($user);
181                 $user = $uid if (defined($uid));
182                 }
183         local $cmd = $config{'user_setquota_command'}." ".quotemeta($user)." ".
184                      int($_[2])." ".int($_[3])." ".int($_[4])." ".int($_[5]).
185                      " ".quotemeta($_[1]);
186         local $out = &backquote_logged("$cmd 2>&1 </dev/null");
187         &error("<tt>".&html_escape($out)."</tt>") if ($?);
188         }
189 else {
190         # Call the quota editor
191         $ENV{'EDITOR'} = $ENV{'VISUAL'} = "$module_root_directory/edquota.pl";
192         $ENV{'QUOTA_USER'} = $_[0];
193         $ENV{'QUOTA_FILESYS'} = $_[1];
194         $ENV{'QUOTA_SBLOCKS'} = $_[2];
195         $ENV{'QUOTA_HBLOCKS'} = $_[3];
196         $ENV{'QUOTA_SFILES'} = $_[4];
197         $ENV{'QUOTA_HFILES'} = $_[5];
198         local $user = $_[0];
199         if ($edquota_use_ids) {
200                 # Use UID instead of username
201                 if ($user =~ /^#(\d+)$/) {
202                         $user = $1;
203                         }
204                 else {
205                         local $uid = getpwnam($user);
206                         $user = $uid if (defined($uid));
207                         }
208                 }
209         &system_logged("$config{'user_edquota_command'} ".
210                        quotemeta($user)." >/dev/null 2>&1");
211         }
212 }
213
214 =head2 edit_group_quota(group, filesys, sblocks, hblocks, sfiles, hfiles)
215
216 Sets the disk quota for some group The parameters are :
217
218 =item user - Unix group name.
219
220 =item filesys - Filesystem on which to change quotas.
221
222 =item sblocks - Soft block limit.
223
224 =item hblocks - Hard block limit.
225
226 =item sfiles - Sort files limit.
227
228 =item hfiles - Hard files limit.
229
230 =cut
231 sub edit_group_quota
232 {
233 if ($config{'group_setquota_command'} &&
234     &has_command((split(/\s+/, $config{'group_setquota_command'}))[0])) {
235         # Use quota setting command
236         local $group = $_[0];
237         if ($group =~ /^#(\d+)$/) {
238                 # Pass numeric UID
239                 $group = $1;
240                 }
241         elsif ($group =~ /^\d+$/) {
242                 # Group name is numeric .. convert to GID
243                 local $gid = getgrnam($group);
244                 $group = $gid if (defined($gid));
245                 }
246         local $cmd =$config{'group_setquota_command'}." ".quotemeta($group)." ".
247                      int($_[2])." ".int($_[3])." ".int($_[4])." ".int($_[5]).
248                      " ".quotemeta($_[1]);
249         local $out = &backquote_logged("$cmd 2>&1 </dev/null");
250         &error("<tt>".&html_escape($out)."</tt>") if ($?);
251         }
252 else {
253         # Call the editor
254         $ENV{'EDITOR'} = $ENV{'VISUAL'} = "$module_root_directory/edquota.pl";
255         $ENV{'QUOTA_USER'} = $_[0];
256         $ENV{'QUOTA_FILESYS'} = $_[1];
257         $ENV{'QUOTA_SBLOCKS'} = $_[2];
258         $ENV{'QUOTA_HBLOCKS'} = $_[3];
259         $ENV{'QUOTA_SFILES'} = $_[4];
260         $ENV{'QUOTA_HFILES'} = $_[5];
261         local $group = $_[0];
262         if ($edquota_use_ids) {
263                 # Use GID instead of group name
264                 if ($group =~ /^#(\d+)$/) {
265                         $group = $1;
266                         }
267                 else {
268                         local $gid = getgrnam($group);
269                         $group = $gid if (defined($gid));
270                         }
271                 }
272         &system_logged("$config{'group_edquota_command'} ".
273                        quotemeta($group)." >/dev/null 2>&1");
274         }
275 }
276
277 =head2 edit_user_grace(filesystem, btime, bunits, ftime, funits)
278
279 Change the grace times for blocks and files on some filesystem. Parameters are:
280
281 =item filesystem - Filesystem to change the grace time on.
282
283 =item btime - Number of units after which a user over his soft block limit is turned into a hard limit.
284
285 =item bunits - Units for the block grace time, such as 'seconds', 'minutes', 'hours' or 'days'.
286
287 =item ftime - Number of units after which a user over his soft file limit is turned into a hard limit.
288
289 =item funits - Units for the file grace time, such as 'seconds', 'minutes', 'hours' or 'days'.
290
291 =cut
292 sub edit_user_grace
293 {
294 $ENV{'EDITOR'} = $ENV{'VISUAL'} = "$module_root_directory/edgrace.pl";
295 $ENV{'QUOTA_FILESYS'} = $_[0];
296 $ENV{'QUOTA_BTIME'} = $_[1];
297 $ENV{'QUOTA_BUNITS'} = $_[2];
298 $ENV{'QUOTA_FTIME'} = $_[3];
299 $ENV{'QUOTA_FUNITS'} = $_[4];
300 &system_logged($config{'user_grace_command'});
301 }
302
303 =head2 edit_group_grace(filesystem, btime, bunits, ftime, funits)
304
305 Change the grace times for groups for blocks and files on some filesystem.
306 The parameters are the same as edit_user_grace.
307
308 =cut
309 sub edit_group_grace
310 {
311 $ENV{'EDITOR'} = $ENV{'VISUAL'} = "$module_root_directory/edgrace.pl";
312 $ENV{'QUOTA_FILESYS'} = $_[0];
313 $ENV{'QUOTA_BTIME'} = $_[1];
314 $ENV{'QUOTA_BUNITS'} = $_[2];
315 $ENV{'QUOTA_FTIME'} = $_[3];
316 $ENV{'QUOTA_FUNITS'} = $_[4];
317 &system_logged($config{'group_grace_command'});
318 }
319
320 =head2 quota_input(name, value, [blocksize])
321
322 Returns an input for selecting a quota or unlimited, in a table. For internal
323 use mainly.
324
325 =cut
326 sub quota_input
327 {
328 return &ui_radio($_[0]."_def", $_[1] == 0 ? 1 : 0,
329                  [ [ 1, $text{'quota_unlimited'} ], [ 0, " " ] ])." ".
330        &quota_inputbox(@_);
331 }
332
333 =head2 quota_inputbox(name, value, [blocksize])
334
335 Returns an input for selecting a quota. Mainly for internal use.
336
337 =cut
338 sub quota_inputbox
339 {
340 if ($_[2]) {
341         # We know the real size, so can offer units
342         local $sz = $_[1]*$_[2];
343         local $units = 1;
344         if ($sz >= 10*1024*1024*1024) {
345                 $units = 1024*1024*1024;
346                 }
347         elsif ($sz >= 10*1024*1024) {
348                 $units = 1024*1024;
349                 }
350         elsif ($sz >= 10*1024) {
351                 $units = 1024;
352                 }
353         else {
354                 $units = 1;
355                 }
356         $sz = $sz == 0 ? "" : sprintf("%.2f", ($sz*1.0)/$units);
357         return &ui_textbox($_[0], $sz, 8).
358                &ui_select($_[0]."_units", $units,
359                          [ [ 1, "bytes" ], [ 1024, "kB" ], [ 1024*1024, "MB" ],
360                            [ 1024*1024*1024, "GB" ] ]);
361         }
362 else {
363         # Just show blocks
364         return &ui_textbox($_[0], $_[1] == 0 ? "" : $_[1], 8);
365         }
366 }
367
368 =head2 quota_parse(name, [bsize], [nodef])
369
370 Parses inputs from the form generated by quota_input.
371
372 =cut
373 sub quota_parse
374 {
375 if ($in{$_[0]."_def"} && !$_[2]) {
376         return 0;
377         }
378 elsif ($_[1]) {
379         # Include units, and covert to blocks
380         return int($in{$_[0]}*$in{$_[0]."_units"}/$_[1]);
381         }
382 else {
383         # Just use blocks
384         return int($in{$_[0]});
385         }
386 }
387
388 =head2 can_edit_filesys(filesys)
389
390 Returns 1 if the current Webmin user can manage quotas on some filesystem.
391
392 =cut
393 sub can_edit_filesys
394 {
395 local $fs;
396 foreach $fs (split(/\s+/, $access{'filesys'})) {
397         return 1 if ($fs eq "*" || $fs eq $_[0]);
398         }
399 return 0;
400 }
401
402 =head2 can_edit_user(user)
403
404 Returns 1 if the current Webmin user can manage quotas for some Unix user.
405
406 =cut
407 sub can_edit_user
408 {
409 if ($access{'umode'} == 0) {
410         return 1;
411         }
412 elsif ($access{'umode'} == 3) {
413         local @u = getpwnam($_[0]);
414         return $access{'users'} == $u[3];
415         }
416 elsif ($access{'umode'} == 4) {
417         local @u = getpwnam($_[0]);
418         return (!$access{'umin'} || $u[2] >= $access{'umin'}) &&
419                (!$access{'umax'} || $u[2] <= $access{'umax'});
420         }
421 else {
422         local %ucan = map { $_, 1 } split(/\s+/, $access{'users'});
423         return $access{'umode'} == 1 && $ucan{$_[0]} ||
424                $access{'umode'} == 2 && !$ucan{$_[0]};
425         }
426 }
427
428 =head2 can_edit_group(group)
429
430 Returns 1 if the current Webmin user can manage quotas for some Unix group.
431
432 =cut
433 sub can_edit_group
434 {
435 if ($access{'gmode'} == 0) {
436         return 1;
437         }
438 elsif ($access{'gmode'} == 3) {
439         return 0;
440         }
441 elsif ($access{'gmode'} == 4) {
442         local @g = getgrnam($_[0]);
443         return (!$access{'gmin'} || $g[2] >= $access{'gmin'}) &&
444                (!$access{'gmax'} || $g[2] <= $access{'gmax'});
445         }
446 else {
447         local %gcan = map { $_, 1 } split(/\s+/, $access{'groups'});
448         return $access{'gmode'} == 1 && $gcan{$_[0]} ||
449                $access{'gmode'} == 2 && !$gcan{$_[0]};
450         }
451 }
452
453 =head2 filesystem_info(filesystem, &hash, count, [blocksize])
454
455 Returns two strings containing information about the amount of disk space
456 granted and used on some filesystem. For internal use.
457
458 =cut
459 sub filesystem_info
460 {
461 local @fs = &free_space($_[0], $_[3]);
462 if ($_[3]) {
463         local $i;
464         foreach $i (0 .. 3) {
465                 $fs[$i] = $i < 2 ? &nice_size($fs[$i]*$_[3])
466                                  : int($fs[$i]);
467                 }
468         }
469 if ($_[1]) {
470         local $bt = 0;
471         local $ft = 0;
472         local $i;
473         for($i=0; $i<$_[2]; $i++) {
474                 $bt += $_[1]->{$i,'hblocks'};
475                 $ft += $_[1]->{$i,'hfiles'};
476                 }
477         if ($_[3]) {
478                 $bt = &nice_size($bt*$_[3]);
479                 }
480         return ( "$fs[0] total / $fs[1] free / $bt granted",
481                  "$fs[2] total / $fs[3] free / $ft granted" );
482         }
483 else {
484         return ( "$fs[0] total / $fs[1] free",
485                  "$fs[2] total / $fs[3] free" );
486         }
487 }
488
489 =head2 block_size(dir, [for-filesys])
490
491 Returns the size (in bytes) of blocks on some filesystem, if known. All
492 quota functions deal with blocks, so they must be multipled by the value
493 returned by this function before display to users.
494
495 =cut
496 sub block_size
497 {
498 return undef if (!$config{'block_mode'});
499 return undef if (!defined(&quota_block_size) &&
500                  !defined(&fs_block_size));
501 local @mounts = &mount::list_mounted();
502 local ($mount) = grep { $_->[0] eq $_[0] } @mounts;
503 if ($mount) {
504         if ($_[1]) {
505                 return &fs_block_size(@$mount);
506                 }
507         else {
508                 if (defined(&quota_block_size)) {
509                         return &quota_block_size(@$mount);
510                         }
511                 else {
512                         return &fs_block_size(@$mount);
513                         }
514                 }
515         }
516 return undef;
517 }
518
519 =head2 nice_limit(amount, bsize, no-blocks)
520
521 Internal function to show a quota limit nicely formatted.
522
523 =cut
524 sub nice_limit
525 {
526 local ($amount, $bsize, $noblocks) = @_;
527 return $amount == 0 ? $text{'quota_unlimited'} :
528        $bsize && !$noblocks ? &nice_size($amount*$bsize) : $amount;
529 }
530
531 =head2 find_email_job
532
533 Returns the cron job hash ref for the quota limit monitoring email job.
534
535 =cut
536 sub find_email_job
537 {
538 &foreign_require("cron", "cron-lib.pl");
539 local @jobs = &cron::list_cron_jobs();
540 local ($job) = grep { $_->{'command'} eq $email_cmd } @jobs;
541 return $job;
542 }
543
544 =head2 create_email_job
545
546 Creates the cron job for scheduled emailing, which runs every 10 minutes.
547
548 =cut
549 sub create_email_job
550 {
551 &foreign_require("cron", "cron-lib.pl");
552 local $job = &find_email_job();
553 if (!$job) {
554         $job = { 'user' => 'root',
555                  'command' => $email_cmd,
556                  'active' => 1,
557                  'mins' => '0,10,20,30,40,50',
558                  'hours' => '*',
559                  'days' => '*',
560                  'months' => '*',
561                  'weekdays' => '*' };
562         &lock_file(&cron::cron_file($job));
563         &cron::create_cron_job($job);
564         &cron::create_wrapper($email_cmd, $module_name, "email.pl");
565         &unlock_file(&cron::cron_file($job));
566         }
567 }
568
569 =head2 trunc_space(string)
570
571 Removes spaces from the start and end of a string.
572
573 =cut
574 sub trunc_space
575 {
576 local $rv = $_[0];
577 $rv =~ s/^\s+//;
578 $rv =~ s/\s+$//;
579 return $rv;
580 }
581
582 =head2 to_percent(used, total)
583
584 Converts an amount used and a total into a percentage.
585
586 =cut
587 sub to_percent
588 {
589 if ($_[1]) {
590         return $_[0]*100/$_[1];
591         }
592 else {
593         return 0;
594         }
595 }
596
597 =head2 select_grace_units(name, value)
598
599 Returns a menu for selecting grace time units.
600
601 =cut
602 sub select_grace_units
603 {
604 local @uarr = &grace_units();
605 return &ui_select($_[0], $_[1],
606         [ map { [ $_, $uarr[$_] ] } (0..$#uarr) ]);
607 }
608
609 # resolve_and_simplify(path)
610 # Resolve symlinks from a path, and simplify the result to remove dots
611 sub resolve_and_simplify
612 {
613 my ($path) = @_;
614 return &simplify_path(&resolve_links($path));
615 }
616
617 1;
618