]>
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 | ||
6b803e5a CH |
7 | #include "command.h" |
8 | #include "input.h" | |
5aead01d NS |
9 | #include "init.h" |
10 | #include "quota.h" | |
11 | ||
12 | static cmdinfo_t project_cmd; | |
13 | static prid_t prid; | |
544f6ba0 | 14 | static int recurse_depth = -1; |
5aead01d NS |
15 | |
16 | enum { | |
17 | CHECK_PROJECT = 0x1, | |
18 | SETUP_PROJECT = 0x2, | |
19 | CLEAR_PROJECT = 0x4, | |
20 | }; | |
21 | ||
366127f7 | 22 | #define EXCLUDED_FILE_TYPES(x) \ |
b9d2d4a2 | 23 | (S_ISCHR((x)) \ |
366127f7 DD |
24 | || S_ISBLK((x)) \ |
25 | || S_ISFIFO((x)) \ | |
26 | || S_ISLNK((x)) \ | |
b9d2d4a2 | 27 | || S_ISSOCK((x))) |
366127f7 | 28 | |
5aead01d NS |
29 | static void |
30 | project_help(void) | |
31 | { | |
32 | printf(_( | |
33 | "\n" | |
34 | " list projects or setup a project tree for tree quota management\n" | |
35 | "\n" | |
36 | " Example:\n" | |
37 | " 'project -c logfiles'\n" | |
38 | " (match project 'logfiles' to a directory, and setup the directory tree)\n" | |
39 | "\n" | |
40 | " Without arguments, report all projects found in the /etc/projects file.\n" | |
41 | " The project quota mechanism in XFS can be used to implement a form of\n" | |
42 | " directory tree quota, where a specified directory and all of the files\n" | |
43 | " and subdirectories below it (i.e. a tree) can be restricted to using a\n" | |
44 | " subset of the available space in the filesystem.\n" | |
45 | "\n" | |
8bfb5eac | 46 | " A managed tree must be setup initially using the -s option with a project.\n" |
5aead01d NS |
47 | " The specified project name or identifier is matched to one or more trees\n" |
48 | " defined in /etc/projects, and these trees are then recursively descended\n" | |
49 | " to mark the affected inodes as being part of that tree - which sets inode\n" | |
50 | " flags and the project identifier on every file.\n" | |
51 | " Once this has been done, new files created in the tree will automatically\n" | |
52 | " be accounted to the tree based on their project identifier. An attempt to\n" | |
53 | " create a hard link to a file in the tree will only succeed if the project\n" | |
ff1f79a7 | 54 | " identifier matches the project identifier for the tree. The xfs_io utility\n" |
5aead01d NS |
55 | " can be used to set the project ID for an arbitrary file, but this can only\n" |
56 | " be done by a privileged user.\n" | |
57 | "\n" | |
58 | " A previously setup tree can be cleared from project quota control through\n" | |
59 | " use of the -C option, which will recursively descend the tree, clearing\n" | |
60 | " the affected inodes from project quota control.\n" | |
61 | "\n" | |
62 | " The -c option can be used to check whether a tree is setup, it reports\n" | |
63 | " nothing if the tree is correct, otherwise it reports the paths of inodes\n" | |
64 | " which do not have the project ID of the rest of the tree, or if the inode\n" | |
65 | " flag is not set.\n" | |
66 | "\n" | |
02b26a6d AM |
67 | " The -p <path> option can be used to manually specify project path without\n" |
68 | " need to create /etc/projects file. This option can be used multiple times\n" | |
69 | " to specify multiple paths. When using this option only one projid/name can\n" | |
70 | " be specified at command line. Note that /etc/projects is also used if exists.\n" | |
71 | "\n" | |
544f6ba0 AM |
72 | " The -d <depth> option allows to descend at most <depth> levels of directories\n" |
73 | " below the command line arguments. -d 0 means only apply the actions\n" | |
74 | " to the top level of the projects. -d -1 means no recursion limit (default).\n" | |
75 | "\n" | |
5aead01d NS |
76 | " The /etc/projid and /etc/projects file formats are simple, and described\n" |
77 | " on the xfs_quota man page.\n" | |
78 | "\n")); | |
79 | } | |
80 | ||
81 | static int | |
82 | check_project( | |
83 | const char *path, | |
84 | const struct stat *stat, | |
366127f7 | 85 | int flag, |
5aead01d NS |
86 | struct FTW *data) |
87 | { | |
88 | struct fsxattr fsx; | |
5aead01d NS |
89 | int fd; |
90 | ||
544f6ba0 | 91 | if (recurse_depth >= 0 && data->level > recurse_depth) |
1472d00f | 92 | return 0; |
544f6ba0 | 93 | |
366127f7 | 94 | if (flag == FTW_NS ){ |
e3210fd8 | 95 | exitcode = 1; |
366127f7 DD |
96 | fprintf(stderr, _("%s: cannot stat file %s\n"), progname, path); |
97 | return 0; | |
98 | } | |
99 | if (EXCLUDED_FILE_TYPES(stat->st_mode)) { | |
100 | fprintf(stderr, _("%s: skipping special file %s\n"), progname, path); | |
101 | return 0; | |
102 | } | |
103 | ||
e3210fd8 AM |
104 | if ((fd = open(path, O_RDONLY|O_NOCTTY)) == -1) { |
105 | exitcode = 1; | |
5aead01d NS |
106 | fprintf(stderr, _("%s: cannot open %s: %s\n"), |
107 | progname, path, strerror(errno)); | |
83f4b5ac | 108 | } else if ((xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx)) < 0) { |
e3210fd8 | 109 | exitcode = 1; |
5aead01d NS |
110 | fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
111 | progname, path, strerror(errno)); | |
e3210fd8 | 112 | } else { |
764b1982 | 113 | if (fsx.fsx_projid != prid) |
5aead01d NS |
114 | printf(_("%s - project identifier is not set" |
115 | " (inode=%u, tree=%u)\n"), | |
2a1888c5 | 116 | path, fsx.fsx_projid, (unsigned int)prid); |
b136f48b | 117 | if (!(fsx.fsx_xflags & FS_XFLAG_PROJINHERIT) && S_ISDIR(stat->st_mode)) |
5aead01d NS |
118 | printf(_("%s - project inheritance flag is not set\n"), |
119 | path); | |
120 | } | |
121 | if (fd != -1) | |
122 | close(fd); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int | |
127 | clear_project( | |
128 | const char *path, | |
129 | const struct stat *stat, | |
366127f7 | 130 | int flag, |
5aead01d NS |
131 | struct FTW *data) |
132 | { | |
133 | struct fsxattr fsx; | |
134 | int fd; | |
135 | ||
544f6ba0 | 136 | if (recurse_depth >= 0 && data->level > recurse_depth) |
1472d00f | 137 | return 0; |
544f6ba0 | 138 | |
366127f7 | 139 | if (flag == FTW_NS ){ |
e3210fd8 | 140 | exitcode = 1; |
366127f7 DD |
141 | fprintf(stderr, _("%s: cannot stat file %s\n"), progname, path); |
142 | return 0; | |
143 | } | |
144 | if (EXCLUDED_FILE_TYPES(stat->st_mode)) { | |
145 | fprintf(stderr, _("%s: skipping special file %s\n"), progname, path); | |
146 | return 0; | |
147 | } | |
148 | ||
5aead01d | 149 | if ((fd = open(path, O_RDONLY|O_NOCTTY)) == -1) { |
e3210fd8 | 150 | exitcode = 1; |
5aead01d NS |
151 | fprintf(stderr, _("%s: cannot open %s: %s\n"), |
152 | progname, path, strerror(errno)); | |
153 | return 0; | |
83f4b5ac | 154 | } else if (xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx) < 0) { |
e3210fd8 | 155 | exitcode = 1; |
5aead01d NS |
156 | fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
157 | progname, path, strerror(errno)); | |
158 | close(fd); | |
159 | return 0; | |
160 | } | |
161 | ||
764b1982 | 162 | fsx.fsx_projid = 0; |
83f4b5ac DC |
163 | fsx.fsx_xflags &= ~FS_XFLAG_PROJINHERIT; |
164 | if (xfsctl(path, fd, FS_IOC_FSSETXATTR, &fsx) < 0) { | |
e3210fd8 | 165 | exitcode = 1; |
764b1982 | 166 | fprintf(stderr, _("%s: cannot clear project on %s: %s\n"), |
5aead01d | 167 | progname, path, strerror(errno)); |
e3210fd8 | 168 | } |
5aead01d NS |
169 | close(fd); |
170 | return 0; | |
171 | } | |
172 | ||
173 | static int | |
174 | setup_project( | |
175 | const char *path, | |
176 | const struct stat *stat, | |
366127f7 | 177 | int flag, |
5aead01d NS |
178 | struct FTW *data) |
179 | { | |
180 | struct fsxattr fsx; | |
181 | int fd; | |
182 | ||
544f6ba0 | 183 | if (recurse_depth >= 0 && data->level > recurse_depth) |
1472d00f | 184 | return 0; |
544f6ba0 | 185 | |
366127f7 | 186 | if (flag == FTW_NS ){ |
e3210fd8 | 187 | exitcode = 1; |
366127f7 DD |
188 | fprintf(stderr, _("%s: cannot stat file %s\n"), progname, path); |
189 | return 0; | |
190 | } | |
191 | if (EXCLUDED_FILE_TYPES(stat->st_mode)) { | |
192 | fprintf(stderr, _("%s: skipping special file %s\n"), progname, path); | |
193 | return 0; | |
194 | } | |
195 | ||
5aead01d | 196 | if ((fd = open(path, O_RDONLY|O_NOCTTY)) == -1) { |
e3210fd8 | 197 | exitcode = 1; |
5aead01d NS |
198 | fprintf(stderr, _("%s: cannot open %s: %s\n"), |
199 | progname, path, strerror(errno)); | |
200 | return 0; | |
83f4b5ac | 201 | } else if (xfsctl(path, fd, FS_IOC_FSGETXATTR, &fsx) < 0) { |
e3210fd8 | 202 | exitcode = 1; |
5aead01d NS |
203 | fprintf(stderr, _("%s: cannot get flags on %s: %s\n"), |
204 | progname, path, strerror(errno)); | |
205 | close(fd); | |
206 | return 0; | |
207 | } | |
208 | ||
764b1982 | 209 | fsx.fsx_projid = prid; |
83f4b5ac DC |
210 | fsx.fsx_xflags |= FS_XFLAG_PROJINHERIT; |
211 | if (xfsctl(path, fd, FS_IOC_FSSETXATTR, &fsx) < 0) { | |
e3210fd8 | 212 | exitcode = 1; |
764b1982 | 213 | fprintf(stderr, _("%s: cannot set project on %s: %s\n"), |
5aead01d | 214 | progname, path, strerror(errno)); |
e3210fd8 | 215 | } |
5aead01d NS |
216 | close(fd); |
217 | return 0; | |
218 | } | |
219 | ||
220 | static void | |
221 | project_operations( | |
222 | char *project, | |
223 | char *dir, | |
224 | int type) | |
225 | { | |
226 | switch (type) { | |
227 | case CHECK_PROJECT: | |
228 | printf(_("Checking project %s (path %s)...\n"), project, dir); | |
544f6ba0 | 229 | nftw(dir, check_project, 100, FTW_PHYS|FTW_MOUNT); |
5aead01d NS |
230 | break; |
231 | case SETUP_PROJECT: | |
232 | printf(_("Setting up project %s (path %s)...\n"), project, dir); | |
544f6ba0 | 233 | nftw(dir, setup_project, 100, FTW_PHYS|FTW_MOUNT); |
5aead01d NS |
234 | break; |
235 | case CLEAR_PROJECT: | |
236 | printf(_("Clearing project %s (path %s)...\n"), project, dir); | |
544f6ba0 | 237 | nftw(dir, clear_project, 100, FTW_PHYS|FTW_MOUNT); |
5aead01d NS |
238 | break; |
239 | } | |
240 | } | |
241 | ||
242 | static void | |
243 | project( | |
244 | char *project, | |
245 | int type) | |
246 | { | |
247 | fs_cursor_t cursor; | |
248 | fs_path_t *path; | |
249 | int count = 0; | |
250 | ||
251 | fs_cursor_initialise(NULL, FS_PROJECT_PATH, &cursor); | |
252 | while ((path = fs_cursor_next_entry(&cursor))) { | |
02b26a6d | 253 | if (prid != path->fs_prid && path->fs_prid != -1) |
5aead01d NS |
254 | continue; |
255 | project_operations(project, path->fs_dir, type); | |
256 | count++; | |
257 | } | |
258 | ||
272f4db5 AM |
259 | printf(_("Processed %d (%s and cmdline) paths for project %s with " |
260 | "recursion depth %s (%d).\n"), | |
544f6ba0 AM |
261 | count, projects_file, project, |
262 | recurse_depth < 0 ? _("infinite") : _("limited"), recurse_depth); | |
5aead01d NS |
263 | } |
264 | ||
265 | static int | |
266 | project_f( | |
267 | int argc, | |
268 | char **argv) | |
269 | { | |
02b26a6d | 270 | int c, type = 0, ispath = 0; |
5aead01d | 271 | |
02b26a6d | 272 | while ((c = getopt(argc, argv, "cd:p:sC")) != EOF) { |
5aead01d NS |
273 | switch (c) { |
274 | case 'c': | |
275 | type = CHECK_PROJECT; | |
276 | break; | |
544f6ba0 AM |
277 | case 'd': |
278 | recurse_depth = atoi(optarg); | |
279 | if (recurse_depth < 0) | |
280 | recurse_depth = -1; | |
281 | break; | |
02b26a6d AM |
282 | case 'p': |
283 | ispath = 1; | |
284 | fs_table_insert_project_path(optarg, -1); | |
285 | break; | |
5aead01d NS |
286 | case 's': |
287 | type = SETUP_PROJECT; | |
288 | break; | |
289 | case 'C': | |
290 | type = CLEAR_PROJECT; | |
291 | break; | |
292 | default: | |
293 | return command_usage(&project_cmd); | |
294 | } | |
295 | } | |
296 | ||
297 | if (argc == optind) | |
298 | return command_usage(&project_cmd); | |
299 | ||
300 | /* no options - just check the given projects */ | |
301 | if (!type) | |
302 | type = CHECK_PROJECT; | |
303 | ||
304 | setprfiles(); | |
02b26a6d | 305 | if (!ispath && access(projects_file, F_OK) != 0) { |
e3210fd8 | 306 | exitcode = 1; |
5aead01d NS |
307 | fprintf(stderr, _("projects file \"%s\" doesn't exist\n"), |
308 | projects_file); | |
309 | return 0; | |
310 | } | |
311 | ||
02b26a6d AM |
312 | if (ispath && argc - optind > 1) { |
313 | exitcode = 1; | |
272f4db5 AM |
314 | fprintf(stderr, _("%s: only one projid/name can be specified " |
315 | "when using -p <path>, %d found.\n"), | |
316 | progname, argc - optind); | |
02b26a6d AM |
317 | return 0; |
318 | } | |
319 | ||
5aead01d NS |
320 | while (argc > optind) { |
321 | prid = prid_from_string(argv[optind]); | |
e3210fd8 AM |
322 | if (prid == -1) { |
323 | exitcode = 1; | |
272f4db5 AM |
324 | fprintf(stderr, _("%s - no such project in %s " |
325 | "or invalid project number\n"), | |
5aead01d | 326 | argv[optind], projects_file); |
e3210fd8 | 327 | } else |
5aead01d NS |
328 | project(argv[optind], type); |
329 | optind++; | |
330 | } | |
331 | ||
332 | return 0; | |
333 | } | |
334 | ||
335 | void | |
336 | project_init(void) | |
337 | { | |
ad765595 AM |
338 | project_cmd.name = "project"; |
339 | project_cmd.altname = "tree"; | |
5aead01d | 340 | project_cmd.cfunc = project_f; |
02b26a6d | 341 | project_cmd.args = _("[-c|-s|-C|-d <depth>|-p <path>] project ..."); |
5aead01d NS |
342 | project_cmd.argmin = 1; |
343 | project_cmd.argmax = -1; | |
344 | project_cmd.oneline = _("check, setup or clear project quota trees"); | |
345 | project_cmd.help = project_help; | |
29647c8d | 346 | project_cmd.flags = CMD_FLAG_FOREIGN_OK; |
5aead01d NS |
347 | |
348 | if (expert) | |
349 | add_command(&project_cmd); | |
350 | } |