vquota(8): More style fixes.
[dragonfly.git] / sbin / vquota / vquota.c
CommitLineData
6a4c3e18
FT
1/*
2 * Copyright (c) 2011 François Tigeot <ftigeot@wolfpond.org>
3 * All rights reserved.
a3dce641 4 *
6a4c3e18
FT
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
a3dce641 8 *
6a4c3e18
FT
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.
a3dce641 18 *
6a4c3e18
FT
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>
a3dce641 34#include <sys/mount.h>
b4d6d8bb 35#include <sys/vfs_quota.h>
6a4c3e18
FT
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <err.h>
40#include <string.h>
41#include <fts.h>
b4d6d8bb
FT
42#include <libprop/proplib.h>
43#include <unistd.h>
49e10979
FT
44#include <sys/tree.h>
45#include <errno.h>
50c7dea7 46#include <inttypes.h>
6a4c3e18 47
b4d6d8bb
FT
48static bool flag_debug = 0;
49
6a4c3e18 50static void usage(int);
cefc2cb0 51static int get_dirsize(char *);
a3dce641 52static int get_fslist(void);
6a4c3e18 53
cefc2cb0
SW
54static void
55usage(int retcode)
56{
b4d6d8bb
FT
57 fprintf(stderr, "usage: vquota [-D] check directory\n");
58 fprintf(stderr, " vquota [-D] lsfs\n");
59 fprintf(stderr, " vquota [-D] show mount_point\n");
6a4c3e18
FT
60 exit(retcode);
61}
62
49e10979
FT
63/*
64 * Inode numbers with more than one hard link often come in groups;
65 * use linear arrays of 1024 ones as the basic unit of allocation.
66 * We only need to check if the inodes have been previously processed,
67 * bit arrays are perfect for that purpose.
68 */
69#define HL_CHUNK_BITS 10
70#define HL_CHUNK_ENTRIES (1<<HL_CHUNK_BITS)
71#define HL_CHUNK_MASK (HL_CHUNK_ENTRIES - 1)
72#define BA_UINT64_BITS 6
73#define BA_UINT64_ENTRIES (1<<BA_UINT64_BITS)
74#define BA_UINT64_MASK (BA_UINT64_ENTRIES - 1)
75
76struct hl_node {
77 RB_ENTRY(hl_node) rb_entry;
78 ino_t ino_left_bits;
79 uint64_t hl_chunk[HL_CHUNK_ENTRIES/64];
80};
81
82RB_HEAD(hl_tree,hl_node) hl_root;
83
84RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
85
86static int
87rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
88
89RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
90
91struct hl_node* hl_node_insert(ino_t);
92
93
94static int
95rb_hl_node_cmp(struct hl_node *a, struct hl_node *b)
96{
97 if (a->ino_left_bits < b->ino_left_bits)
98 return(-1);
99 else if (a->ino_left_bits > b->ino_left_bits)
100 return(1);
101 return(0);
102}
103
104struct hl_node* hl_node_insert(ino_t inode)
105{
106 struct hl_node *hlp, *res;
107
108 hlp = malloc(sizeof(struct hl_node));
109 if (hlp == NULL) {
110 /* shouldn't happen */
111 printf("hl_node_insert(): malloc failed\n");
112 exit(ENOMEM);
113 }
114 bzero(hlp, sizeof(struct hl_node));
115
116 hlp->ino_left_bits = (inode >> HL_CHUNK_BITS);
117 res = RB_INSERT(hl_tree, &hl_root, hlp);
118
119 if (res != NULL) /* shouldn't happen */
120 printf("hl_node_insert(): RB_INSERT didn't return NULL\n");
121
122 return hlp;
123}
124
125/*
126 * hl_register: register an inode number in a rb-tree of bit arrays
127 * returns:
128 * - true if the inode was already processed
129 * - false otherwise
130 */
131static bool
132hl_register(ino_t inode)
133{
134 struct hl_node hl_find, *hlp;
135 uint64_t ino_right_bits, ba_index, ba_offset;
136 uint64_t bitmask, bitval;
137 bool retval = false;
138
139 /* calculate the different addresses of the wanted bit */
140 hl_find.ino_left_bits = (inode >> HL_CHUNK_BITS);
141
142 ino_right_bits = inode & HL_CHUNK_MASK;
143 ba_index = ino_right_bits >> BA_UINT64_BITS;
144 ba_offset = ino_right_bits & BA_UINT64_MASK;
145
146 /* no existing node? create and initialize it */
147 if ((hlp = RB_FIND(hl_tree, &hl_root, &hl_find)) == NULL) {
148 hlp = hl_node_insert(inode);
149 }
150
151 /* node was found, check the bit value */
152 bitmask = 1 << ba_offset;
153 bitval = hlp->hl_chunk[ba_index] & bitmask;
154 if (bitval != 0) {
155 retval = true;
156 }
157
158 /* set the bit */
159 hlp->hl_chunk[ba_index] |= bitmask;
160
161 return retval;
162}
163
f236a458
FT
164/* storage for collected uid numbers */
165/* FIXME: same data structures used in kernel, should find a way to
166 * deduplicate this code */
167
168static int
169rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
170static int
171rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
172
173RB_HEAD(ac_utree,ac_unode) ac_uroot;
174RB_HEAD(ac_gtree,ac_gnode) ac_groot;
175RB_PROTOTYPE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
176RB_PROTOTYPE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
177RB_GENERATE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
178RB_GENERATE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
179
180static int
181rb_ac_unode_cmp(struct ac_unode *a, struct ac_unode *b)
182{
183 if (a->left_bits < b->left_bits)
184 return(-1);
185 else if (a->left_bits > b->left_bits)
186 return(1);
187 return(0);
188}
189
190static int
191rb_ac_gnode_cmp(struct ac_gnode *a, struct ac_gnode *b)
192{
193 if (a->left_bits < b->left_bits)
194 return(-1);
195 else if (a->left_bits > b->left_bits)
196 return(1);
197 return(0);
198}
199
200static struct ac_unode*
201unode_insert(uid_t uid)
202{
203 struct ac_unode *unp, *res;
204
205 unp = malloc(sizeof(struct ac_unode));
206 if (unp == NULL) {
207 printf("unode_insert(): malloc failed\n");
c89ff6a6 208 exit(ENOMEM);
f236a458
FT
209 }
210 bzero(unp, sizeof(struct ac_unode));
211
212 unp->left_bits = (uid >> ACCT_CHUNK_BITS);
213 res = RB_INSERT(ac_utree, &ac_uroot, unp);
214
215 if (res != NULL) /* shouldn't happen */
216 printf("unode_insert(): RB_INSERT didn't return NULL\n");
217
218 return unp;
219}
220
221static struct ac_gnode*
222gnode_insert(gid_t gid)
223{
224 struct ac_gnode *gnp, *res;
225
226 gnp = malloc(sizeof(struct ac_gnode));
227 if (gnp == NULL) {
228 printf("gnode_insert(): malloc failed\n");
c89ff6a6 229 exit(ENOMEM);
f236a458
FT
230 }
231 bzero(gnp, sizeof(struct ac_gnode));
232
233 gnp->left_bits = (gid >> ACCT_CHUNK_BITS);
234 res = RB_INSERT(ac_gtree, &ac_groot, gnp);
235
236 if (res != NULL) /* shouldn't happen */
237 printf("gnode_insert(): RB_INSERT didn't return NULL\n");
238
239 return gnp;
240}
241
cefc2cb0
SW
242static int
243get_dirsize(char* dirname)
244{
245 FTS *fts;
6a4c3e18
FT
246 FTSENT *p;
247 char* fts_args[2];
49e10979 248 uint64_t global_size = 0;
cefc2cb0 249 int retval = 0;
6a4c3e18 250
49e10979
FT
251 /* what we need */
252 ino_t file_inode;
253 off_t file_size;
254 uid_t file_uid;
255 gid_t file_gid;
256
f236a458
FT
257 struct ac_unode *unp, ufind;
258 struct ac_gnode *gnp, gfind;
259 int i;
260
6a4c3e18
FT
261 /* TODO: check directory name sanity */
262 fts_args[0] = dirname;
263 fts_args[1] = NULL;
264
49e10979 265 if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
6a4c3e18
FT
266 err(1, "fts_open() failed");
267
268 while ((p = fts_read(fts)) != NULL) {
269 switch (p->fts_info) {
270 /* directories, ignore them */
271 case FTS_D:
272 case FTS_DC:
273 case FTS_DP:
274 break;
275 /* read errors, warn, continue and flag */
276 case FTS_DNR:
277 case FTS_ERR:
278 case FTS_NS:
279 warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
280 retval = 1;
281 break;
282 default:
49e10979
FT
283 file_inode = p->fts_statp->st_ino;
284 file_size = p->fts_statp->st_size;
285 file_uid = p->fts_statp->st_uid;
286 file_gid = p->fts_statp->st_gid;
287
f236a458
FT
288 /* files with more than one hard link: */
289 /* process them only once */
290 if (p->fts_statp->st_nlink > 1)
291 if (hl_register(file_inode) == false)
292 break;
293
294 global_size += file_size;
295 ufind.left_bits = (file_uid >> ACCT_CHUNK_BITS);
296 gfind.left_bits = (file_gid >> ACCT_CHUNK_BITS);
297 if ((unp = RB_FIND(ac_utree, &ac_uroot, &ufind)) == NULL)
298 unp = unode_insert(file_uid);
299 if ((gnp = RB_FIND(ac_gtree, &ac_groot, &gfind)) == NULL)
300 gnp = gnode_insert(file_gid);
301 unp->uid_chunk[(file_uid & ACCT_CHUNK_MASK)] += file_size;
302 gnp->gid_chunk[(file_gid & ACCT_CHUNK_MASK)] += file_size;
6a4c3e18
FT
303 }
304 }
a89ecd20 305 fts_close(fts);
6a4c3e18 306
f236a458
FT
307 printf("total: %"PRIu64"\n", global_size);
308 RB_FOREACH(unp, ac_utree, &ac_uroot) {
309 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
310 if (unp->uid_chunk[i] != 0) {
311 printf("uid %"PRIu32": %"PRIu64"\n",
c89ff6a6
SW
312 (unp->left_bits << ACCT_CHUNK_BITS) + i,
313 unp->uid_chunk[i]);
f236a458
FT
314 }
315
316 }
317 }
318 RB_FOREACH(gnp, ac_gtree, &ac_groot) {
319 for (i=0; i<ACCT_CHUNK_NIDS; i++) {
320 if (gnp->gid_chunk[i] != 0) {
321 printf("gid %"PRIu32": %"PRIu64"\n",
c89ff6a6
SW
322 (gnp->left_bits << ACCT_CHUNK_BITS) + i,
323 gnp->gid_chunk[i]);
f236a458
FT
324 }
325 }
326 }
327
6a4c3e18
FT
328 return retval;
329}
330
a3dce641 331/* print a list of filesystems with accounting enabled */
c89ff6a6
SW
332static int
333get_fslist(void)
334{
a3dce641
FT
335 struct statfs *mntbufp;
336 int nloc, i;
337
338 /* read mount table from kernel */
339 nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
340 if (nloc <= 0) {
341 perror("getmntinfo");
342 exit(1);
343 }
344
345 /* iterate mounted filesystems */
346 for (i=0; i<nloc; i++) {
347 /* vfs accounting enabled on this one ? */
348 if (mntbufp[i].f_flags & MNT_ACCOUNTING)
349 printf("%s on %s\n", mntbufp[i].f_mntfromname,
c89ff6a6 350 mntbufp[i].f_mntonname);
a3dce641
FT
351 }
352
353 return 0;
354}
355
b4d6d8bb
FT
356static bool
357send_command(const char *path, const char *cmd,
c89ff6a6
SW
358 prop_dictionary_t args, prop_dictionary_t *res)
359{
b4d6d8bb
FT
360 prop_dictionary_t dict;
361 struct plistref pref;
362
363 bool rv;
364 int error;
365
366 dict = prop_dictionary_create();
367
368 if (dict == NULL) {
369 printf("send_command(): couldn't create dictionary\n");
370 return false;
371 }
372
373 rv = prop_dictionary_set_cstring(dict, "command", cmd);
374 if (rv== false) {
375 printf("send_command(): couldn't initialize dictionary\n");
376 return false;
377 }
378
379 rv = prop_dictionary_set(dict, "arguments", args);
380 if (rv == false) {
381 printf("prop_dictionary_set() failed\n");
382 return false;
383 }
384
385 error = prop_dictionary_send_syscall(dict, &pref);
386 if (error != 0) {
387 printf("prop_dictionary_send_syscall() failed\n");
388 prop_object_release(dict);
389 return false;
390 }
391
392 if (flag_debug)
c89ff6a6
SW
393 printf("Message to kernel:\n%s\n",
394 prop_dictionary_externalize(dict));
b4d6d8bb
FT
395
396 error = vquotactl(path, &pref);
397 if (error != 0) {
398 printf("send_command: vquotactl = %d\n", error);
399 return false;
400 }
401
402 error = prop_dictionary_recv_syscall(&pref, res);
403 if (error != 0) {
404 printf("prop_dictionary_recv_syscall() failed\n");
405 }
406
407 if (flag_debug)
c89ff6a6
SW
408 printf("Message from kernel:\n%s\n",
409 prop_dictionary_externalize(*res));
b4d6d8bb
FT
410
411 return true;
412}
413
414/* show collected statistics on mount point */
c89ff6a6
SW
415static int
416show_mp(char *path)
417{
b4d6d8bb
FT
418 prop_dictionary_t args, res;
419 prop_array_t reslist;
420 bool rv;
421 prop_object_iterator_t iter;
422 prop_dictionary_t item;
423 uint32_t id;
424 uint64_t space;
425
426 args = prop_dictionary_create();
427 res = prop_dictionary_create();
428 if (args == NULL)
429 printf("couldn't create args dictionary\n");
430
431 rv = send_command(path, "get usage all", args, &res);
432 if (rv == false) {
433 printf("show-mp(): failed to send message to kernel\n");
434 goto end;
435 }
436
437 reslist = prop_dictionary_get(res, "get usage all");
438 if (reslist == NULL) {
439 printf("show_mp(): failed to get array of results");
440 rv = false;
441 goto end;
442 }
443
444 iter = prop_array_iterator(reslist);
445 if (iter == NULL) {
446 printf("show_mp(): failed to create iterator\n");
447 rv = false;
448 goto end;
449 }
450
451 while ((item = prop_object_iterator_next(iter)) != NULL) {
452 rv = prop_dictionary_get_uint64(item, "space used", &space);
453 if (prop_dictionary_get_uint32(item, "uid", &id))
454 printf("uid %u:", id);
455 else if (prop_dictionary_get_uint32(item, "gid", &id))
456 printf("gid %u:", id);
457 else
458 printf("total space used");
c89ff6a6 459 printf(" %"PRIu64"\n", space);
b4d6d8bb
FT
460 }
461 prop_object_iterator_release(iter);
462
463end:
464 prop_object_release(args);
465 prop_object_release(res);
466 return (rv == true);
467}
468
cefc2cb0 469int
c89ff6a6
SW
470main(int argc, char **argv)
471{
b4d6d8bb
FT
472 int ch;
473
474 while ((ch = getopt(argc, argv, "D")) != -1) {
475 switch(ch) {
476 case 'D':
477 flag_debug = 1;
478 break;
479 }
480 }
481 argc -= optind;
482 argv += optind;
483 if (argc < 1)
6a4c3e18
FT
484 usage(1);
485
b4d6d8bb
FT
486 if (strcmp(argv[0], "check") == 0) {
487 if (argc != 2)
a3dce641 488 usage(1);
b4d6d8bb 489 return get_dirsize(argv[1]);
a3dce641 490 }
b4d6d8bb 491 if (strcmp(argv[0], "lsfs") == 0) {
a3dce641 492 return get_fslist();
6a4c3e18 493 }
b4d6d8bb
FT
494 if (strcmp(argv[0], "show") == 0) {
495 if (argc != 2)
496 usage(1);
497 return show_mp(argv[1]);
498 }
6a4c3e18 499
b4d6d8bb 500 usage(0);
6a4c3e18 501}