xref: /dragonfly/sbin/vquota/vquota.c (revision 2b3f93ea)
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] glim  mount_point group size\n");
68 	fprintf(stderr, "       vquota [-Dhn] show mount_point\n");
69 	fprintf(stderr, "       vquota [-Dhn] sync mount_point\n");
70 	exit(retcode);
71 }
72 
73 /*
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.
78  */
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)
85 
86 struct hl_node {
87 	RB_ENTRY(hl_node)	rb_entry;
88 	ino_t			ino_left_bits;
89 	uint64_t		hl_chunk[HL_CHUNK_ENTRIES/64];
90 };
91 
92 RB_HEAD(hl_tree,hl_node)	hl_root;
93 
94 RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
95 
96 static int
97 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
98 
99 RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
100 
101 struct hl_node* hl_node_insert(ino_t);
102 
103 
104 static int
105 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b)
106 {
107 	if (a->ino_left_bits < b->ino_left_bits)
108 		return(-1);
109 	else if (a->ino_left_bits > b->ino_left_bits)
110 		return(1);
111 	return(0);
112 }
113 
114 struct hl_node* hl_node_insert(ino_t inode)
115 {
116 	struct hl_node *hlp, *res;
117 
118 	hlp = malloc(sizeof(struct hl_node));
119 	if (hlp == NULL) {
120 		/* shouldn't happen */
121 		printf("hl_node_insert(): malloc failed\n");
122 		exit(ENOMEM);
123 	}
124 	bzero(hlp, sizeof(struct hl_node));
125 
126 	hlp->ino_left_bits = (inode >> HL_CHUNK_BITS);
127 	res = RB_INSERT(hl_tree, &hl_root, hlp);
128 
129 	if (res != NULL)	/* shouldn't happen */
130 		printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
131 
132 	return hlp;
133 }
134 
135 /*
136  * hl_register: register an inode number in a rb-tree of bit arrays
137  * returns:
138  * - true if the inode was already processed
139  * - false otherwise
140  */
141 static bool
142 hl_register(ino_t inode)
143 {
144 	struct hl_node hl_find, *hlp;
145 	uint64_t ino_right_bits, ba_index, ba_offset;
146 	uint64_t bitmask, bitval;
147 	bool retval = false;
148 
149 	/* calculate the different addresses of the wanted bit */
150 	hl_find.ino_left_bits = (inode >> HL_CHUNK_BITS);
151 
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;
155 
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);
159 	}
160 
161 	/* node was found, check the bit value */
162 	bitmask = 1 << ba_offset;
163 	bitval = hlp->hl_chunk[ba_index] & bitmask;
164 	if (bitval != 0) {
165 		retval = true;
166 	}
167 
168 	/* set the bit */
169 	hlp->hl_chunk[ba_index] |= bitmask;
170 
171 	return retval;
172 }
173 
174 /* global variable used by get_dir_size() */
175 uint64_t global_size;
176 
177 /* storage for collected id numbers */
178 /* FIXME: same data structures used in kernel, should find a way to
179  * deduplicate this code */
180 
181 static int
182 rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
183 static int
184 rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
185 
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);
192 
193 static int
194 rb_ac_unode_cmp(struct ac_unode *a, struct ac_unode *b)
195 {
196 	if (a->left_bits < b->left_bits)
197 		return(-1);
198 	else if (a->left_bits > b->left_bits)
199 		return(1);
200 	return(0);
201 }
202 
203 static int
204 rb_ac_gnode_cmp(struct ac_gnode *a, struct ac_gnode *b)
205 {
206 	if (a->left_bits < b->left_bits)
207 		return(-1);
208 	else if (a->left_bits > b->left_bits)
209 		return(1);
210 	return(0);
211 }
212 
213 static struct ac_unode*
214 unode_insert(uid_t uid)
215 {
216 	struct ac_unode *unp, *res;
217 
218 	unp = malloc(sizeof(struct ac_unode));
219 	if (unp == NULL) {
220 		printf("unode_insert(): malloc failed\n");
221 		exit(ENOMEM);
222 	}
223 	bzero(unp, sizeof(struct ac_unode));
224 
225 	unp->left_bits = (uid >> ACCT_CHUNK_BITS);
226 	res = RB_INSERT(ac_utree, &ac_uroot, unp);
227 
228 	if (res != NULL)	/* shouldn't happen */
229 		printf("unode_insert(): RB_INSERT didn't return NULL\n");
230 
231 	return unp;
232 }
233 
234 static struct ac_gnode*
235 gnode_insert(gid_t gid)
236 {
237 	struct ac_gnode *gnp, *res;
238 
239 	gnp = malloc(sizeof(struct ac_gnode));
240 	if (gnp == NULL) {
241 		printf("gnode_insert(): malloc failed\n");
242 		exit(ENOMEM);
243 	}
244 	bzero(gnp, sizeof(struct ac_gnode));
245 
246 	gnp->left_bits = (gid >> ACCT_CHUNK_BITS);
247 	res = RB_INSERT(ac_gtree, &ac_groot, gnp);
248 
249 	if (res != NULL)	/* shouldn't happen */
250 		printf("gnode_insert(): RB_INSERT didn't return NULL\n");
251 
252 	return gnp;
253 }
254 
255 /*
256  * get_dirsize(): walks a directory tree in the same filesystem
257  * output:
258  * - global rb-trees ac_uroot and ac_groot
259  * - global variable global_size
260  */
261 static int
262 get_dirsize(char* dirname)
263 {
264 	FTS		*fts;
265 	FTSENT		*p;
266 	char*		fts_args[2];
267 	int		retval = 0;
268 
269 	/* what we need */
270 	ino_t		file_inode;
271 	off_t		file_size;
272 	uid_t		file_uid;
273 	gid_t		file_gid;
274 
275 	struct ac_unode *unp, ufind;
276 	struct ac_gnode *gnp, gfind;
277 
278 	/* TODO: check directory name sanity */
279 	fts_args[0] = dirname;
280 	fts_args[1] = NULL;
281 
282 	if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
283 		err(1, "fts_open() failed");
284 
285 	while ((p = fts_read(fts)) != NULL) {
286 		switch (p->fts_info) {
287 		/* directories, ignore them */
288 		case FTS_D:
289 		case FTS_DC:
290 		case FTS_DP:
291 			break;
292 		/* read errors, warn, continue and flag */
293 		case FTS_DNR:
294 		case FTS_ERR:
295 		case FTS_NS:
296 			warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
297 			retval = 1;
298 			break;
299 		default:
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;
304 
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))
309 					break;
310 
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;
320 		}
321 	}
322 	fts_close(fts);
323 
324 	return retval;
325 }
326 
327 static void
328 print_user(uid_t uid)
329 {
330 	struct passwd *pw;
331 
332 	if (flag_resolve_ids && ((pw = getpwuid(uid)) != NULL)) {
333 		printf("user %s:", pw->pw_name);
334 	} else {
335 		printf("uid %u:", uid);
336 	}
337 }
338 
339 static void
340 print_group(gid_t gid)
341 {
342 	struct group *gr;
343 
344 	if (flag_resolve_ids && ((gr = getgrgid(gid)) != NULL)) {
345 		printf("group %s:", gr->gr_name);
346 	} else {
347 		printf("gid %u:", gid);
348 	}
349 }
350 
351 static int
352 cmd_check(char* dirname)
353 {
354 	int32_t uid, gid;
355 	char	hbuf[5];
356 	struct  ac_unode *unp;
357 	struct  ac_gnode *gnp;
358 	int	rv, i;
359 
360 	rv = get_dirsize(dirname);
361 
362 	if (flag_humanize) {
363 		humanize_number(hbuf, sizeof(hbuf), global_size, "",
364 		    HN_AUTOSCALE, HN_NOSPACE);
365 		printf("total: %s\n", hbuf);
366 	} else {
367 		printf("total: %"PRIu64"\n", global_size);
368 	}
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;
373 		    print_user(uid);
374 		    if (flag_humanize) {
375 			humanize_number(hbuf, sizeof(hbuf),
376 			unp->uid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
377 			printf(" %s\n", hbuf);
378 		    } else {
379 			printf(" %" PRIu64 "\n", unp->uid_chunk[i].space);
380 		    }
381 		}
382 	    }
383 	}
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;
388 		    print_group(gid);
389 		    if (flag_humanize) {
390 			humanize_number(hbuf, sizeof(hbuf),
391 			gnp->gid_chunk[i].space, "", HN_AUTOSCALE, HN_NOSPACE);
392 			printf(" %s\n", hbuf);
393 		    } else {
394 			printf(" %" PRIu64 "\n", gnp->gid_chunk[i].space);
395 		    }
396 		}
397 	    }
398 	}
399 
400 	return rv;
401 }
402 
403 /* print a list of filesystems with accounting enabled */
404 static int
405 get_fslist(void)
406 {
407 	struct statfs *mntbufp;
408 	int nloc, i;
409 
410 	/* read mount table from kernel */
411 	nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
412 	if (nloc <= 0) {
413 		perror("getmntinfo");
414 		exit(1);
415 	}
416 
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);
423 	}
424 
425 	return 0;
426 }
427 
428 static bool
429 send_command(const char *path, const char *cmd,
430 		prop_object_t args, prop_dictionary_t *res)
431 {
432 	prop_dictionary_t dict;
433 	struct plistref pref;
434 
435 	bool rv;
436 	int error;
437 
438 	dict = prop_dictionary_create();
439 
440 	if (dict == NULL) {
441 		printf("send_command(): couldn't create dictionary\n");
442 		return false;
443 	}
444 
445 	rv = prop_dictionary_set_cstring(dict, "command", cmd);
446 	if (rv== false) {
447 		printf("send_command(): couldn't initialize dictionary\n");
448 		return false;
449 	}
450 
451 	rv = prop_dictionary_set(dict, "arguments", args);
452 	if (rv == false) {
453 		printf("prop_dictionary_set() failed\n");
454 		return false;
455 	}
456 
457 	error = prop_dictionary_send_syscall(dict, &pref);
458 	if (error != 0) {
459 		printf("prop_dictionary_send_syscall() failed\n");
460 		prop_object_release(dict);
461 		return false;
462 	}
463 
464 	if (flag_debug)
465 		printf("Message to kernel:\n%s\n",
466 		    prop_dictionary_externalize(dict));
467 
468 	error = vquotactl(path, &pref);
469 	if (error != 0) {
470 		printf("send_command: vquotactl = %d\n", error);
471 		return false;
472 	}
473 
474 	error = prop_dictionary_recv_syscall(&pref, res);
475 	if (error != 0) {
476 		printf("prop_dictionary_recv_syscall() failed\n");
477 	}
478 
479 	if (flag_debug)
480 		printf("Message from kernel:\n%s\n",
481 		    prop_dictionary_externalize(*res));
482 
483 	return true;
484 }
485 
486 /* show collected statistics on mount point */
487 static int
488 show_mp(char *path)
489 {
490 	prop_dictionary_t args, res;
491 	prop_array_t reslist;
492 	bool rv;
493 	prop_object_iterator_t	iter;
494 	prop_dictionary_t item;
495 	uint32_t id;
496 	uint64_t space, limit=0;
497 	char hbuf[5];
498 
499 	args = prop_dictionary_create();
500 	res  = prop_dictionary_create();
501 	if (args == NULL)
502 		printf("show_mp(): couldn't create args dictionary\n");
503 	res  = prop_dictionary_create();
504 	if (res == NULL)
505 		printf("show_mp(): couldn't create res dictionary\n");
506 
507 	rv = send_command(path, "get usage all", args, &res);
508 	if (rv == false) {
509 		printf("show-mp(): failed to send message to kernel\n");
510 		goto end;
511 	}
512 
513 	reslist = prop_dictionary_get(res, "returned data");
514 	if (reslist == NULL) {
515 		printf("show_mp(): failed to get array of results");
516 		rv = false;
517 		goto end;
518 	}
519 
520 	iter = prop_array_iterator(reslist);
521 	if (iter == NULL) {
522 		printf("show_mp(): failed to create iterator\n");
523 		rv = false;
524 		goto end;
525 	}
526 
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))
531 			print_user(id);
532 		else if (prop_dictionary_get_uint32(item, "gid", &id))
533 			print_group(id);
534 		else
535 			printf("total:");
536 		if (flag_humanize) {
537 			humanize_number(hbuf, sizeof(hbuf), space, "", HN_AUTOSCALE, HN_NOSPACE);
538 			printf(" %s", hbuf);
539 		} else {
540 			printf(" %"PRIu64, space);
541 		}
542 		if (limit == 0) {
543 			printf("\n");
544 			continue;
545 		}
546 		if (flag_humanize) {
547 			humanize_number(hbuf, sizeof(hbuf), limit, "", HN_AUTOSCALE, HN_NOSPACE);
548 			printf(", limit = %s\n", hbuf);
549 		} else {
550 			printf(", limit = %"PRIu64"\n", limit);
551 		}
552 	}
553 	prop_object_iterator_release(iter);
554 
555 end:
556 	prop_object_release(args);
557 	prop_object_release(res);
558 	return (rv == true);
559 }
560 
561 /* sync the in-kernel counters to the actual file system usage */
562 static int cmd_sync(char *dirname)
563 {
564 	prop_dictionary_t res, item;
565 	prop_array_t args;
566 	struct ac_unode *unp;
567 	struct ac_gnode *gnp;
568 	int rv = 0, i;
569 
570 	args = prop_array_create();
571 	if (args == NULL)
572 		printf("cmd_sync(): couldn't create args dictionary\n");
573 	res  = prop_dictionary_create();
574 	if (res == NULL)
575 		printf("cmd_sync(): couldn't create res dictionary\n");
576 
577 	rv = get_dirsize(dirname);
578 
579 	item = prop_dictionary_create();
580 	if (item == NULL)
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);
584 
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);
594 		}
595 	    }
596 	}
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);
606 		}
607 	    }
608 	}
609 
610 	if (send_command(dirname, "set usage all", args, &res) == false) {
611 		printf("Failed to send message to kernel\n");
612 		rv = 1;
613 	}
614 
615 	prop_object_release(args);
616 	prop_object_release(res);
617 
618 	return rv;
619 }
620 
621 static int
622 cmd_limit(char *dirname, uint64_t limit)
623 {
624 	prop_dictionary_t res, args;
625 	int rv = 0;
626 
627 	args = prop_dictionary_create();
628 	if (args == NULL)
629 		printf("cmd_limit(): couldn't create args dictionary\n");
630 	res  = prop_dictionary_create();
631 	if (res == NULL)
632 		printf("cmd_limit(): couldn't create res dictionary\n");
633 
634 	(void) prop_dictionary_set_uint64(args, "limit", limit);
635 
636 	if (send_command(dirname, "set limit", args, &res) == false) {
637 		printf("Failed to send message to kernel\n");
638 		rv = 1;
639 	}
640 
641 	prop_object_release(args);
642 	prop_object_release(res);
643 
644 	return rv;
645 }
646 
647 static int
648 cmd_limit_uid(char *dirname, uid_t uid, uint64_t limit)
649 {
650 	prop_dictionary_t res, args;
651 	int rv = 0;
652 
653 	args = prop_dictionary_create();
654 	if (args == NULL)
655 		printf("cmd_limit_uid(): couldn't create args dictionary\n");
656 	res  = prop_dictionary_create();
657 	if (res == NULL)
658 		printf("cmd_limit_uid(): couldn't create res dictionary\n");
659 
660 	(void) prop_dictionary_set_uint32(args, "uid", uid);
661 	(void) prop_dictionary_set_uint64(args, "limit", limit);
662 
663 	if (send_command(dirname, "set limit uid", args, &res) == false) {
664 		printf("Failed to send message to kernel\n");
665 		rv = 1;
666 	}
667 
668 	prop_object_release(args);
669 	prop_object_release(res);
670 
671 	return rv;
672 }
673 
674 static int
675 cmd_limit_gid(char *dirname, gid_t gid, uint64_t limit)
676 {
677 	prop_dictionary_t res, args;
678 	int rv = 0;
679 
680 	args = prop_dictionary_create();
681 	if (args == NULL)
682 		printf("cmd_limit_gid(): couldn't create args dictionary\n");
683 	res  = prop_dictionary_create();
684 	if (res == NULL)
685 		printf("cmd_limit_gid(): couldn't create res dictionary\n");
686 
687 	(void) prop_dictionary_set_uint32(args, "gid", gid);
688 	(void) prop_dictionary_set_uint64(args, "limit", limit);
689 
690 	if (send_command(dirname, "set limit gid", args, &res) == false) {
691 		printf("Failed to send message to kernel\n");
692 		rv = 1;
693 	}
694 
695 	prop_object_release(args);
696 	prop_object_release(res);
697 
698 	return rv;
699 }
700 
701 int
702 main(int argc, char **argv)
703 {
704 	int ch;
705 	uint64_t limit;
706 
707 	while ((ch = getopt(argc, argv, "Dhn")) != -1) {
708 		switch(ch) {
709 		case 'D':
710 			flag_debug = 1;
711 			break;
712 		case 'h':
713 			flag_humanize = 1;
714 			break;
715 		case 'n':
716 			flag_resolve_ids = 0;
717 			break;
718 		}
719 	}
720 	argc -= optind;
721 	argv += optind;
722 	if (argc < 1)
723 		usage(1);
724 
725 	if (strcmp(argv[0], "check") == 0) {
726 		if (argc != 2)
727 			usage(1);
728 		return cmd_check(argv[1]);
729 	}
730 	if (strcmp(argv[0], "lsfs") == 0) {
731 		return get_fslist();
732 	}
733 	if (strcmp(argv[0], "limit") == 0) {
734 		if (argc != 3)
735 			usage(1);
736 		if (dehumanize_number(argv[2], &limit) < 0)
737 			err(1, "bad number for option: %s", argv[2]);
738 
739 		return cmd_limit(argv[1], limit);
740 	}
741 	if (strcmp(argv[0], "show") == 0) {
742 		if (argc != 2)
743 			usage(1);
744 		return show_mp(argv[1]);
745 	}
746 	if (strcmp(argv[0], "sync") == 0) {
747 		if (argc != 2)
748 			usage(1);
749 		return cmd_sync(argv[1]);
750 	}
751 	if (strcmp(argv[0], "ulim") == 0) {
752 		struct passwd *pwd;
753 		if (argc != 4)
754 			usage(1);
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]);
759 
760 		return cmd_limit_uid(argv[1], pwd->pw_uid, limit);
761 	}
762 	if (strcmp(argv[0], "glim") == 0) {
763 		struct group *grp;
764 		if (argc != 4)
765 			usage(1);
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]);
770 
771 		return cmd_limit_gid(argv[1], grp->gr_gid, limit);
772 	}
773 
774 	usage(0);
775 }
776