a1f35920f3c97cfbe361923c44335fe7a3d0fd1b
[dragonfly.git] / sbin / vquota / vquota.c
1 /*
2  * Copyright (c) 2011, 2012 Fran├žois Tigeot <ftigeot@wolfpond.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  * 3. Neither the name of The DragonFly Project nor the names of its
16  *    contributors may be used to endorse or promote products derived
17  *    from this software without specific, prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 #include <sys/stat.h>
34 #include <sys/mount.h>
35 #include <sys/vfs_quota.h>
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <err.h>
40 #include <string.h>
41 #include <fts.h>
42 #include <libprop/proplib.h>
43 #include <unistd.h>
44 #include <sys/tree.h>
45 #include <errno.h>
46 #include <inttypes.h>
47 #include <sys/types.h>
48 #include <libutil.h>
49 #include <pwd.h>
50 #include <grp.h>
51
52 static bool flag_debug = 0;
53 static bool flag_humanize = 0;
54 static bool flag_resolve_ids = 1;
55
56 static void usage(int);
57 static int get_dirsize(char *);
58 static int get_fslist(void);
59
60 static void
61 usage(int retcode)
62 {
63         fprintf(stderr, "usage: vquota [-Dhn] check directory\n");
64         fprintf(stderr, "       vquota [-Dhn] lsfs\n");
65         fprintf(stderr, "       vquota [-Dhn] limit mount_point size\n");
66         fprintf(stderr, "       vquota [-Dhn] ulim  mount_point user  size\n");
67         fprintf(stderr, "       vquota [-Dhn] show mount_point\n");
68         fprintf(stderr, "       vquota [-Dhn] sync mount_point\n");
69         exit(retcode);
70 }
71
72 /*
73  * Inode numbers with more than one hard link often come in groups;
74  * use linear arrays of 1024 ones as the basic unit of allocation.
75  * We only need to check if the inodes have been previously processed,
76  * bit arrays are perfect for that purpose.
77  */
78 #define HL_CHUNK_BITS           10
79 #define HL_CHUNK_ENTRIES        (1<<HL_CHUNK_BITS)
80 #define HL_CHUNK_MASK           (HL_CHUNK_ENTRIES - 1)
81 #define BA_UINT64_BITS          6
82 #define BA_UINT64_ENTRIES       (1<<BA_UINT64_BITS)
83 #define BA_UINT64_MASK          (BA_UINT64_ENTRIES - 1)
84
85 struct hl_node {
86         RB_ENTRY(hl_node)       rb_entry;
87         ino_t                   ino_left_bits;
88         uint64_t                hl_chunk[HL_CHUNK_ENTRIES/64];
89 };
90
91 RB_HEAD(hl_tree,hl_node)        hl_root;
92
93 RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
94
95 static int
96 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
97
98 RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
99
100 struct hl_node* hl_node_insert(ino_t);
101
102
103 static int
104 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b)
105 {
106         if (a->ino_left_bits < b->ino_left_bits)
107                 return(-1);
108         else if (a->ino_left_bits > b->ino_left_bits)
109                 return(1);
110         return(0);
111 }
112
113 struct hl_node* hl_node_insert(ino_t inode)
114 {
115         struct hl_node *hlp, *res;
116
117         hlp = malloc(sizeof(struct hl_node));
118         if (hlp == NULL) {
119                 /* shouldn't happen */
120                 printf("hl_node_insert(): malloc failed\n");
121                 exit(ENOMEM);
122         }
123         bzero(hlp, sizeof(struct hl_node));
124
125         hlp->ino_left_bits = (inode >> HL_CHUNK_BITS);
126         res = RB_INSERT(hl_tree, &hl_root, hlp);
127
128         if (res != NULL)        /* shouldn't happen */
129                 printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
130
131         return hlp;
132 }
133
134 /*
135  * hl_register: register an inode number in a rb-tree of bit arrays
136  * returns:
137  * - true if the inode was already processed
138  * - false otherwise
139  */
140 static bool
141 hl_register(ino_t inode)
142 {
143         struct hl_node hl_find, *hlp;
144         uint64_t ino_right_bits, ba_index, ba_offset;
145         uint64_t bitmask, bitval;
146         bool retval = false;
147
148         /* calculate the different addresses of the wanted bit */
149         hl_find.ino_left_bits = (inode >> HL_CHUNK_BITS);
150
151         ino_right_bits = inode & HL_CHUNK_MASK;
152         ba_index  = ino_right_bits >> BA_UINT64_BITS;
153         ba_offset = ino_right_bits & BA_UINT64_MASK;
154
155         /* no existing node? create and initialize it */
156         if ((hlp = RB_FIND(hl_tree, &hl_root, &hl_find)) == NULL) {
157                 hlp = hl_node_insert(inode);
158         }
159
160         /* node was found, check the bit value */
161         bitmask = 1 << ba_offset;
162         bitval = hlp->hl_chunk[ba_index] & bitmask;
163         if (bitval != 0) {
164                 retval = true;
165         }
166
167         /* set the bit */
168         hlp->hl_chunk[ba_index] |= bitmask;
169
170         return retval;
171 }
172
173 /* global variable used by get_dir_size() */
174 uint64_t global_size;
175
176 /* storage for collected id numbers */
177 /* FIXME: same data structures used in kernel, should find a way to
178  * deduplicate this code */
179
180 static int
181 rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
182 static int
183 rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
184
185 RB_HEAD(ac_utree,ac_unode) ac_uroot;
186 RB_HEAD(ac_gtree,ac_gnode) ac_groot;
187 RB_PROTOTYPE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
188 RB_PROTOTYPE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
189 RB_GENERATE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
190 RB_GENERATE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
191
192 static int
193 rb_ac_unode_cmp(struct ac_unode *a, struct ac_unode *b)
194 {
195         if (a->left_bits < b->left_bits)
196                 return(-1);
197         else if (a->left_bits > b->left_bits)
198                 return(1);
199         return(0);
200 }
201
202 static int
203 rb_ac_gnode_cmp(struct ac_gnode *a, struct ac_gnode *b)
204 {
205         if (a->left_bits < b->left_bits)
206                 return(-1);
207         else if (a->left_bits > b->left_bits)
208                 return(1);
209         return(0);
210 }
211
212 static struct ac_unode*
213 unode_insert(uid_t uid)
214 {
215         struct ac_unode *unp, *res;
216
217         unp = malloc(sizeof(struct ac_unode));
218         if (unp == NULL) {
219                 printf("unode_insert(): malloc failed\n");
220                 exit(ENOMEM);
221         }
222         bzero(unp, sizeof(struct ac_unode));
223
224         unp->left_bits = (uid >> ACCT_CHUNK_BITS);
225         res = RB_INSERT(ac_utree, &ac_uroot, unp);
226
227         if (res != NULL)        /* shouldn't happen */
228                 printf("unode_insert(): RB_INSERT didn't return NULL\n");
229
230         return unp;
231 }
232
233 static struct ac_gnode*
234 gnode_insert(gid_t gid)
235 {
236         struct ac_gnode *gnp, *res;
237
238         gnp = malloc(sizeof(struct ac_gnode));
239         if (gnp == NULL) {
240                 printf("gnode_insert(): malloc failed\n");
241                 exit(ENOMEM);
242         }
243         bzero(gnp, sizeof(struct ac_gnode));
244
245         gnp->left_bits = (gid >> ACCT_CHUNK_BITS);
246         res = RB_INSERT(ac_gtree, &ac_groot, gnp);
247
248         if (res != NULL)        /* shouldn't happen */
249                 printf("gnode_insert(): RB_INSERT didn't return NULL\n");
250
251         return gnp;
252 }
253
254 /*
255  * get_dirsize(): walks a directory tree in the same filesystem
256  * output:
257  * - global rb-trees ac_uroot and ac_groot
258  * - global variable global_size
259  */
260 static int
261 get_dirsize(char* dirname)
262 {
263         FTS             *fts;
264         FTSENT          *p;
265         char*           fts_args[2];
266         int             retval = 0;
267
268         /* what we need */
269         ino_t           file_inode;
270         off_t           file_size;
271         uid_t           file_uid;
272         gid_t           file_gid;
273
274         struct ac_unode *unp, ufind;
275         struct ac_gnode *gnp, gfind;
276
277         /* TODO: check directory name sanity */
278         fts_args[0] = dirname;
279         fts_args[1] = NULL;
280
281         if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
282                 err(1, "fts_open() failed");
283
284         while ((p = fts_read(fts)) != NULL) {
285                 switch (p->fts_info) {
286                 /* directories, ignore them */
287                 case FTS_D:
288                 case FTS_DC:
289                 case FTS_DP:
290                         break;
291                 /* read errors, warn, continue and flag */
292                 case FTS_DNR:
293                 case FTS_ERR:
294                 case FTS_NS:
295                         warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
296                         retval = 1;
297                         break;
298                 default:
299                         file_inode = p->fts_statp->st_ino;
300                         file_size = p->fts_statp->st_size;
301                         file_uid = p->fts_statp->st_uid;
302                         file_gid = p->fts_statp->st_gid;
303
304                         /* files with more than one hard link: */
305                         /* process them only once */
306                         if (p->fts_statp->st_nlink > 1)
307                                 if (hl_register(file_inode))
308                                         break;
309
310                         global_size += file_size;
311                         ufind.left_bits = (file_uid >> ACCT_CHUNK_BITS);
312                         gfind.left_bits = (file_gid >> ACCT_CHUNK_BITS);
313                         if ((unp = RB_FIND(ac_utree, &ac_uroot, &ufind)) == NULL)
314                                 unp = unode_insert(file_uid);
315                         if ((gnp = RB_FIND(ac_gtree, &ac_groot, &gfind)) == NULL)
316                                 gnp = gnode_insert(file_gid);
317                         unp->uid_chunk[(file_uid & ACCT_CHUNK_MASK)].space += file_size;
318                         gnp->gid_chunk[(file_gid & ACCT_CHUNK_MASK)].space += file_size;
319                 }
320         }
321         fts_close(fts);
322
323         return retval;
324 }
325
326 static void
327 print_user(uid_t uid)
328 {
329         struct passwd *pw;
330
331         if (flag_resolve_ids && ((pw = getpwuid(uid)) != NULL)) {
332                 printf("user %s:", pw->pw_name);
333         } else {
334                 printf("uid %u:", uid);
335         }
336 }
337
338 static void
339 print_group(gid_t gid)
340 {
341         struct group *gr;
342
343         if (flag_resolve_ids && ((gr = getgrgid(gid)) != NULL)) {
344                 printf("group %s:", gr->gr_name);
345         } else {
346                 printf("gid %u:", gid);
347         }
348 }
349
350 static int
351 cmd_check(char* dirname)
352 {
353         int32_t uid, gid;
354         char    hbuf[5];
355         struct  ac_unode *unp;
356         struct  ac_gnode *gnp;
357         int     rv, i;
358
359         rv = get_dirsize(dirname);
360
361         if (flag_humanize) {
362                 humanize_number(hbuf, sizeof(hbuf), global_size, "",
363                     HN_AUTOSCALE, HN_NOSPACE);
364                 printf("total: %s\n", hbuf);
365         } else {
366                 printf("total: %"PRIu64"\n", global_size);
367         }
368         RB_FOREACH(unp, ac_utree, &ac_uroot) {
369             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
370                 if (unp->uid_chunk[i].space != 0) {
371                     uid = (unp->left_bits << ACCT_CHUNK_BITS) + i;
372                     print_user(uid);
373                     if (flag_humanize) {
374                         humanize_number(hbuf, sizeof(hbuf),
375                         unp->uid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
376                         printf(" %s\n", hbuf);
377                     } else {
378                         printf(" %" PRIu64 "\n", unp->uid_chunk[i].space);
379                     }
380                 }
381             }
382         }
383         RB_FOREACH(gnp, ac_gtree, &ac_groot) {
384             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
385                 if (gnp->gid_chunk[i].space != 0) {
386                     gid = (gnp->left_bits << ACCT_CHUNK_BITS) + i;
387                     print_group(gid);
388                     if (flag_humanize) {
389                         humanize_number(hbuf, sizeof(hbuf),
390                         gnp->gid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
391                         printf(" %s\n", hbuf);
392                     } else {
393                         printf(" %" PRIu64 "\n", gnp->gid_chunk[i].space);
394                     }
395                 }
396             }
397         }
398
399         return rv;
400 }
401
402 /* print a list of filesystems with accounting enabled */
403 static int
404 get_fslist(void)
405 {
406         struct statfs *mntbufp;
407         int nloc, i;
408
409         /* read mount table from kernel */
410         nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
411         if (nloc <= 0) {
412                 perror("getmntinfo");
413                 exit(1);
414         }
415
416         /* iterate mounted filesystems */
417         for (i=0; i<nloc; i++) {
418             /* vfs accounting enabled on this one ? */
419             if (mntbufp[i].f_flags & MNT_ACCOUNTING)
420                 printf("%s on %s\n", mntbufp[i].f_mntfromname,
421                     mntbufp[i].f_mntonname);
422         }
423
424         return 0;
425 }
426
427 static bool
428 send_command(const char *path, const char *cmd,
429                 prop_object_t args, prop_dictionary_t *res)
430 {
431         prop_dictionary_t dict;
432         struct plistref pref;
433
434         bool rv;
435         int error;
436
437         dict = prop_dictionary_create();
438
439         if (dict == NULL) {
440                 printf("send_command(): couldn't create dictionary\n");
441                 return false;
442         }
443
444         rv = prop_dictionary_set_cstring(dict, "command", cmd);
445         if (rv== false) {
446                 printf("send_command(): couldn't initialize dictionary\n");
447                 return false;
448         }
449
450         rv = prop_dictionary_set(dict, "arguments", args);
451         if (rv == false) {
452                 printf("prop_dictionary_set() failed\n");
453                 return false;
454         }
455
456         error = prop_dictionary_send_syscall(dict, &pref);
457         if (error != 0) {
458                 printf("prop_dictionary_send_syscall() failed\n");
459                 prop_object_release(dict);
460                 return false;
461         }
462
463         if (flag_debug)
464                 printf("Message to kernel:\n%s\n",
465                     prop_dictionary_externalize(dict));
466
467         error = vquotactl(path, &pref);
468         if (error != 0) {
469                 printf("send_command: vquotactl = %d\n", error);
470                 return false;
471         }
472
473         error = prop_dictionary_recv_syscall(&pref, res);
474         if (error != 0) {
475                 printf("prop_dictionary_recv_syscall() failed\n");
476         }
477
478         if (flag_debug)
479                 printf("Message from kernel:\n%s\n",
480                     prop_dictionary_externalize(*res));
481
482         return true;
483 }
484
485 /* show collected statistics on mount point */
486 static int
487 show_mp(char *path)
488 {
489         prop_dictionary_t args, res;
490         prop_array_t reslist;
491         bool rv;
492         prop_object_iterator_t  iter;
493         prop_dictionary_t item;
494         uint32_t id;
495         uint64_t space, limit=0;
496         char hbuf[5];
497
498         args = prop_dictionary_create();
499         res  = prop_dictionary_create();
500         if (args == NULL)
501                 printf("show_mp(): couldn't create args dictionary\n");
502         res  = prop_dictionary_create();
503         if (res == NULL)
504                 printf("show_mp(): couldn't create res dictionary\n");
505
506         rv = send_command(path, "get usage all", args, &res);
507         if (rv == false) {
508                 printf("show-mp(): failed to send message to kernel\n");
509                 goto end;
510         }
511
512         reslist = prop_dictionary_get(res, "returned data");
513         if (reslist == NULL) {
514                 printf("show_mp(): failed to get array of results");
515                 rv = false;
516                 goto end;
517         }
518
519         iter = prop_array_iterator(reslist);
520         if (iter == NULL) {
521                 printf("show_mp(): failed to create iterator\n");
522                 rv = false;
523                 goto end;
524         }
525
526         while ((item = prop_object_iterator_next(iter)) != NULL) {
527                 rv = prop_dictionary_get_uint64(item, "space used", &space);
528                 rv = prop_dictionary_get_uint64(item, "limit", &limit);
529                 if (prop_dictionary_get_uint32(item, "uid", &id))
530                         print_user(id);
531                 else if (prop_dictionary_get_uint32(item, "gid", &id))
532                         print_group(id);
533                 else
534                         printf("total:");
535                 if (flag_humanize) {
536                         humanize_number(hbuf, sizeof(hbuf), space, "", HN_AUTOSCALE, HN_NOSPACE);
537                         printf(" %s", hbuf);
538                 } else {
539                         printf(" %"PRIu64, space);
540                 }
541                 if (limit == 0) {
542                         printf("\n");
543                         continue;
544                 }
545                 if (flag_humanize) {
546                         humanize_number(hbuf, sizeof(hbuf), limit, "", HN_AUTOSCALE, HN_NOSPACE);
547                         printf(", limit = %s\n", hbuf);
548                 } else {
549                         printf(", limit = %"PRIu64"\n", limit);
550                 }
551         }
552         prop_object_iterator_release(iter);
553
554 end:
555         prop_object_release(args);
556         prop_object_release(res);
557         return (rv == true);
558 }
559
560 /* sync the in-kernel counters to the actual file system usage */
561 static int cmd_sync(char *dirname)
562 {
563         prop_dictionary_t res, item;
564         prop_array_t args;
565         struct ac_unode *unp;
566         struct ac_gnode *gnp;
567         int rv = 0, i;
568
569         args = prop_array_create();
570         if (args == NULL)
571                 printf("cmd_sync(): couldn't create args dictionary\n");
572         res  = prop_dictionary_create();
573         if (res == NULL)
574                 printf("cmd_sync(): couldn't create res dictionary\n");
575
576         rv = get_dirsize(dirname);
577
578         item = prop_dictionary_create();
579         if (item == NULL)
580                 printf("cmd_sync(): couldn't create item dictionary\n");
581         (void) prop_dictionary_set_uint64(item, "space used", global_size);
582         prop_array_add_and_rel(args, item);
583
584         RB_FOREACH(unp, ac_utree, &ac_uroot) {
585             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
586                 if (unp->uid_chunk[i].space != 0) {
587                     item = prop_dictionary_create();
588                     (void) prop_dictionary_set_uint32(item, "uid",
589                                 (unp->left_bits << ACCT_CHUNK_BITS) + i);
590                     (void) prop_dictionary_set_uint64(item, "space used",
591                                 unp->uid_chunk[i].space);
592                     prop_array_add_and_rel(args, item);
593                 }
594             }
595         }
596         RB_FOREACH(gnp, ac_gtree, &ac_groot) {
597             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
598                 if (gnp->gid_chunk[i].space != 0) {
599                     item = prop_dictionary_create();
600                     (void) prop_dictionary_set_uint32(item, "gid",
601                                 (gnp->left_bits << ACCT_CHUNK_BITS) + i);
602                     (void) prop_dictionary_set_uint64(item, "space used",
603                                 gnp->gid_chunk[i].space);
604                     prop_array_add_and_rel(args, item);
605                 }
606             }
607         }
608
609         if (send_command(dirname, "set usage all", args, &res) == false) {
610                 printf("Failed to send message to kernel\n");
611                 rv = 1;
612         }
613
614         prop_object_release(args);
615         prop_object_release(res);
616
617         return rv;
618 }
619
620 static int
621 cmd_limit(char *dirname, uint64_t limit)
622 {
623         prop_dictionary_t res, args;
624         int rv = 0;
625
626         args = prop_dictionary_create();
627         if (args == NULL)
628                 printf("cmd_limit(): couldn't create args dictionary\n");
629         res  = prop_dictionary_create();
630         if (res == NULL)
631                 printf("cmd_limit(): couldn't create res dictionary\n");
632
633         (void) prop_dictionary_set_uint64(args, "limit", limit);
634
635         if (send_command(dirname, "set limit", args, &res) == false) {
636                 printf("Failed to send message to kernel\n");
637                 rv = 1;
638         }
639
640         prop_object_release(args);
641         prop_object_release(res);
642
643         return rv;
644 }
645
646 static int
647 cmd_limit_uid(char *dirname, uid_t uid, uint64_t limit)
648 {
649         prop_dictionary_t res, args;
650         int rv = 0;
651
652         args = prop_dictionary_create();
653         if (args == NULL)
654                 printf("cmd_limit_uid(): couldn't create args dictionary\n");
655         res  = prop_dictionary_create();
656         if (res == NULL)
657                 printf("cmd_limit_uid(): couldn't create res dictionary\n");
658
659         (void) prop_dictionary_set_uint32(args, "uid", uid);
660         (void) prop_dictionary_set_uint64(args, "limit", limit);
661
662         if (send_command(dirname, "set limit uid", args, &res) == false) {
663                 printf("Failed to send message to kernel\n");
664                 rv = 1;
665         }
666
667         prop_object_release(args);
668         prop_object_release(res);
669
670         return rv;
671 }
672
673 int
674 main(int argc, char **argv)
675 {
676         int ch;
677         uint64_t limit;
678
679         while ((ch = getopt(argc, argv, "Dhn")) != -1) {
680                 switch(ch) {
681                 case 'D':
682                         flag_debug = 1;
683                         break;
684                 case 'h':
685                         flag_humanize = 1;
686                         break;
687                 case 'n':
688                         flag_resolve_ids = 0;
689                         break;
690                 }
691         }
692         argc -= optind;
693         argv += optind;
694         if (argc < 1)
695                 usage(1);
696         
697         if (strcmp(argv[0], "check") == 0) {
698                 if (argc != 2)
699                         usage(1);
700                 return cmd_check(argv[1]);
701         }
702         if (strcmp(argv[0], "lsfs") == 0) {
703                 return get_fslist();
704         }
705         if (strcmp(argv[0], "limit") == 0) {
706                 if (argc != 3)
707                         usage(1);
708                 if (dehumanize_number(argv[2], &limit) < 0)
709                         err(1, "bad number for option: %s", argv[2]);
710
711                 return cmd_limit(argv[1], limit);
712         }
713         if (strcmp(argv[0], "show") == 0) {
714                 if (argc != 2)
715                         usage(1);
716                 return show_mp(argv[1]);
717         }
718         if (strcmp(argv[0], "sync") == 0) {
719                 if (argc != 2)
720                         usage(1);
721                 return cmd_sync(argv[1]);
722         }
723         if (strcmp(argv[0], "ulim") == 0) {
724                 struct passwd *pwd;
725                 if (argc != 4)
726                         usage(1);
727                 if ((pwd = getpwnam(argv[2])) == NULL)
728                         errx(1, "%s: no such user", argv[2]);
729                 if (dehumanize_number(argv[3], &limit) < 0)
730                         err(1, "bad number for option: %s", argv[2]);
731
732                 return cmd_limit_uid(argv[1], pwd->pw_uid, limit);
733         }
734
735         usage(0);
736 }