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] limit mount_point size\n");
66 fprintf(stderr
, " vquota [-Dhn] ulim mount_point user size\n");
67 fprintf(stderr
, " vquota [-Dhn] glim mount_point group size\n");
68 fprintf(stderr
, " vquota [-Dhn] show mount_point\n");
69 fprintf(stderr
, " vquota [-Dhn] sync mount_point\n");
74 * Inode numbers with more than one hard link often come in groups;
75 * use linear arrays of 1024 ones as the basic unit of allocation.
76 * We only need to check if the inodes have been previously processed,
77 * bit arrays are perfect for that purpose.
79 #define HL_CHUNK_BITS 10
80 #define HL_CHUNK_ENTRIES (1<<HL_CHUNK_BITS)
81 #define HL_CHUNK_MASK (HL_CHUNK_ENTRIES - 1)
82 #define BA_UINT64_BITS 6
83 #define BA_UINT64_ENTRIES (1<<BA_UINT64_BITS)
84 #define BA_UINT64_MASK (BA_UINT64_ENTRIES - 1)
87 RB_ENTRY(hl_node
) rb_entry
;
89 uint64_t hl_chunk
[HL_CHUNK_ENTRIES
/64];
92 RB_HEAD(hl_tree
,hl_node
) hl_root
;
94 RB_PROTOTYPE(hl_tree
, hl_node
, rb_entry
, rb_hl_node_cmp
);
97 rb_hl_node_cmp(struct hl_node
*a
, struct hl_node
*b
);
99 RB_GENERATE(hl_tree
, hl_node
, rb_entry
, rb_hl_node_cmp
);
101 struct hl_node
* hl_node_insert(ino_t
);
105 rb_hl_node_cmp(struct hl_node
*a
, struct hl_node
*b
)
107 if (a
->ino_left_bits
< b
->ino_left_bits
)
109 else if (a
->ino_left_bits
> b
->ino_left_bits
)
114 struct hl_node
* hl_node_insert(ino_t inode
)
116 struct hl_node
*hlp
, *res
;
118 hlp
= malloc(sizeof(struct hl_node
));
120 /* shouldn't happen */
121 printf("hl_node_insert(): malloc failed\n");
124 bzero(hlp
, sizeof(struct hl_node
));
126 hlp
->ino_left_bits
= (inode
>> HL_CHUNK_BITS
);
127 res
= RB_INSERT(hl_tree
, &hl_root
, hlp
);
129 if (res
!= NULL
) /* shouldn't happen */
130 printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
136 * hl_register: register an inode number in a rb-tree of bit arrays
138 * - true if the inode was already processed
142 hl_register(ino_t inode
)
144 struct hl_node hl_find
, *hlp
;
145 uint64_t ino_right_bits
, ba_index
, ba_offset
;
146 uint64_t bitmask
, bitval
;
149 /* calculate the different addresses of the wanted bit */
150 hl_find
.ino_left_bits
= (inode
>> HL_CHUNK_BITS
);
152 ino_right_bits
= inode
& HL_CHUNK_MASK
;
153 ba_index
= ino_right_bits
>> BA_UINT64_BITS
;
154 ba_offset
= ino_right_bits
& BA_UINT64_MASK
;
156 /* no existing node? create and initialize it */
157 if ((hlp
= RB_FIND(hl_tree
, &hl_root
, &hl_find
)) == NULL
) {
158 hlp
= hl_node_insert(inode
);
161 /* node was found, check the bit value */
162 bitmask
= 1 << ba_offset
;
163 bitval
= hlp
->hl_chunk
[ba_index
] & bitmask
;
169 hlp
->hl_chunk
[ba_index
] |= bitmask
;
174 /* global variable used by get_dir_size() */
175 uint64_t global_size
;
177 /* storage for collected id numbers */
178 /* FIXME: same data structures used in kernel, should find a way to
179 * deduplicate this code */
182 rb_ac_unode_cmp(struct ac_unode
*, struct ac_unode
*);
184 rb_ac_gnode_cmp(struct ac_gnode
*, struct ac_gnode
*);
186 RB_HEAD(ac_utree
,ac_unode
) ac_uroot
;
187 RB_HEAD(ac_gtree
,ac_gnode
) ac_groot
;
188 RB_PROTOTYPE(ac_utree
, ac_unode
, rb_entry
, rb_ac_unode_cmp
);
189 RB_PROTOTYPE(ac_gtree
, ac_gnode
, rb_entry
, rb_ac_gnode_cmp
);
190 RB_GENERATE(ac_utree
, ac_unode
, rb_entry
, rb_ac_unode_cmp
);
191 RB_GENERATE(ac_gtree
, ac_gnode
, rb_entry
, rb_ac_gnode_cmp
);
194 rb_ac_unode_cmp(struct ac_unode
*a
, struct ac_unode
*b
)
196 if (a
->left_bits
< b
->left_bits
)
198 else if (a
->left_bits
> b
->left_bits
)
204 rb_ac_gnode_cmp(struct ac_gnode
*a
, struct ac_gnode
*b
)
206 if (a
->left_bits
< b
->left_bits
)
208 else if (a
->left_bits
> b
->left_bits
)
213 static struct ac_unode
*
214 unode_insert(uid_t uid
)
216 struct ac_unode
*unp
, *res
;
218 unp
= malloc(sizeof(struct ac_unode
));
220 printf("unode_insert(): malloc failed\n");
223 bzero(unp
, sizeof(struct ac_unode
));
225 unp
->left_bits
= (uid
>> ACCT_CHUNK_BITS
);
226 res
= RB_INSERT(ac_utree
, &ac_uroot
, unp
);
228 if (res
!= NULL
) /* shouldn't happen */
229 printf("unode_insert(): RB_INSERT didn't return NULL\n");
234 static struct ac_gnode
*
235 gnode_insert(gid_t gid
)
237 struct ac_gnode
*gnp
, *res
;
239 gnp
= malloc(sizeof(struct ac_gnode
));
241 printf("gnode_insert(): malloc failed\n");
244 bzero(gnp
, sizeof(struct ac_gnode
));
246 gnp
->left_bits
= (gid
>> ACCT_CHUNK_BITS
);
247 res
= RB_INSERT(ac_gtree
, &ac_groot
, gnp
);
249 if (res
!= NULL
) /* shouldn't happen */
250 printf("gnode_insert(): RB_INSERT didn't return NULL\n");
256 * get_dirsize(): walks a directory tree in the same filesystem
258 * - global rb-trees ac_uroot and ac_groot
259 * - global variable global_size
262 get_dirsize(char* dirname
)
275 struct ac_unode
*unp
, ufind
;
276 struct ac_gnode
*gnp
, gfind
;
278 /* TODO: check directory name sanity */
279 fts_args
[0] = dirname
;
282 if ((fts
= fts_open(fts_args
, FTS_PHYSICAL
|FTS_XDEV
, NULL
)) == NULL
)
283 err(1, "fts_open() failed");
285 while ((p
= fts_read(fts
)) != NULL
) {
286 switch (p
->fts_info
) {
287 /* directories, ignore them */
292 /* read errors, warn, continue and flag */
296 warnx("%s: %s", p
->fts_path
, strerror(p
->fts_errno
));
300 file_inode
= p
->fts_statp
->st_ino
;
301 file_size
= p
->fts_statp
->st_size
;
302 file_uid
= p
->fts_statp
->st_uid
;
303 file_gid
= p
->fts_statp
->st_gid
;
305 /* files with more than one hard link: */
306 /* process them only once */
307 if (p
->fts_statp
->st_nlink
> 1)
308 if (hl_register(file_inode
))
311 global_size
+= file_size
;
312 ufind
.left_bits
= (file_uid
>> ACCT_CHUNK_BITS
);
313 gfind
.left_bits
= (file_gid
>> ACCT_CHUNK_BITS
);
314 if ((unp
= RB_FIND(ac_utree
, &ac_uroot
, &ufind
)) == NULL
)
315 unp
= unode_insert(file_uid
);
316 if ((gnp
= RB_FIND(ac_gtree
, &ac_groot
, &gfind
)) == NULL
)
317 gnp
= gnode_insert(file_gid
);
318 unp
->uid_chunk
[(file_uid
& ACCT_CHUNK_MASK
)].space
+= file_size
;
319 gnp
->gid_chunk
[(file_gid
& ACCT_CHUNK_MASK
)].space
+= file_size
;
328 print_user(uid_t uid
)
332 if (flag_resolve_ids
&& ((pw
= getpwuid(uid
)) != NULL
)) {
333 printf("user %s:", pw
->pw_name
);
335 printf("uid %u:", uid
);
340 print_group(gid_t gid
)
344 if (flag_resolve_ids
&& ((gr
= getgrgid(gid
)) != NULL
)) {
345 printf("group %s:", gr
->gr_name
);
347 printf("gid %u:", gid
);
352 cmd_check(char* dirname
)
356 struct ac_unode
*unp
;
357 struct ac_gnode
*gnp
;
360 rv
= get_dirsize(dirname
);
363 humanize_number(hbuf
, sizeof(hbuf
), global_size
, "",
364 HN_AUTOSCALE
, HN_NOSPACE
);
365 printf("total: %s\n", hbuf
);
367 printf("total: %"PRIu64
"\n", global_size
);
369 RB_FOREACH(unp
, ac_utree
, &ac_uroot
) {
370 for (i
=0; i
<ACCT_CHUNK_NIDS
; i
++) {
371 if (unp
->uid_chunk
[i
].space
!= 0) {
372 uid
= (unp
->left_bits
<< ACCT_CHUNK_BITS
) + i
;
375 humanize_number(hbuf
, sizeof(hbuf
),
376 unp
->uid_chunk
[i
].space
, "", HN_AUTOSCALE
, HN_NOSPACE
);
377 printf(" %s\n", hbuf
);
379 printf(" %" PRIu64
"\n", unp
->uid_chunk
[i
].space
);
384 RB_FOREACH(gnp
, ac_gtree
, &ac_groot
) {
385 for (i
=0; i
<ACCT_CHUNK_NIDS
; i
++) {
386 if (gnp
->gid_chunk
[i
].space
!= 0) {
387 gid
= (gnp
->left_bits
<< ACCT_CHUNK_BITS
) + i
;
390 humanize_number(hbuf
, sizeof(hbuf
),
391 gnp
->gid_chunk
[i
].space
, "", HN_AUTOSCALE
, HN_NOSPACE
);
392 printf(" %s\n", hbuf
);
394 printf(" %" PRIu64
"\n", gnp
->gid_chunk
[i
].space
);
403 /* print a list of filesystems with accounting enabled */
407 struct statfs
*mntbufp
;
410 /* read mount table from kernel */
411 nloc
= getmntinfo(&mntbufp
, MNT_NOWAIT
|MNT_LOCAL
);
413 perror("getmntinfo");
417 /* iterate mounted filesystems */
418 for (i
=0; i
<nloc
; i
++) {
419 /* vfs quota enabled on this one ? */
420 if (mntbufp
[i
].f_flags
& MNT_QUOTA
)
421 printf("%s on %s\n", mntbufp
[i
].f_mntfromname
,
422 mntbufp
[i
].f_mntonname
);
429 send_command(const char *path
, const char *cmd
,
430 prop_object_t args
, prop_dictionary_t
*res
)
432 prop_dictionary_t dict
;
433 struct plistref pref
;
438 dict
= prop_dictionary_create();
441 printf("send_command(): couldn't create dictionary\n");
445 rv
= prop_dictionary_set_cstring(dict
, "command", cmd
);
447 printf("send_command(): couldn't initialize dictionary\n");
451 rv
= prop_dictionary_set(dict
, "arguments", args
);
453 printf("prop_dictionary_set() failed\n");
457 error
= prop_dictionary_send_syscall(dict
, &pref
);
459 printf("prop_dictionary_send_syscall() failed\n");
460 prop_object_release(dict
);
465 printf("Message to kernel:\n%s\n",
466 prop_dictionary_externalize(dict
));
468 error
= vquotactl(path
, &pref
);
470 printf("send_command: vquotactl = %d\n", error
);
474 error
= prop_dictionary_recv_syscall(&pref
, res
);
476 printf("prop_dictionary_recv_syscall() failed\n");
480 printf("Message from kernel:\n%s\n",
481 prop_dictionary_externalize(*res
));
486 /* show collected statistics on mount point */
490 prop_dictionary_t args
, res
;
491 prop_array_t reslist
;
493 prop_object_iterator_t iter
;
494 prop_dictionary_t item
;
496 uint64_t space
, limit
=0;
499 args
= prop_dictionary_create();
500 res
= prop_dictionary_create();
502 printf("show_mp(): couldn't create args dictionary\n");
503 res
= prop_dictionary_create();
505 printf("show_mp(): couldn't create res dictionary\n");
507 rv
= send_command(path
, "get usage all", args
, &res
);
509 printf("show-mp(): failed to send message to kernel\n");
513 reslist
= prop_dictionary_get(res
, "returned data");
514 if (reslist
== NULL
) {
515 printf("show_mp(): failed to get array of results");
520 iter
= prop_array_iterator(reslist
);
522 printf("show_mp(): failed to create iterator\n");
527 while ((item
= prop_object_iterator_next(iter
)) != NULL
) {
528 rv
= prop_dictionary_get_uint64(item
, "space used", &space
);
529 rv
= prop_dictionary_get_uint64(item
, "limit", &limit
);
530 if (prop_dictionary_get_uint32(item
, "uid", &id
))
532 else if (prop_dictionary_get_uint32(item
, "gid", &id
))
537 humanize_number(hbuf
, sizeof(hbuf
), space
, "", HN_AUTOSCALE
, HN_NOSPACE
);
540 printf(" %"PRIu64
, space
);
547 humanize_number(hbuf
, sizeof(hbuf
), limit
, "", HN_AUTOSCALE
, HN_NOSPACE
);
548 printf(", limit = %s\n", hbuf
);
550 printf(", limit = %"PRIu64
"\n", limit
);
553 prop_object_iterator_release(iter
);
556 prop_object_release(args
);
557 prop_object_release(res
);
561 /* sync the in-kernel counters to the actual file system usage */
562 static int cmd_sync(char *dirname
)
564 prop_dictionary_t res
, item
;
566 struct ac_unode
*unp
;
567 struct ac_gnode
*gnp
;
570 args
= prop_array_create();
572 printf("cmd_sync(): couldn't create args dictionary\n");
573 res
= prop_dictionary_create();
575 printf("cmd_sync(): couldn't create res dictionary\n");
577 rv
= get_dirsize(dirname
);
579 item
= prop_dictionary_create();
581 printf("cmd_sync(): couldn't create item dictionary\n");
582 (void) prop_dictionary_set_uint64(item
, "space used", global_size
);
583 prop_array_add_and_rel(args
, item
);
585 RB_FOREACH(unp
, ac_utree
, &ac_uroot
) {
586 for (i
=0; i
<ACCT_CHUNK_NIDS
; i
++) {
587 if (unp
->uid_chunk
[i
].space
!= 0) {
588 item
= prop_dictionary_create();
589 (void) prop_dictionary_set_uint32(item
, "uid",
590 (unp
->left_bits
<< ACCT_CHUNK_BITS
) + i
);
591 (void) prop_dictionary_set_uint64(item
, "space used",
592 unp
->uid_chunk
[i
].space
);
593 prop_array_add_and_rel(args
, item
);
597 RB_FOREACH(gnp
, ac_gtree
, &ac_groot
) {
598 for (i
=0; i
<ACCT_CHUNK_NIDS
; i
++) {
599 if (gnp
->gid_chunk
[i
].space
!= 0) {
600 item
= prop_dictionary_create();
601 (void) prop_dictionary_set_uint32(item
, "gid",
602 (gnp
->left_bits
<< ACCT_CHUNK_BITS
) + i
);
603 (void) prop_dictionary_set_uint64(item
, "space used",
604 gnp
->gid_chunk
[i
].space
);
605 prop_array_add_and_rel(args
, item
);
610 if (send_command(dirname
, "set usage all", args
, &res
) == false) {
611 printf("Failed to send message to kernel\n");
615 prop_object_release(args
);
616 prop_object_release(res
);
622 cmd_limit(char *dirname
, uint64_t limit
)
624 prop_dictionary_t res
, args
;
627 args
= prop_dictionary_create();
629 printf("cmd_limit(): couldn't create args dictionary\n");
630 res
= prop_dictionary_create();
632 printf("cmd_limit(): couldn't create res dictionary\n");
634 (void) prop_dictionary_set_uint64(args
, "limit", limit
);
636 if (send_command(dirname
, "set limit", args
, &res
) == false) {
637 printf("Failed to send message to kernel\n");
641 prop_object_release(args
);
642 prop_object_release(res
);
648 cmd_limit_uid(char *dirname
, uid_t uid
, uint64_t limit
)
650 prop_dictionary_t res
, args
;
653 args
= prop_dictionary_create();
655 printf("cmd_limit_uid(): couldn't create args dictionary\n");
656 res
= prop_dictionary_create();
658 printf("cmd_limit_uid(): couldn't create res dictionary\n");
660 (void) prop_dictionary_set_uint32(args
, "uid", uid
);
661 (void) prop_dictionary_set_uint64(args
, "limit", limit
);
663 if (send_command(dirname
, "set limit uid", args
, &res
) == false) {
664 printf("Failed to send message to kernel\n");
668 prop_object_release(args
);
669 prop_object_release(res
);
675 cmd_limit_gid(char *dirname
, gid_t gid
, uint64_t limit
)
677 prop_dictionary_t res
, args
;
680 args
= prop_dictionary_create();
682 printf("cmd_limit_gid(): couldn't create args dictionary\n");
683 res
= prop_dictionary_create();
685 printf("cmd_limit_gid(): couldn't create res dictionary\n");
687 (void) prop_dictionary_set_uint32(args
, "gid", gid
);
688 (void) prop_dictionary_set_uint64(args
, "limit", limit
);
690 if (send_command(dirname
, "set limit gid", args
, &res
) == false) {
691 printf("Failed to send message to kernel\n");
695 prop_object_release(args
);
696 prop_object_release(res
);
702 main(int argc
, char **argv
)
707 while ((ch
= getopt(argc
, argv
, "Dhn")) != -1) {
716 flag_resolve_ids
= 0;
725 if (strcmp(argv
[0], "check") == 0) {
728 return cmd_check(argv
[1]);
730 if (strcmp(argv
[0], "lsfs") == 0) {
733 if (strcmp(argv
[0], "limit") == 0) {
736 if (dehumanize_number(argv
[2], &limit
) < 0)
737 err(1, "bad number for option: %s", argv
[2]);
739 return cmd_limit(argv
[1], limit
);
741 if (strcmp(argv
[0], "show") == 0) {
744 return show_mp(argv
[1]);
746 if (strcmp(argv
[0], "sync") == 0) {
749 return cmd_sync(argv
[1]);
751 if (strcmp(argv
[0], "ulim") == 0) {
755 if ((pwd
= getpwnam(argv
[2])) == NULL
)
756 errx(1, "%s: no such user", argv
[2]);
757 if (dehumanize_number(argv
[3], &limit
) < 0)
758 err(1, "bad number for option: %s", argv
[2]);
760 return cmd_limit_uid(argv
[1], pwd
->pw_uid
, limit
);
762 if (strcmp(argv
[0], "glim") == 0) {
766 if ((grp
= getgrnam(argv
[2])) == NULL
)
767 errx(1, "%s: no such group", argv
[2]);
768 if (dehumanize_number(argv
[3], &limit
) < 0)
769 err(1, "bad number for option: %s", argv
[2]);
771 return cmd_limit_gid(argv
[1], grp
->gr_gid
, limit
);