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