vquota(8): add code to manage hard links
[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
cefc2cb0
SW
164static int
165get_dirsize(char* dirname)
166{
167 FTS *fts;
6a4c3e18
FT
168 FTSENT *p;
169 char* fts_args[2];
49e10979 170 uint64_t global_size = 0;
cefc2cb0 171 int retval = 0;
6a4c3e18 172
49e10979
FT
173 /* what we need */
174 ino_t file_inode;
175 off_t file_size;
176 uid_t file_uid;
177 gid_t file_gid;
178
6a4c3e18
FT
179 /* TODO: check directory name sanity */
180 fts_args[0] = dirname;
181 fts_args[1] = NULL;
182
49e10979 183 if ((fts = fts_open(fts_args, FTS_PHYSICAL|FTS_XDEV, NULL)) == NULL)
6a4c3e18
FT
184 err(1, "fts_open() failed");
185
186 while ((p = fts_read(fts)) != NULL) {
187 switch (p->fts_info) {
188 /* directories, ignore them */
189 case FTS_D:
190 case FTS_DC:
191 case FTS_DP:
192 break;
193 /* read errors, warn, continue and flag */
194 case FTS_DNR:
195 case FTS_ERR:
196 case FTS_NS:
197 warnx("%s: %s", p->fts_path, strerror(p->fts_errno));
198 retval = 1;
199 break;
200 default:
49e10979
FT
201 file_inode = p->fts_statp->st_ino;
202 file_size = p->fts_statp->st_size;
203 file_uid = p->fts_statp->st_uid;
204 file_gid = p->fts_statp->st_gid;
205
206 /* files with more than one link */
207 if (p->fts_statp->st_nlink > 1) {
208 if (hl_register(file_inode)) {
209 /* only process them once */
210 global_size += file_size;
211 }
212 } else {
213 global_size += file_size;
214 }
6a4c3e18
FT
215 }
216 }
a89ecd20 217 fts_close(fts);
6a4c3e18 218
49e10979 219 printf("%"PRIu64"\n", global_size);
6a4c3e18
FT
220 return retval;
221}
222
a3dce641
FT
223/* print a list of filesystems with accounting enabled */
224static int get_fslist(void) {
225 struct statfs *mntbufp;
226 int nloc, i;
227
228 /* read mount table from kernel */
229 nloc = getmntinfo(&mntbufp, MNT_NOWAIT|MNT_LOCAL);
230 if (nloc <= 0) {
231 perror("getmntinfo");
232 exit(1);
233 }
234
235 /* iterate mounted filesystems */
236 for (i=0; i<nloc; i++) {
237 /* vfs accounting enabled on this one ? */
238 if (mntbufp[i].f_flags & MNT_ACCOUNTING)
239 printf("%s on %s\n", mntbufp[i].f_mntfromname,
240 mntbufp[i].f_mntonname);
241 }
242
243 return 0;
244}
245
b4d6d8bb
FT
246static bool
247send_command(const char *path, const char *cmd,
248 prop_dictionary_t args, prop_dictionary_t *res) {
249 prop_dictionary_t dict;
250 struct plistref pref;
251
252 bool rv;
253 int error;
254
255 dict = prop_dictionary_create();
256
257 if (dict == NULL) {
258 printf("send_command(): couldn't create dictionary\n");
259 return false;
260 }
261
262 rv = prop_dictionary_set_cstring(dict, "command", cmd);
263 if (rv== false) {
264 printf("send_command(): couldn't initialize dictionary\n");
265 return false;
266 }
267
268 rv = prop_dictionary_set(dict, "arguments", args);
269 if (rv == false) {
270 printf("prop_dictionary_set() failed\n");
271 return false;
272 }
273
274 error = prop_dictionary_send_syscall(dict, &pref);
275 if (error != 0) {
276 printf("prop_dictionary_send_syscall() failed\n");
277 prop_object_release(dict);
278 return false;
279 }
280
281 if (flag_debug)
49e10979 282 printf("Message to kernel:\n%s\n", prop_dictionary_externalize(dict));
b4d6d8bb
FT
283
284 error = vquotactl(path, &pref);
285 if (error != 0) {
286 printf("send_command: vquotactl = %d\n", error);
287 return false;
288 }
289
290 error = prop_dictionary_recv_syscall(&pref, res);
291 if (error != 0) {
292 printf("prop_dictionary_recv_syscall() failed\n");
293 }
294
295 if (flag_debug)
296 printf("Message from kernel:\n%s\n", prop_dictionary_externalize(*res));
297
298 return true;
299}
300
301/* show collected statistics on mount point */
302static int show_mp(char *path) {
303 prop_dictionary_t args, res;
304 prop_array_t reslist;
305 bool rv;
306 prop_object_iterator_t iter;
307 prop_dictionary_t item;
308 uint32_t id;
309 uint64_t space;
310
311 args = prop_dictionary_create();
312 res = prop_dictionary_create();
313 if (args == NULL)
314 printf("couldn't create args dictionary\n");
315
316 rv = send_command(path, "get usage all", args, &res);
317 if (rv == false) {
318 printf("show-mp(): failed to send message to kernel\n");
319 goto end;
320 }
321
322 reslist = prop_dictionary_get(res, "get usage all");
323 if (reslist == NULL) {
324 printf("show_mp(): failed to get array of results");
325 rv = false;
326 goto end;
327 }
328
329 iter = prop_array_iterator(reslist);
330 if (iter == NULL) {
331 printf("show_mp(): failed to create iterator\n");
332 rv = false;
333 goto end;
334 }
335
336 while ((item = prop_object_iterator_next(iter)) != NULL) {
337 rv = prop_dictionary_get_uint64(item, "space used", &space);
338 if (prop_dictionary_get_uint32(item, "uid", &id))
339 printf("uid %u:", id);
340 else if (prop_dictionary_get_uint32(item, "gid", &id))
341 printf("gid %u:", id);
342 else
343 printf("total space used");
344 printf(" %" PRIu64 "\n", space);
345 }
346 prop_object_iterator_release(iter);
347
348end:
349 prop_object_release(args);
350 prop_object_release(res);
351 return (rv == true);
352}
353
cefc2cb0 354int
b4d6d8bb
FT
355main(int argc, char **argv) {
356 int ch;
357
358 while ((ch = getopt(argc, argv, "D")) != -1) {
359 switch(ch) {
360 case 'D':
361 flag_debug = 1;
362 break;
363 }
364 }
365 argc -= optind;
366 argv += optind;
367 if (argc < 1)
6a4c3e18
FT
368 usage(1);
369
b4d6d8bb
FT
370 if (strcmp(argv[0], "check") == 0) {
371 if (argc != 2)
a3dce641 372 usage(1);
b4d6d8bb 373 return get_dirsize(argv[1]);
a3dce641 374 }
b4d6d8bb 375 if (strcmp(argv[0], "lsfs") == 0) {
a3dce641 376 return get_fslist();
6a4c3e18 377 }
b4d6d8bb
FT
378 if (strcmp(argv[0], "show") == 0) {
379 if (argc != 2)
380 usage(1);
381 return show_mp(argv[1]);
382 }
6a4c3e18 383
b4d6d8bb 384 usage(0);
6a4c3e18 385}