]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0 |
5aead01d | 2 | /* |
da23017d NS |
3 | * Copyright (c) 2005 Silicon Graphics, Inc. |
4 | * All Rights Reserved. | |
5aead01d NS |
5 | */ |
6 | ||
29647c8d | 7 | #include <stdbool.h> |
6b803e5a | 8 | #include "command.h" |
5aead01d NS |
9 | #include <ctype.h> |
10 | #include <pwd.h> | |
11 | #include <grp.h> | |
12 | #include "init.h" | |
13 | #include "quota.h" | |
9fc3ef62 | 14 | #include "libfrog/logging.h" |
fee68490 | 15 | #include "libfrog/fsgeom.h" |
f31b5e12 | 16 | #include "libfrog/bulkstat.h" |
5aead01d NS |
17 | |
18 | typedef struct du { | |
19 | struct du *next; | |
14f8b681 DW |
20 | uint64_t blocks; |
21 | uint64_t blocks30; | |
22 | uint64_t blocks60; | |
23 | uint64_t blocks90; | |
24 | uint64_t nfiles; | |
25 | uint32_t id; | |
5aead01d NS |
26 | } du_t; |
27 | ||
28 | #define TSIZE 500 | |
14f8b681 DW |
29 | static uint64_t sizes[TSIZE]; |
30 | static uint64_t overflow; | |
5aead01d NS |
31 | |
32 | #define NDU 60000 | |
33 | #define DUHASH 8209 | |
34 | static du_t du[3][NDU]; | |
35 | static du_t *duhash[3][DUHASH]; | |
36 | static int ndu[3]; /* #usr/grp/prj */ | |
37 | ||
38 | #define NBSTAT 4069 | |
39 | ||
40 | static time_t now; | |
41 | static cmdinfo_t quot_cmd; | |
42 | ||
43 | static void | |
44 | quot_help(void) | |
45 | { | |
46 | printf(_( | |
47 | "\n" | |
48 | " display a summary of filesystem ownership\n" | |
49 | "\n" | |
50 | " -a -- summarise for all local XFS filesystem mount points\n" | |
51 | " -c -- display three columns giving file size in kilobytes, number of files\n" | |
52 | " of that size, and cumulative total of kilobytes in that size or\n" | |
53 | " smaller file. The last row is used as an overflow bucket and is the\n" | |
54 | " total of all files greater than 500 kilobytes.\n" | |
55 | " -v -- display three columns containing the number of kilobytes not\n" | |
56 | " accessed in the last 30, 60, and 90 days.\n" | |
57 | " -g -- display group summary\n" | |
58 | " -p -- display project summary\n" | |
59 | " -u -- display user summary\n" | |
60 | " -b -- display number of blocks used\n" | |
61 | " -i -- display number of inodes used\n" | |
62 | " -r -- display number of realtime blocks used\n" | |
1774874a NS |
63 | " -n -- skip identifier-to-name translations, just report IDs\n" |
64 | " -N -- suppress the initial header\n" | |
5aead01d NS |
65 | " -f -- send output to a file\n" |
66 | " The (optional) user/group/project can be specified either by name or by\n" | |
67 | " number (i.e. uid/gid/projid).\n" | |
68 | "\n")); | |
69 | } | |
70 | ||
71 | static void | |
72 | quot_bulkstat_add( | |
4cca629d | 73 | struct xfs_bulkstat *p, |
5aead01d NS |
74 | uint flags) |
75 | { | |
76 | du_t *dp; | |
77 | du_t **hp; | |
14f8b681 DW |
78 | uint64_t size; |
79 | uint32_t i, id; | |
5aead01d NS |
80 | |
81 | if ((p->bs_mode & S_IFMT) == 0) | |
82 | return; | |
83 | size = howmany((p->bs_blocks * p->bs_blksize), 0x400ULL); | |
84 | ||
85 | if (flags & HISTOGRAM_FLAG) { | |
86 | if (!(S_ISDIR(p->bs_mode) || S_ISREG(p->bs_mode))) | |
87 | return; | |
88 | if (size >= TSIZE) { | |
89 | overflow += size; | |
90 | size = TSIZE - 1; | |
91 | } | |
92 | sizes[(int)size]++; | |
93 | return; | |
94 | } | |
95 | for (i = 0; i < 3; i++) { | |
96 | id = (i == 0) ? p->bs_uid : ((i == 1) ? | |
4cca629d | 97 | p->bs_gid : p->bs_projectid); |
5aead01d NS |
98 | hp = &duhash[i][id % DUHASH]; |
99 | for (dp = *hp; dp; dp = dp->next) | |
100 | if (dp->id == id) | |
101 | break; | |
5e656dbb | 102 | if (dp == NULL) { |
5aead01d NS |
103 | if (ndu[i] >= NDU) |
104 | return; | |
105 | dp = &du[i][(ndu[i]++)]; | |
106 | dp->next = *hp; | |
107 | *hp = dp; | |
108 | dp->id = id; | |
109 | dp->nfiles = 0; | |
110 | dp->blocks = 0; | |
111 | dp->blocks30 = 0; | |
112 | dp->blocks60 = 0; | |
113 | dp->blocks90 = 0; | |
114 | } | |
115 | dp->blocks += size; | |
116 | ||
4cca629d | 117 | if (now - p->bs_atime > 30 * (60*60*24)) |
5aead01d | 118 | dp->blocks30 += size; |
4cca629d | 119 | if (now - p->bs_atime > 60 * (60*60*24)) |
5aead01d | 120 | dp->blocks60 += size; |
4cca629d | 121 | if (now - p->bs_atime > 90 * (60*60*24)) |
5aead01d NS |
122 | dp->blocks90 += size; |
123 | dp->nfiles++; | |
124 | } | |
125 | } | |
126 | ||
127 | static void | |
128 | quot_bulkstat_mount( | |
129 | char *fsdir, | |
f31b5e12 | 130 | unsigned int flags) |
5aead01d | 131 | { |
f31b5e12 | 132 | struct xfs_fd fsxfd = XFS_FD_INIT_EMPTY; |
4cca629d | 133 | struct xfs_bulkstat_req *breq; |
248af7cb | 134 | int i, sts, ret; |
5aead01d NS |
135 | du_t **dp; |
136 | ||
137 | /* | |
138 | * Initialize tables between checks; because of the qsort | |
139 | * in report() the hash tables must be rebuilt each time. | |
140 | */ | |
141 | for (sts = 0; sts < TSIZE; sts++) | |
142 | sizes[sts] = 0; | |
143 | overflow = 0; | |
144 | for (i = 0; i < 3; i++) | |
145 | for (dp = duhash[i]; dp < &duhash[i][DUHASH]; dp++) | |
5e656dbb | 146 | *dp = NULL; |
5aead01d NS |
147 | ndu[0] = ndu[1] = ndu[2] = 0; |
148 | ||
03d96c64 | 149 | ret = -xfd_open(&fsxfd, fsdir, O_RDONLY); |
248af7cb | 150 | if (ret) { |
9fc3ef62 | 151 | xfrog_perror(ret, fsdir); |
5aead01d NS |
152 | return; |
153 | } | |
154 | ||
e6542132 DW |
155 | ret = -xfrog_bulkstat_alloc_req(NBSTAT, 0, &breq); |
156 | if (ret) { | |
157 | xfrog_perror(ret, "calloc"); | |
f31b5e12 | 158 | xfd_close(&fsxfd); |
5aead01d NS |
159 | return; |
160 | } | |
161 | ||
e6542132 | 162 | while ((sts = -xfrog_bulkstat(&fsxfd, breq)) == 0) { |
4cca629d | 163 | if (breq->hdr.ocount == 0) |
5aead01d | 164 | break; |
4cca629d DW |
165 | for (i = 0; i < breq->hdr.ocount; i++) |
166 | quot_bulkstat_add(&breq->bulkstat[i], flags); | |
5aead01d | 167 | } |
9fc3ef62 DW |
168 | if (sts < 0) |
169 | xfrog_perror(sts, "XFS_IOC_FSBULKSTAT"); | |
4cca629d | 170 | free(breq); |
f31b5e12 | 171 | xfd_close(&fsxfd); |
5aead01d NS |
172 | } |
173 | ||
174 | static int | |
175 | qcompare( | |
559b58fa ES |
176 | const void *p1, |
177 | const void *p2) | |
5aead01d | 178 | { |
559b58fa ES |
179 | du_t *d1 = (struct du *)p1; |
180 | du_t *d2 = (struct du *)p2; | |
181 | ||
182 | if (d1->blocks > d2->blocks) | |
5aead01d | 183 | return -1; |
559b58fa | 184 | if (d1->blocks < d2->blocks) |
5aead01d | 185 | return 1; |
559b58fa | 186 | if (d1->id > d2->id) |
5aead01d | 187 | return 1; |
559b58fa | 188 | else if (d1->id < d2->id) |
5aead01d NS |
189 | return -1; |
190 | return 0; | |
191 | } | |
192 | ||
14f8b681 | 193 | typedef char *(*idtoname_t)(uint32_t); |
5aead01d NS |
194 | |
195 | static void | |
196 | quot_report_mount_any_type( | |
197 | FILE *fp, | |
198 | du_t *dp, | |
199 | int count, | |
200 | idtoname_t names, | |
201 | uint form, | |
202 | uint type, | |
203 | fs_path_t *mount, | |
204 | uint flags) | |
205 | { | |
206 | char *cp; | |
207 | ||
208 | fprintf(fp, _("%s (%s) %s:\n"), | |
209 | mount->fs_name, mount->fs_dir, type_to_string(type)); | |
559b58fa | 210 | qsort(dp, count, sizeof(dp[0]), qcompare); |
5aead01d NS |
211 | for (; dp < &dp[count]; dp++) { |
212 | if (dp->blocks == 0) | |
213 | return; | |
214 | fprintf(fp, "%8llu ", (unsigned long long) dp->blocks); | |
215 | if (form & XFS_INODE_QUOTA) | |
216 | fprintf(fp, "%8llu ", | |
217 | (unsigned long long) dp->nfiles); | |
1774874a NS |
218 | if (!(flags & NO_LOOKUP_FLAG) && |
219 | ((cp = (names)(dp->id)) != NULL)) | |
5aead01d NS |
220 | fprintf(fp, "%-8.8s", cp); |
221 | else | |
222 | fprintf(fp, "#%-7d", dp->id); | |
223 | if (flags & VERBOSE_FLAG) | |
224 | fprintf(fp, " %8llu %8llu %8llu", | |
225 | (unsigned long long) dp->blocks30, | |
226 | (unsigned long long) dp->blocks60, | |
227 | (unsigned long long) dp->blocks90); | |
228 | fputc('\n', fp); | |
229 | } | |
230 | } | |
231 | ||
232 | static void | |
233 | quot_report_mount( | |
234 | FILE *fp, | |
235 | uint form, | |
236 | uint type, | |
237 | fs_path_t *mount, | |
238 | uint flags) | |
239 | { | |
240 | switch (type) { | |
241 | case XFS_GROUP_QUOTA: | |
242 | quot_report_mount_any_type(fp, du[1], ndu[1], gid_to_name, | |
243 | form, type, mount, flags); | |
244 | break; | |
245 | case XFS_PROJ_QUOTA: | |
246 | quot_report_mount_any_type(fp, du[2], ndu[2], prid_to_name, | |
247 | form, type, mount, flags); | |
248 | break; | |
249 | case XFS_USER_QUOTA: | |
250 | quot_report_mount_any_type(fp, du[0], ndu[0], uid_to_name, | |
251 | form, type, mount, flags); | |
252 | } | |
253 | } | |
254 | ||
255 | static void | |
256 | quot_report( | |
257 | FILE *fp, | |
258 | uint form, | |
259 | uint type, | |
260 | char *dir, | |
261 | uint flags) | |
262 | { | |
263 | fs_cursor_t cursor; | |
264 | fs_path_t *mount; | |
265 | ||
5e656dbb | 266 | now = time(NULL); |
5aead01d NS |
267 | fs_cursor_initialise(dir, FS_MOUNT_POINT, &cursor); |
268 | while ((mount = fs_cursor_next_entry(&cursor))) { | |
269 | quot_bulkstat_mount(mount->fs_dir, flags); | |
270 | quot_report_mount(fp, form, type, mount, flags); | |
271 | } | |
272 | } | |
273 | ||
274 | static void | |
275 | quot_histogram_mount( | |
276 | FILE *fp, | |
277 | fs_path_t *mount, | |
278 | uint flags) | |
279 | { | |
14f8b681 | 280 | uint64_t t = 0; |
5aead01d NS |
281 | int i; |
282 | ||
283 | fprintf(fp, _("%s (%s):\n"), mount->fs_name, mount->fs_dir); | |
284 | ||
285 | for (i = 0; i < TSIZE - 1; i++) | |
286 | if (sizes[i] > 0) { | |
287 | t += sizes[i] * i; | |
288 | fprintf(fp, _("%d\t%llu\t%llu\n"), i, | |
289 | (unsigned long long) sizes[i], | |
290 | (unsigned long long) t); | |
291 | } | |
292 | fprintf(fp, _("%d\t%llu\t%llu\n"), TSIZE - 1, | |
293 | (unsigned long long) sizes[TSIZE - 1], | |
294 | (unsigned long long) (overflow + t)); | |
295 | } | |
296 | ||
297 | static void | |
298 | quot_histogram( | |
299 | FILE *fp, | |
300 | char *dir, | |
301 | uint flags) | |
302 | { | |
303 | fs_cursor_t cursor; | |
304 | fs_path_t *mount; | |
305 | ||
306 | fs_cursor_initialise(dir, FS_MOUNT_POINT, &cursor); | |
307 | while ((mount = fs_cursor_next_entry(&cursor))) { | |
308 | quot_bulkstat_mount(mount->fs_dir, flags); | |
309 | quot_histogram_mount(fp, mount, flags); | |
310 | } | |
311 | } | |
312 | ||
313 | static void | |
314 | quot_any_type( | |
315 | FILE *fp, | |
316 | uint form, | |
317 | uint type, | |
318 | char *dir, | |
319 | uint flags) | |
320 | { | |
321 | if (flags & HISTOGRAM_FLAG) | |
322 | quot_histogram(fp, dir, flags); | |
323 | else | |
324 | quot_report(fp, form, type, dir, flags); | |
325 | } | |
326 | ||
327 | static int | |
328 | quot_f( | |
329 | int argc, | |
330 | char **argv) | |
331 | { | |
332 | FILE *fp = NULL; | |
333 | char *fname = NULL; | |
334 | int c, flags = 0, type = 0, form = 0; | |
335 | ||
1774874a | 336 | while ((c = getopt(argc, argv, "abcf:ghinpruv")) != EOF) { |
5aead01d NS |
337 | switch (c) { |
338 | case 'f': | |
339 | fname = optarg; | |
340 | break; | |
341 | case 'b': | |
342 | form |= XFS_BLOCK_QUOTA; | |
343 | break; | |
344 | case 'i': | |
345 | form |= XFS_INODE_QUOTA; | |
346 | break; | |
347 | case 'r': | |
348 | form |= XFS_RTBLOCK_QUOTA; | |
349 | break; | |
350 | case 'g': | |
c614d3bf | 351 | type |= XFS_GROUP_QUOTA; |
5aead01d NS |
352 | break; |
353 | case 'p': | |
c614d3bf | 354 | type |= XFS_PROJ_QUOTA; |
5aead01d NS |
355 | break; |
356 | case 'u': | |
c614d3bf | 357 | type |= XFS_USER_QUOTA; |
5aead01d NS |
358 | break; |
359 | case 'a': | |
360 | flags |= ALL_MOUNTS_FLAG; | |
361 | break; | |
362 | case 'c': | |
363 | flags |= HISTOGRAM_FLAG; | |
364 | break; | |
1774874a NS |
365 | case 'n': |
366 | flags |= NO_LOOKUP_FLAG; | |
367 | break; | |
5aead01d NS |
368 | case 'v': |
369 | flags |= VERBOSE_FLAG; | |
370 | break; | |
371 | default: | |
372 | return command_usage("_cmd); | |
373 | } | |
374 | } | |
375 | ||
376 | if (!form) | |
377 | form = XFS_BLOCK_QUOTA; | |
378 | ||
c614d3bf | 379 | if (!type) { |
5aead01d | 380 | type = XFS_USER_QUOTA; |
c614d3bf ZL |
381 | } else if (type != XFS_GROUP_QUOTA && |
382 | type != XFS_PROJ_QUOTA && | |
383 | type != XFS_USER_QUOTA) { | |
384 | return command_usage("_cmd); | |
385 | } | |
5aead01d NS |
386 | |
387 | if ((fp = fopen_write_secure(fname)) == NULL) | |
388 | return 0; | |
389 | ||
fa13a00f NS |
390 | if (argc == optind) { |
391 | if (flags & ALL_MOUNTS_FLAG) | |
392 | quot_any_type(fp, form, type, NULL, flags); | |
393 | else if (fs_path->fs_flags & FS_MOUNT_POINT) | |
394 | quot_any_type(fp, form, type, fs_path->fs_dir, flags); | |
395 | } else while (argc > optind) { | |
5aead01d | 396 | quot_any_type(fp, form, type, argv[optind++], flags); |
fa13a00f | 397 | } |
5aead01d NS |
398 | |
399 | if (fname) | |
400 | fclose(fp); | |
401 | return 0; | |
402 | } | |
403 | ||
404 | void | |
405 | quot_init(void) | |
406 | { | |
ad765595 | 407 | quot_cmd.name = "quot"; |
5aead01d NS |
408 | quot_cmd.cfunc = quot_f; |
409 | quot_cmd.argmin = 0; | |
410 | quot_cmd.argmax = -1; | |
c614d3bf | 411 | quot_cmd.args = _("[-bir] [-g|-p|-u] [-acv] [-f file]"); |
5aead01d NS |
412 | quot_cmd.oneline = _("summarize filesystem ownership"); |
413 | quot_cmd.help = quot_help; | |
414 | ||
415 | if (expert) | |
416 | add_command("_cmd); | |
417 | } |