2 * Copyright (c) 2011, 2012 François Tigeot <ftigeot@wolfpond.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
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.
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
34 #include <sys/mount.h>
35 #include <sys/vfs_quota.h>
42 #include <libprop/proplib.h>
47 #include <sys/types.h>
52 static bool flag_debug = 0;
53 static bool flag_humanize = 0;
54 static bool flag_resolve_ids = 1;
56 static void usage(int);
57 static int get_dirsize(char *);
58 static int get_fslist(void);
63 fprintf(stderr, "usage: vquota [-Dhn] check directory\n");
64 fprintf(stderr, " vquota [-Dhn] lsfs\n");
65 fprintf(stderr, " vquota [-Dhn] show mount_point\n");
66 fprintf(stderr, " vquota [-Dhn] sync mount_point\n");
71 * Inode numbers with more than one hard link often come in groups;
72 * use linear arrays of 1024 ones as the basic unit of allocation.
73 * We only need to check if the inodes have been previously processed,
74 * bit arrays are perfect for that purpose.
76 #define HL_CHUNK_BITS 10
77 #define HL_CHUNK_ENTRIES (1<<HL_CHUNK_BITS)
78 #define HL_CHUNK_MASK (HL_CHUNK_ENTRIES - 1)
79 #define BA_UINT64_BITS 6
80 #define BA_UINT64_ENTRIES (1<<BA_UINT64_BITS)
81 #define BA_UINT64_MASK (BA_UINT64_ENTRIES - 1)
84 RB_ENTRY(hl_node) rb_entry;
86 uint64_t hl_chunk[HL_CHUNK_ENTRIES/64];
89 RB_HEAD(hl_tree,hl_node) hl_root;
91 RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
94 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
96 RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
98 struct hl_node* hl_node_insert(ino_t);
102 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b)
104 if (a->ino_left_bits < b->ino_left_bits)
106 else if (a->ino_left_bits > b->ino_left_bits)
111 struct hl_node* hl_node_insert(ino_t inode)
113 struct hl_node *hlp, *res;
115 hlp = malloc(sizeof(struct hl_node));
117 /* shouldn't happen */
118 printf("hl_node_insert(): malloc failed\n");
121 bzero(hlp, sizeof(struct hl_node));
123 hlp->ino_left_bits = (inode >> HL_CHUNK_BITS);
124 res = RB_INSERT(hl_tree, &hl_root, hlp);
126 if (res != NULL) /* shouldn't happen */
127 printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
133 * hl_register: register an inode number in a rb-tree of bit arrays
135 * - true if the inode was already processed
139 hl_register(ino_t inode)
141 struct hl_node hl_find, *hlp;
142 uint64_t ino_right_bits, ba_index, ba_offset;
143 uint64_t bitmask, bitval;
146 /* calculate the different addresses of the wanted bit */
147 hl_find.ino_left_bits = (inode >> HL_CHUNK_BITS);
149 ino_right_bits = inode & HL_CHUNK_MASK;
150 ba_index = ino_right_bits >> BA_UINT64_BITS;
151 ba_offset = ino_right_bits & BA_UINT64_MASK;
153 /* no existing node? create and initialize it */
154 if ((hlp = RB_FIND(hl_tree, &hl_root, &hl_find)) == NULL) {
155 hlp = hl_node_insert(inode);
158 /* node was found, check the bit value */
159 bitmask = 1 << ba_offset;
160 bitval = hlp->hl_chunk[ba_index] & bitmask;
166 hlp->hl_chunk[ba_index] |= bitmask;
171 /* global variable used by get_dir_size() */
172 uint64_t global_size;
174 /* storage for collected id numbers */
175 /* FIXME: same data structures used in kernel, should find a way to
176 * deduplicate this code */
179 rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
181 rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
183 RB_HEAD(ac_utree,ac_unode) ac_uroot;
184 RB_HEAD(ac_gtree,ac_gnode) ac_groot;
185 RB_PROTOTYPE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
186 RB_PROTOTYPE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
187 RB_GENERATE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
188 RB_GENERATE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
191 rb_ac_unode_cmp(struct ac_unode *a, struct ac_unode *b)
193 if (a->left_bits < b->left_bits)
195 else if (a->left_bits > b->left_bits)
201 rb_ac_gnode_cmp(struct ac_gnode *a, struct ac_gnode *b)
203 if (a->left_bits < b->left_bits)
205 else if (a->left_bits > b->left_bits)
210 static struct ac_unode*
211 unode_insert(uid_t uid)
213 struct ac_unode *unp, *res;
215 unp = malloc(sizeof(struct ac_unode));
217 printf("unode_insert(): malloc failed\n");
220 bzero(unp, sizeof(struct ac_unode));
222 unp->left_bits = (uid >> ACCT_CHUNK_BITS);
223 res = RB_INSERT(ac_utree, &ac_uroot, unp);
225 if (res != NULL) /* shouldn't happen */
226 printf("unode_insert(): RB_INSERT didn't return NULL\n");
231 static struct ac_gnode*
232 gnode_insert(gid_t gid)
234 struct ac_gnode *gnp, *res;
236 gnp = malloc(sizeof(struct ac_gnode));
238 printf("gnode_insert(): malloc failed\n");
241 bzero(gnp, sizeof(struct ac_gnode));
243 gnp->left_bits = (gid >> ACCT_CHUNK_BITS);
244 res = RB_INSERT(ac_gtree, &ac_groot, gnp);
246 if (res != NULL) /* shouldn't happen */
247 printf("gnode_insert(): RB_INSERT didn't return NULL\n");
253 * get_dirsize(): walks a directory tree in the same filesystem
255 * - global rb-trees ac_uroot and ac_groot
256 * - global variable global_size
259 get_dirsize(char* dirname)
272 struct ac_unode *unp, ufind;
273 struct ac_gnode *gnp, gfind;
275 /* TODO: check directory name sanity */
276 fts_args[0] = dirname;
279 if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
280 err(1, "fts_open() failed");
282 while ((p = fts_read(fts)) != NULL) {
283 switch (p->fts_info) {
284 /* directories, ignore them */
289 /* read errors, warn, continue and flag */
293 warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
297 file_inode = p->fts_statp->st_ino;
298 file_size = p->fts_statp->st_size;
299 file_uid = p->fts_statp->st_uid;
300 file_gid = p->fts_statp->st_gid;
302 /* files with more than one hard link: */
303 /* process them only once */
304 if (p->fts_statp->st_nlink > 1)
305 if (hl_register(file_inode))
308 global_size += file_size;
309 ufind.left_bits = (file_uid >> ACCT_CHUNK_BITS);
310 gfind.left_bits = (file_gid >> ACCT_CHUNK_BITS);
311 if ((unp = RB_FIND(ac_utree, &ac_uroot, &ufind)) == NULL)
312 unp = unode_insert(file_uid);
313 if ((gnp = RB_FIND(ac_gtree, &ac_groot, &gfind)) == NULL)
314 gnp = gnode_insert(file_gid);
315 unp->uid_chunk[(file_uid & ACCT_CHUNK_MASK)].space += file_size;
316 gnp->gid_chunk[(file_gid & ACCT_CHUNK_MASK)].space += file_size;
325 print_user(uid_t uid)
329 if (flag_resolve_ids && ((pw = getpwuid(uid)) != NULL)) {
330 printf("user %s:", pw->pw_name);
332 printf("uid %u:", uid);
337 print_group(gid_t gid)
341 if (flag_resolve_ids && ((gr = getgrgid(gid)) != NULL)) {
342 printf("group %s:", gr->gr_name);
344 printf("gid %u:", gid);
349 cmd_check(char* dirname)
353 struct ac_unode *unp;
354 struct ac_gnode *gnp;
357 rv = get_dirsize(dirname);
360 humanize_number(hbuf, sizeof(hbuf), global_size, "",
361 HN_AUTOSCALE, HN_NOSPACE);
362 printf("total: %s\n", hbuf);
364 printf("total: %"PRIu64"\n", global_size);
366 RB_FOREACH(unp, ac_utree, &ac_uroot) {
367 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
368 if (unp->uid_chunk[i].space != 0) {
369 uid = (unp->left_bits << ACCT_CHUNK_BITS) + i;
372 humanize_number(hbuf, sizeof(hbuf),
373 unp->uid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
374 printf(" %s\n", hbuf);
376 printf(" %" PRIu64 "\n", unp->uid_chunk[i].space);
381 RB_FOREACH(gnp, ac_gtree, &ac_groot) {
382 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
383 if (gnp->gid_chunk[i].space != 0) {
384 gid = (gnp->left_bits << ACCT_CHUNK_BITS) + i;
387 humanize_number(hbuf, sizeof(hbuf),
388 gnp->gid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
389 printf(" %s\n", hbuf);
391 printf(" %" PRIu64 "\n", gnp->gid_chunk[i].space);
400 /* print a list of filesystems with accounting enabled */
404 struct statfs *mntbufp;
407 /* read mount table from kernel */
408 nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
410 perror("getmntinfo");
414 /* iterate mounted filesystems */
415 for (i=0; i<nloc; i++) {
416 /* vfs accounting enabled on this one ? */
417 if (mntbufp[i].f_flags & MNT_ACCOUNTING)
418 printf("%s on %s\n", mntbufp[i].f_mntfromname,
419 mntbufp[i].f_mntonname);
426 send_command(const char *path, const char *cmd,
427 prop_object_t args, prop_dictionary_t *res)
429 prop_dictionary_t dict;
430 struct plistref pref;
435 dict = prop_dictionary_create();
438 printf("send_command(): couldn't create dictionary\n");
442 rv = prop_dictionary_set_cstring(dict, "command", cmd);
444 printf("send_command(): couldn't initialize dictionary\n");
448 rv = prop_dictionary_set(dict, "arguments", args);
450 printf("prop_dictionary_set() failed\n");
454 error = prop_dictionary_send_syscall(dict, &pref);
456 printf("prop_dictionary_send_syscall() failed\n");
457 prop_object_release(dict);
462 printf("Message to kernel:\n%s\n",
463 prop_dictionary_externalize(dict));
465 error = vquotactl(path, &pref);
467 printf("send_command: vquotactl = %d\n", error);
471 error = prop_dictionary_recv_syscall(&pref, res);
473 printf("prop_dictionary_recv_syscall() failed\n");
477 printf("Message from kernel:\n%s\n",
478 prop_dictionary_externalize(*res));
483 /* show collected statistics on mount point */
487 prop_dictionary_t args, res;
488 prop_array_t reslist;
490 prop_object_iterator_t iter;
491 prop_dictionary_t item;
493 uint64_t space, limit=0;
496 args = prop_dictionary_create();
497 res = prop_dictionary_create();
499 printf("show_mp(): couldn't create args dictionary\n");
500 res = prop_dictionary_create();
502 printf("show_mp(): couldn't create res dictionary\n");
504 rv = send_command(path, "get usage all", args, &res);
506 printf("show-mp(): failed to send message to kernel\n");
510 reslist = prop_dictionary_get(res, "returned data");
511 if (reslist == NULL) {
512 printf("show_mp(): failed to get array of results");
517 iter = prop_array_iterator(reslist);
519 printf("show_mp(): failed to create iterator\n");
524 while ((item = prop_object_iterator_next(iter)) != NULL) {
525 rv = prop_dictionary_get_uint64(item, "space used", &space);
526 rv = prop_dictionary_get_uint64(item, "limit", &limit);
527 if (prop_dictionary_get_uint32(item, "uid", &id))
529 else if (prop_dictionary_get_uint32(item, "gid", &id))
534 humanize_number(hbuf, sizeof(hbuf), space, "", HN_AUTOSCALE, HN_NOSPACE);
537 printf(" %"PRIu64, space);
544 humanize_number(hbuf, sizeof(hbuf), limit, "", HN_AUTOSCALE, HN_NOSPACE);
545 printf(", limit = %s\n", hbuf);
547 printf(", limit = %"PRIu64"\n", limit);
550 prop_object_iterator_release(iter);
553 prop_object_release(args);
554 prop_object_release(res);
558 /* sync the in-kernel counters to the actual file system usage */
559 static int cmd_sync(char *dirname)
561 prop_dictionary_t res, item;
563 struct ac_unode *unp;
564 struct ac_gnode *gnp;
567 args = prop_array_create();
569 printf("cmd_sync(): couldn't create args dictionary\n");
570 res = prop_dictionary_create();
572 printf("cmd_sync(): couldn't create res dictionary\n");
574 rv = get_dirsize(dirname);
576 item = prop_dictionary_create();
578 printf("cmd_sync(): couldn't create item dictionary\n");
579 (void) prop_dictionary_set_uint64(item, "space used", global_size);
580 prop_array_add_and_rel(args, item);
582 RB_FOREACH(unp, ac_utree, &ac_uroot) {
583 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
584 if (unp->uid_chunk[i].space != 0) {
585 item = prop_dictionary_create();
586 (void) prop_dictionary_set_uint32(item, "uid",
587 (unp->left_bits << ACCT_CHUNK_BITS) + i);
588 (void) prop_dictionary_set_uint64(item, "space used",
589 unp->uid_chunk[i].space);
590 prop_array_add_and_rel(args, item);
594 RB_FOREACH(gnp, ac_gtree, &ac_groot) {
595 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
596 if (gnp->gid_chunk[i].space != 0) {
597 item = prop_dictionary_create();
598 (void) prop_dictionary_set_uint32(item, "gid",
599 (gnp->left_bits << ACCT_CHUNK_BITS) + i);
600 (void) prop_dictionary_set_uint64(item, "space used",
601 gnp->gid_chunk[i].space);
602 prop_array_add_and_rel(args, item);
607 if (send_command(dirname, "set usage all", args, &res) == false) {
608 printf("Failed to send message to kernel\n");
612 prop_object_release(args);
613 prop_object_release(res);
619 main(int argc, char **argv)
623 while ((ch = getopt(argc, argv, "Dhn")) != -1) {
632 flag_resolve_ids = 0;
641 if (strcmp(argv[0], "check") == 0) {
644 return cmd_check(argv[1]);
646 if (strcmp(argv[0], "lsfs") == 0) {
649 if (strcmp(argv[0], "show") == 0) {
652 return show_mp(argv[1]);
654 if (strcmp(argv[0], "sync") == 0) {
657 return cmd_sync(argv[1]);