5e13b02097bae992064c526db6f5c273dc8db871
[dragonfly.git] / sbin / vquota / vquota.c
1 /*
2  * Copyright (c) 2011 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
48 static bool flag_debug = 0;
49
50 static void usage(int);
51 static int get_dirsize(char *);
52 static int get_fslist(void);
53
54 static void
55 usage(int retcode)
56 {
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");
60         exit(retcode);
61 }
62
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
76 struct 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
82 RB_HEAD(hl_tree,hl_node)        hl_root;
83
84 RB_PROTOTYPE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
85
86 static int
87 rb_hl_node_cmp(struct hl_node *a, struct hl_node *b);
88
89 RB_GENERATE(hl_tree, hl_node, rb_entry, rb_hl_node_cmp);
90
91 struct hl_node* hl_node_insert(ino_t);
92
93
94 static int
95 rb_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
104 struct 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  */
131 static bool
132 hl_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
164 /* storage for collected uid numbers */
165 /* FIXME: same data structures used in kernel, should find a way to
166  * deduplicate this code */
167
168 static int
169 rb_ac_unode_cmp(struct ac_unode*, struct ac_unode*);
170 static int
171 rb_ac_gnode_cmp(struct ac_gnode*, struct ac_gnode*);
172
173 RB_HEAD(ac_utree,ac_unode) ac_uroot;
174 RB_HEAD(ac_gtree,ac_gnode) ac_groot;
175 RB_PROTOTYPE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
176 RB_PROTOTYPE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
177 RB_GENERATE(ac_utree, ac_unode, rb_entry, rb_ac_unode_cmp);
178 RB_GENERATE(ac_gtree, ac_gnode, rb_entry, rb_ac_gnode_cmp);
179
180 static int
181 rb_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
190 static int
191 rb_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
200 static struct ac_unode*
201 unode_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");
208                 exit(ENOMEM);
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
221 static struct ac_gnode*
222 gnode_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");
229                 exit(ENOMEM);
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
242 static int
243 get_dirsize(char* dirname)
244 {
245         FTS             *fts;
246         FTSENT          *p;
247         char*           fts_args[2];
248         uint64_t        global_size = 0;
249         int             retval = 0;
250
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
257         struct ac_unode *unp, ufind;
258         struct ac_gnode *gnp, gfind;
259         int             i;
260
261         /* TODO: check directory name sanity */
262         fts_args[0] = dirname;
263         fts_args[1] = NULL;
264
265         if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
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:
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
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;
303                 }
304         }
305         fts_close(fts);
306
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",
312                                     (unp->left_bits << ACCT_CHUNK_BITS) + i,
313                                     unp->uid_chunk[i]);
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",
322                                     (gnp->left_bits << ACCT_CHUNK_BITS) + i,
323                                     gnp->gid_chunk[i]);
324                         }
325                 }
326         }
327
328         return retval;
329 }
330
331 /* print a list of filesystems with accounting enabled */
332 static int
333 get_fslist(void)
334 {
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,
350                     mntbufp[i].f_mntonname);
351         }
352
353         return 0;
354 }
355
356 static bool
357 send_command(const char *path, const char *cmd,
358                 prop_dictionary_t args, prop_dictionary_t *res)
359 {
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)
393                 printf("Message to kernel:\n%s\n",
394                     prop_dictionary_externalize(dict));
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)
408                 printf("Message from kernel:\n%s\n",
409                     prop_dictionary_externalize(*res));
410
411         return true;
412 }
413
414 /* show collected statistics on mount point */
415 static int
416 show_mp(char *path)
417 {
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");
459                 printf(" %"PRIu64"\n", space);
460         }
461         prop_object_iterator_release(iter);
462
463 end:
464         prop_object_release(args);
465         prop_object_release(res);
466         return (rv == true);
467 }
468
469 int
470 main(int argc, char **argv)
471 {
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)
484                 usage(1);
485         
486         if (strcmp(argv[0], "check") == 0) {
487                 if (argc != 2)
488                         usage(1);
489                 return get_dirsize(argv[1]);
490         }
491         if (strcmp(argv[0], "lsfs") == 0) {
492                 return get_fslist();
493         }
494         if (strcmp(argv[0], "show") == 0) {
495                 if (argc != 2)
496                         usage(1);
497                 return show_mp(argv[1]);
498         }
499
500         usage(0);
501 }