e6348ba055a760d7e4965529bce1ecc373a3ae3e
[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] show mount_point\n");
67         fprintf(stderr, "       vquota [-Dhn] sync mount_point\n");
68         exit(retcode);
69 }
70
71 /*
72  * Inode numbers with more than one hard link often come in groups;
73  * use linear arrays of 1024 ones as the basic unit of allocation.
74  * We only need to check if the inodes have been previously processed,
75  * bit arrays are perfect for that purpose.
76  */
77 #define HL_CHUNK_BITS           10
78 #define HL_CHUNK_ENTRIES        (1<<HL_CHUNK_BITS)
79 #define HL_CHUNK_MASK           (HL_CHUNK_ENTRIES - 1)
80 #define BA_UINT64_BITS          6
81 #define BA_UINT64_ENTRIES       (1<<BA_UINT64_BITS)
82 #define BA_UINT64_MASK          (BA_UINT64_ENTRIES - 1)
83
84 struct hl_node {
85         RB_ENTRY(hl_node)       rb_entry;
86         ino_t                   ino_left_bits;
87         uint64_t                hl_chunk[HL_CHUNK_ENTRIES/64];
88 };
89
90 RB_HEAD(hl_tree,hl_node)        hl_root;
91
92 RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
93
94 static int
95 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
96
97 RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
98
99 struct hl_node* hl_node_insert(ino_t);
100
101
102 static int
103 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b)
104 {
105         if (a->ino_left_bits < b->ino_left_bits)
106                 return(-1);
107         else if (a->ino_left_bits > b->ino_left_bits)
108                 return(1);
109         return(0);
110 }
111
112 struct hl_node* hl_node_insert(ino_t inode)
113 {
114         struct hl_node *hlp, *res;
115
116         hlp = malloc(sizeof(struct hl_node));
117         if (hlp == NULL) {
118                 /* shouldn't happen */
119                 printf("hl_node_insert(): malloc failed\n");
120                 exit(ENOMEM);
121         }
122         bzero(hlp, sizeof(struct hl_node));
123
124         hlp->ino_left_bits = (inode >> HL_CHUNK_BITS);
125         res = RB_INSERT(hl_tree, &hl_root, hlp);
126
127         if (res != NULL)        /* shouldn't happen */
128                 printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
129
130         return hlp;
131 }
132
133 /*
134  * hl_register: register an inode number in a rb-tree of bit arrays
135  * returns:
136  * - true if the inode was already processed
137  * - false otherwise
138  */
139 static bool
140 hl_register(ino_t inode)
141 {
142         struct hl_node hl_find, *hlp;
143         uint64_t ino_right_bits, ba_index, ba_offset;
144         uint64_t bitmask, bitval;
145         bool retval = false;
146
147         /* calculate the different addresses of the wanted bit */
148         hl_find.ino_left_bits = (inode >> HL_CHUNK_BITS);
149
150         ino_right_bits = inode & HL_CHUNK_MASK;
151         ba_index  = ino_right_bits >> BA_UINT64_BITS;
152         ba_offset = ino_right_bits & BA_UINT64_MASK;
153
154         /* no existing node? create and initialize it */
155         if ((hlp = RB_FIND(hl_tree, &hl_root, &hl_find)) == NULL) {
156                 hlp = hl_node_insert(inode);
157         }
158
159         /* node was found, check the bit value */
160         bitmask = 1 << ba_offset;
161         bitval = hlp->hl_chunk[ba_index] & bitmask;
162         if (bitval != 0) {
163                 retval = true;
164         }
165
166         /* set the bit */
167         hlp->hl_chunk[ba_index] |= bitmask;
168
169         return retval;
170 }
171
172 /* global variable used by get_dir_size() */
173 uint64_t global_size;
174
175 /* storage for collected id numbers */
176 /* FIXME: same data structures used in kernel, should find a way to
177  * deduplicate this code */
178
179 static int
180 rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
181 static int
182 rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
183
184 RB_HEAD(ac_utree,ac_unode) ac_uroot;
185 RB_HEAD(ac_gtree,ac_gnode) ac_groot;
186 RB_PROTOTYPE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
187 RB_PROTOTYPE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
188 RB_GENERATE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
189 RB_GENERATE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
190
191 static int
192 rb_ac_unode_cmp(struct ac_unode *a, struct ac_unode *b)
193 {
194         if (a->left_bits < b->left_bits)
195                 return(-1);
196         else if (a->left_bits > b->left_bits)
197                 return(1);
198         return(0);
199 }
200
201 static int
202 rb_ac_gnode_cmp(struct ac_gnode *a, struct ac_gnode *b)
203 {
204         if (a->left_bits < b->left_bits)
205                 return(-1);
206         else if (a->left_bits > b->left_bits)
207                 return(1);
208         return(0);
209 }
210
211 static struct ac_unode*
212 unode_insert(uid_t uid)
213 {
214         struct ac_unode *unp, *res;
215
216         unp = malloc(sizeof(struct ac_unode));
217         if (unp == NULL) {
218                 printf("unode_insert(): malloc failed\n");
219                 exit(ENOMEM);
220         }
221         bzero(unp, sizeof(struct ac_unode));
222
223         unp->left_bits = (uid >> ACCT_CHUNK_BITS);
224         res = RB_INSERT(ac_utree, &ac_uroot, unp);
225
226         if (res != NULL)        /* shouldn't happen */
227                 printf("unode_insert(): RB_INSERT didn't return NULL\n");
228
229         return unp;
230 }
231
232 static struct ac_gnode*
233 gnode_insert(gid_t gid)
234 {
235         struct ac_gnode *gnp, *res;
236
237         gnp = malloc(sizeof(struct ac_gnode));
238         if (gnp == NULL) {
239                 printf("gnode_insert(): malloc failed\n");
240                 exit(ENOMEM);
241         }
242         bzero(gnp, sizeof(struct ac_gnode));
243
244         gnp->left_bits = (gid >> ACCT_CHUNK_BITS);
245         res = RB_INSERT(ac_gtree, &ac_groot, gnp);
246
247         if (res != NULL)        /* shouldn't happen */
248                 printf("gnode_insert(): RB_INSERT didn't return NULL\n");
249
250         return gnp;
251 }
252
253 /*
254  * get_dirsize(): walks a directory tree in the same filesystem
255  * output:
256  * - global rb-trees ac_uroot and ac_groot
257  * - global variable global_size
258  */
259 static int
260 get_dirsize(char* dirname)
261 {
262         FTS             *fts;
263         FTSENT          *p;
264         char*           fts_args[2];
265         int             retval = 0;
266
267         /* what we need */
268         ino_t           file_inode;
269         off_t           file_size;
270         uid_t           file_uid;
271         gid_t           file_gid;
272
273         struct ac_unode *unp, ufind;
274         struct ac_gnode *gnp, gfind;
275
276         /* TODO: check directory name sanity */
277         fts_args[0] = dirname;
278         fts_args[1] = NULL;
279
280         if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
281                 err(1, "fts_open() failed");
282
283         while ((p = fts_read(fts)) != NULL) {
284                 switch (p->fts_info) {
285                 /* directories, ignore them */
286                 case FTS_D:
287                 case FTS_DC:
288                 case FTS_DP:
289                         break;
290                 /* read errors, warn, continue and flag */
291                 case FTS_DNR:
292                 case FTS_ERR:
293                 case FTS_NS:
294                         warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
295                         retval = 1;
296                         break;
297                 default:
298                         file_inode = p->fts_statp->st_ino;
299                         file_size = p->fts_statp->st_size;
300                         file_uid = p->fts_statp->st_uid;
301                         file_gid = p->fts_statp->st_gid;
302
303                         /* files with more than one hard link: */
304                         /* process them only once */
305                         if (p->fts_statp->st_nlink > 1)
306                                 if (hl_register(file_inode))
307                                         break;
308
309                         global_size += file_size;
310                         ufind.left_bits = (file_uid >> ACCT_CHUNK_BITS);
311                         gfind.left_bits = (file_gid >> ACCT_CHUNK_BITS);
312                         if ((unp = RB_FIND(ac_utree, &ac_uroot, &ufind)) == NULL)
313                                 unp = unode_insert(file_uid);
314                         if ((gnp = RB_FIND(ac_gtree, &ac_groot, &gfind)) == NULL)
315                                 gnp = gnode_insert(file_gid);
316                         unp->uid_chunk[(file_uid & ACCT_CHUNK_MASK)].space += file_size;
317                         gnp->gid_chunk[(file_gid & ACCT_CHUNK_MASK)].space += file_size;
318                 }
319         }
320         fts_close(fts);
321
322         return retval;
323 }
324
325 static void
326 print_user(uid_t uid)
327 {
328         struct passwd *pw;
329
330         if (flag_resolve_ids && ((pw = getpwuid(uid)) != NULL)) {
331                 printf("user %s:", pw->pw_name);
332         } else {
333                 printf("uid %u:", uid);
334         }
335 }
336
337 static void
338 print_group(gid_t gid)
339 {
340         struct group *gr;
341
342         if (flag_resolve_ids && ((gr = getgrgid(gid)) != NULL)) {
343                 printf("group %s:", gr->gr_name);
344         } else {
345                 printf("gid %u:", gid);
346         }
347 }
348
349 static int
350 cmd_check(char* dirname)
351 {
352         int32_t uid, gid;
353         char    hbuf[5];
354         struct  ac_unode *unp;
355         struct  ac_gnode *gnp;
356         int     rv, i;
357
358         rv = get_dirsize(dirname);
359
360         if (flag_humanize) {
361                 humanize_number(hbuf, sizeof(hbuf), global_size, "",
362                     HN_AUTOSCALE, HN_NOSPACE);
363                 printf("total: %s\n", hbuf);
364         } else {
365                 printf("total: %"PRIu64"\n", global_size);
366         }
367         RB_FOREACH(unp, ac_utree, &ac_uroot) {
368             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
369                 if (unp->uid_chunk[i].space != 0) {
370                     uid = (unp->left_bits << ACCT_CHUNK_BITS) + i;
371                     print_user(uid);
372                     if (flag_humanize) {
373                         humanize_number(hbuf, sizeof(hbuf),
374                         unp->uid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
375                         printf(" %s\n", hbuf);
376                     } else {
377                         printf(" %" PRIu64 "\n", unp->uid_chunk[i].space);
378                     }
379                 }
380             }
381         }
382         RB_FOREACH(gnp, ac_gtree, &ac_groot) {
383             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
384                 if (gnp->gid_chunk[i].space != 0) {
385                     gid = (gnp->left_bits << ACCT_CHUNK_BITS) + i;
386                     print_group(gid);
387                     if (flag_humanize) {
388                         humanize_number(hbuf, sizeof(hbuf),
389                         gnp->gid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
390                         printf(" %s\n", hbuf);
391                     } else {
392                         printf(" %" PRIu64 "\n", gnp->gid_chunk[i].space);
393                     }
394                 }
395             }
396         }
397
398         return rv;
399 }
400
401 /* print a list of filesystems with accounting enabled */
402 static int
403 get_fslist(void)
404 {
405         struct statfs *mntbufp;
406         int nloc, i;
407
408         /* read mount table from kernel */
409         nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
410         if (nloc <= 0) {
411                 perror("getmntinfo");
412                 exit(1);
413         }
414
415         /* iterate mounted filesystems */
416         for (i=0; i<nloc; i++) {
417             /* vfs accounting enabled on this one ? */
418             if (mntbufp[i].f_flags & MNT_ACCOUNTING)
419                 printf("%s on %s\n", mntbufp[i].f_mntfromname,
420                     mntbufp[i].f_mntonname);
421         }
422
423         return 0;
424 }
425
426 static bool
427 send_command(const char *path, const char *cmd,
428                 prop_object_t args, prop_dictionary_t *res)
429 {
430         prop_dictionary_t dict;
431         struct plistref pref;
432
433         bool rv;
434         int error;
435
436         dict = prop_dictionary_create();
437
438         if (dict == NULL) {
439                 printf("send_command(): couldn't create dictionary\n");
440                 return false;
441         }
442
443         rv = prop_dictionary_set_cstring(dict, "command", cmd);
444         if (rv== false) {
445                 printf("send_command(): couldn't initialize dictionary\n");
446                 return false;
447         }
448
449         rv = prop_dictionary_set(dict, "arguments", args);
450         if (rv == false) {
451                 printf("prop_dictionary_set() failed\n");
452                 return false;
453         }
454
455         error = prop_dictionary_send_syscall(dict, &pref);
456         if (error != 0) {
457                 printf("prop_dictionary_send_syscall() failed\n");
458                 prop_object_release(dict);
459                 return false;
460         }
461
462         if (flag_debug)
463                 printf("Message to kernel:\n%s\n",
464                     prop_dictionary_externalize(dict));
465
466         error = vquotactl(path, &pref);
467         if (error != 0) {
468                 printf("send_command: vquotactl = %d\n", error);
469                 return false;
470         }
471
472         error = prop_dictionary_recv_syscall(&pref, res);
473         if (error != 0) {
474                 printf("prop_dictionary_recv_syscall() failed\n");
475         }
476
477         if (flag_debug)
478                 printf("Message from kernel:\n%s\n",
479                     prop_dictionary_externalize(*res));
480
481         return true;
482 }
483
484 /* show collected statistics on mount point */
485 static int
486 show_mp(char *path)
487 {
488         prop_dictionary_t args, res;
489         prop_array_t reslist;
490         bool rv;
491         prop_object_iterator_t  iter;
492         prop_dictionary_t item;
493         uint32_t id;
494         uint64_t space, limit=0;
495         char hbuf[5];
496
497         args = prop_dictionary_create();
498         res  = prop_dictionary_create();
499         if (args == NULL)
500                 printf("show_mp(): couldn't create args dictionary\n");
501         res  = prop_dictionary_create();
502         if (res == NULL)
503                 printf("show_mp(): couldn't create res dictionary\n");
504
505         rv = send_command(path, "get usage all", args, &res);
506         if (rv == false) {
507                 printf("show-mp(): failed to send message to kernel\n");
508                 goto end;
509         }
510
511         reslist = prop_dictionary_get(res, "returned data");
512         if (reslist == NULL) {
513                 printf("show_mp(): failed to get array of results");
514                 rv = false;
515                 goto end;
516         }
517
518         iter = prop_array_iterator(reslist);
519         if (iter == NULL) {
520                 printf("show_mp(): failed to create iterator\n");
521                 rv = false;
522                 goto end;
523         }
524
525         while ((item = prop_object_iterator_next(iter)) != NULL) {
526                 rv = prop_dictionary_get_uint64(item, "space used", &space);
527                 rv = prop_dictionary_get_uint64(item, "limit", &limit);
528                 if (prop_dictionary_get_uint32(item, "uid", &id))
529                         print_user(id);
530                 else if (prop_dictionary_get_uint32(item, "gid", &id))
531                         print_group(id);
532                 else
533                         printf("total:");
534                 if (flag_humanize) {
535                         humanize_number(hbuf, sizeof(hbuf), space, "", HN_AUTOSCALE, HN_NOSPACE);
536                         printf(" %s", hbuf);
537                 } else {
538                         printf(" %"PRIu64, space);
539                 }
540                 if (limit == 0) {
541                         printf("\n");
542                         continue;
543                 }
544                 if (flag_humanize) {
545                         humanize_number(hbuf, sizeof(hbuf), limit, "", HN_AUTOSCALE, HN_NOSPACE);
546                         printf(", limit = %s\n", hbuf);
547                 } else {
548                         printf(", limit = %"PRIu64"\n", limit);
549                 }
550         }
551         prop_object_iterator_release(iter);
552
553 end:
554         prop_object_release(args);
555         prop_object_release(res);
556         return (rv == true);
557 }
558
559 /* sync the in-kernel counters to the actual file system usage */
560 static int cmd_sync(char *dirname)
561 {
562         prop_dictionary_t res, item;
563         prop_array_t args;
564         struct ac_unode *unp;
565         struct ac_gnode *gnp;
566         int rv = 0, i;
567
568         args = prop_array_create();
569         if (args == NULL)
570                 printf("cmd_sync(): couldn't create args dictionary\n");
571         res  = prop_dictionary_create();
572         if (res == NULL)
573                 printf("cmd_sync(): couldn't create res dictionary\n");
574
575         rv = get_dirsize(dirname);
576
577         item = prop_dictionary_create();
578         if (item == NULL)
579                 printf("cmd_sync(): couldn't create item dictionary\n");
580         (void) prop_dictionary_set_uint64(item, "space used", global_size);
581         prop_array_add_and_rel(args, item);
582
583         RB_FOREACH(unp, ac_utree, &ac_uroot) {
584             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
585                 if (unp->uid_chunk[i].space != 0) {
586                     item = prop_dictionary_create();
587                     (void) prop_dictionary_set_uint32(item, "uid",
588                                 (unp->left_bits << ACCT_CHUNK_BITS) + i);
589                     (void) prop_dictionary_set_uint64(item, "space used",
590                                 unp->uid_chunk[i].space);
591                     prop_array_add_and_rel(args, item);
592                 }
593             }
594         }
595         RB_FOREACH(gnp, ac_gtree, &ac_groot) {
596             for (i=0; i<ACCT_CHUNK_NIDS; i++) {
597                 if (gnp->gid_chunk[i].space != 0) {
598                     item = prop_dictionary_create();
599                     (void) prop_dictionary_set_uint32(item, "gid",
600                                 (gnp->left_bits << ACCT_CHUNK_BITS) + i);
601                     (void) prop_dictionary_set_uint64(item, "space used",
602                                 gnp->gid_chunk[i].space);
603                     prop_array_add_and_rel(args, item);
604                 }
605             }
606         }
607
608         if (send_command(dirname, "set usage all", args, &res) == false) {
609                 printf("Failed to send message to kernel\n");
610                 rv = 1;
611         }
612
613         prop_object_release(args);
614         prop_object_release(res);
615
616         return rv;
617 }
618
619 static int
620 cmd_limit(char *dirname, uint64_t limit)
621 {
622         prop_dictionary_t res, args;
623         int rv = 0;
624
625         args = prop_dictionary_create();
626         if (args == NULL)
627                 printf("cmd_limit(): couldn't create args dictionary\n");
628         res  = prop_dictionary_create();
629         if (res == NULL)
630                 printf("cmd_limit(): couldn't create res dictionary\n");
631
632         (void) prop_dictionary_set_uint64(args, "limit", limit);
633
634         if (send_command(dirname, "set limit", args, &res) == false) {
635                 printf("Failed to send message to kernel\n");
636                 rv = 1;
637         }
638
639         prop_object_release(args);
640         prop_object_release(res);
641
642         return rv;
643 }
644
645 int
646 main(int argc, char **argv)
647 {
648         int ch;
649
650         while ((ch = getopt(argc, argv, "Dhn")) != -1) {
651                 switch(ch) {
652                 case 'D':
653                         flag_debug = 1;
654                         break;
655                 case 'h':
656                         flag_humanize = 1;
657                         break;
658                 case 'n':
659                         flag_resolve_ids = 0;
660                         break;
661                 }
662         }
663         argc -= optind;
664         argv += optind;
665         if (argc < 1)
666                 usage(1);
667         
668         if (strcmp(argv[0], "check") == 0) {
669                 if (argc != 2)
670                         usage(1);
671                 return cmd_check(argv[1]);
672         }
673         if (strcmp(argv[0], "lsfs") == 0) {
674                 return get_fslist();
675         }
676         if (strcmp(argv[0], "limit") == 0) {
677                 uint64_t limit;
678                 if (argc != 3)
679                         usage(1);
680                 if (dehumanize_number(argv[2], &limit) < 0)
681                         err(1, "bad number for option: %s", argv[2]);
682
683                 return cmd_limit(argv[1], limit);
684         }
685         if (strcmp(argv[0], "show") == 0) {
686                 if (argc != 2)
687                         usage(1);
688                 return show_mp(argv[1]);
689         }
690         if (strcmp(argv[0], "sync") == 0) {
691                 if (argc != 2)
692                         usage(1);
693                 return cmd_sync(argv[1]);
694         }
695
696         usage(0);
697 }