]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blob - libxcmd/paths.c
xfs_quota: add capabilities for use on non-XFS filesystems
[thirdparty/xfsprogs-dev.git] / libxcmd / paths.c
1 /*
2 * Copyright (c) 2005-2006 Silicon Graphics, Inc.
3 * All Rights Reserved.
4 *
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
7 * published by the Free Software Foundation.
8 *
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.
13 *
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
17 */
18
19 #include <paths.h>
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include "path.h"
28 #include "input.h"
29 #include "project.h"
30 #include <limits.h>
31
32 extern char *progname;
33
34 int fs_count;
35 struct fs_path *fs_table;
36 struct fs_path *fs_path;
37
38 char *mtab_file;
39 #define PROC_MOUNTS "/proc/self/mounts"
40
41 static int
42 fs_device_number(
43 const char *name,
44 dev_t *devnum)
45 {
46 struct stat64 sbuf;
47
48 if (stat64(name, &sbuf) < 0)
49 return errno;
50 /*
51 * We want to match st_rdev if the path provided is a device
52 * special file. Otherwise we are looking for the the
53 * device id for the containing filesystem, in st_dev.
54 */
55 if (S_ISBLK(sbuf.st_mode) || S_ISCHR(sbuf.st_mode))
56 *devnum = sbuf.st_rdev;
57 else
58 *devnum = sbuf.st_dev;
59
60 return 0;
61 }
62
63 /*
64 * Find the FS table entry for the given path. The "flags" argument
65 * is a mask containing FS_MOUNT_POINT or FS_PROJECT_PATH (or both)
66 * to indicate the type of table entry sought.
67 */
68 struct fs_path *
69 fs_table_lookup(
70 const char *dir,
71 uint flags)
72 {
73 uint i;
74 dev_t dev = 0;
75
76 if (fs_device_number(dir, &dev))
77 return NULL;
78
79 for (i = 0; i < fs_count; i++) {
80 if (flags && !(flags & fs_table[i].fs_flags))
81 continue;
82 if (fs_table[i].fs_datadev == dev)
83 return &fs_table[i];
84 }
85 return NULL;
86 }
87
88 static int
89 fs_table_insert(
90 char *dir,
91 uint prid,
92 uint flags,
93 char *fsname,
94 char *fslog,
95 char *fsrt)
96 {
97 dev_t datadev, logdev, rtdev;
98 struct fs_path *tmp_fs_table;
99 int error;
100
101 datadev = logdev = rtdev = 0;
102 error = fs_device_number(dir, &datadev);
103 if (error)
104 goto out_nodev;
105 if (fslog) {
106 error = fs_device_number(fslog, &logdev);
107 if (error)
108 goto out_nodev;
109 }
110 if (fsrt) {
111 error = fs_device_number(fsrt, &rtdev);
112 if (error)
113 goto out_nodev;
114 }
115
116 if (!platform_test_xfs_path(dir))
117 flags |= FS_FOREIGN;
118
119 /*
120 * Make copies of the directory and data device path.
121 * The log device and real-time device, if non-null,
122 * are already the result of strdup() calls so we
123 * don't need to duplicate those. In fact, this
124 * function is assumed to "consume" both of those
125 * pointers, meaning if an error occurs they will
126 * both get freed.
127 */
128 error = ENOMEM;
129 dir = strdup(dir);
130 if (!dir)
131 goto out_nodev;
132 fsname = strdup(fsname);
133 if (!fsname)
134 goto out_noname;
135
136 tmp_fs_table = realloc(fs_table, sizeof(fs_path_t) * (fs_count + 1));
137 if (!tmp_fs_table)
138 goto out_norealloc;
139 fs_table = tmp_fs_table;
140
141 fs_path = &fs_table[fs_count];
142 fs_path->fs_dir = dir;
143 fs_path->fs_prid = prid;
144 fs_path->fs_flags = flags;
145 fs_path->fs_name = fsname;
146 fs_path->fs_log = fslog;
147 fs_path->fs_rt = fsrt;
148 fs_path->fs_datadev = datadev;
149 fs_path->fs_logdev = logdev;
150 fs_path->fs_rtdev = rtdev;
151 fs_count++;
152
153 return 0;
154
155 out_norealloc:
156 free(fsname);
157 out_noname:
158 free(dir);
159 out_nodev:
160 /* "Consume" fslog and fsrt even if there's an error */
161 free(fslog);
162 free(fsrt);
163
164 return error;
165 }
166
167 /*
168 * Table iteration (cursor-based) interfaces
169 */
170
171 /*
172 * Initialize an fs_table cursor. If a directory path is supplied,
173 * the cursor is set up to appear as though the table contains only
174 * a single entry which represents the directory specified.
175 * Otherwise it is set up to prepare for visiting all entries in the
176 * global table, starting with the first. "flags" can be either
177 * FS_MOUNT_POINT or FS_PROJECT_PATH to limit what type of entries
178 * will be selected by fs_cursor_next_entry(). 0 can be used as a
179 * wild card (selecting either type).
180 */
181 void
182 fs_cursor_initialise(
183 char *dir,
184 uint flags,
185 fs_cursor_t *cur)
186 {
187 fs_path_t *path;
188
189 memset(cur, 0, sizeof(*cur));
190 if (dir) {
191 if ((path = fs_table_lookup(dir, flags)) == NULL)
192 return;
193 cur->local = *path;
194 cur->count = 1;
195 cur->table = &cur->local;
196 } else {
197 cur->count = fs_count;
198 cur->table = fs_table;
199 }
200 cur->flags = flags;
201 }
202
203 /*
204 * Use the cursor to find the next entry in the table having the
205 * type specified by the cursor's "flags" field.
206 */
207 struct fs_path *
208 fs_cursor_next_entry(
209 fs_cursor_t *cur)
210 {
211 while (cur->index < cur->count) {
212 fs_path_t *next = &cur->table[cur->index++];
213
214 if (!cur->flags || (cur->flags & next->fs_flags))
215 return next;
216 }
217 return NULL;
218 }
219
220
221 #if defined(HAVE_GETMNTENT)
222 #include <mntent.h>
223
224 /*
225 * Determines whether the "logdev" or "rtdev" mount options are
226 * present for the given mount point. If so, the value for each (a
227 * device path) is returned in the pointers whose addresses are
228 * provided. The pointers are assigned NULL for an option not
229 * present. Note that the path buffers returned are allocated
230 * dynamically and it is the caller's responsibility to free them.
231 */
232 static int
233 fs_extract_mount_options(
234 struct mntent *mnt,
235 char **logp,
236 char **rtp)
237 {
238 char *fslog, *fsrt;
239
240 /*
241 * Extract log device and realtime device from mount options.
242 *
243 * Note: the glibc hasmntopt implementation requires that the
244 * character in mnt_opts immediately after the search string
245 * must be a NULL ('\0'), a comma (','), or an equals ('=').
246 * Therefore we cannot search for 'logdev=' directly.
247 */
248 if ((fslog = hasmntopt(mnt, "logdev")) && fslog[6] == '=')
249 fslog += 7;
250 if ((fsrt = hasmntopt(mnt, "rtdev")) && fsrt[5] == '=')
251 fsrt += 6;
252
253 /* Do this only after we've finished processing mount options */
254 if (fslog) {
255 fslog = strndup(fslog, strcspn(fslog, " ,"));
256 if (!fslog)
257 goto out_nomem;
258 }
259 if (fsrt) {
260 fsrt = strndup(fsrt, strcspn(fsrt, " ,"));
261 if (!fsrt) {
262 free(fslog);
263 goto out_nomem;
264 }
265 }
266 *logp = fslog;
267 *rtp = fsrt;
268
269 return 0;
270
271 out_nomem:
272 *logp = NULL;
273 *rtp = NULL;
274 fprintf(stderr, _("%s: unable to extract mount options for \"%s\"\n"),
275 progname, mnt->mnt_dir);
276 return ENOMEM;
277 }
278
279 /*
280 * If *path is NULL, initialize the fs table with all xfs mount points in mtab
281 * If *path is specified, search for that path in mtab
282 *
283 * Everything - path, devices, and mountpoints - are boiled down to realpath()
284 * for comparison, but fs_table is populated with what comes from getmntent.
285 */
286 static int
287 fs_table_initialise_mounts(
288 char *path)
289 {
290 struct mntent *mnt;
291 FILE *mtp;
292 char *fslog, *fsrt;
293 int error, found;
294 char rpath[PATH_MAX], rmnt_fsname[PATH_MAX], rmnt_dir[PATH_MAX];
295
296 error = found = 0;
297 fslog = fsrt = NULL;
298
299 if (!mtab_file) {
300 mtab_file = PROC_MOUNTS;
301 if (access(mtab_file, R_OK) != 0)
302 mtab_file = MOUNTED;
303 }
304
305 if ((mtp = setmntent(mtab_file, "r")) == NULL)
306 return ENOENT;
307
308 /* Use realpath to resolve symlinks, relative paths, etc */
309 if (path)
310 if (!realpath(path, rpath))
311 return errno;
312
313 while ((mnt = getmntent(mtp)) != NULL) {
314 if (!realpath(mnt->mnt_dir, rmnt_dir))
315 continue;
316 if (!realpath(mnt->mnt_fsname, rmnt_fsname))
317 continue;
318
319 if (path &&
320 ((strcmp(rpath, rmnt_dir) != 0) &&
321 (strcmp(rpath, rmnt_fsname) != 0)))
322 continue;
323 if (fs_extract_mount_options(mnt, &fslog, &fsrt))
324 continue;
325 (void) fs_table_insert(mnt->mnt_dir, 0, FS_MOUNT_POINT,
326 mnt->mnt_fsname, fslog, fsrt);
327 if (path) {
328 found = 1;
329 break;
330 }
331 }
332 endmntent(mtp);
333
334 if (path && !found)
335 error = ENXIO;
336
337 return error;
338 }
339
340 #elif defined(HAVE_GETMNTINFO)
341 #include <sys/mount.h>
342
343 /*
344 * If *path is NULL, initialize the fs table with all xfs mount points in mtab
345 * If *path is specified, search for that path in mtab
346 *
347 * Everything - path, devices, and mountpoints - are boiled down to realpath()
348 * for comparison, but fs_table is populated with what comes from getmntinfo.
349 */
350 static int
351 fs_table_initialise_mounts(
352 char *path)
353 {
354 struct statfs *stats;
355 int i, count, error, found;
356 char rpath[PATH_MAX], rmntfromname[PATH_MAX], rmntonname[PATH_MAX];
357
358 error = found = 0;
359 if ((count = getmntinfo(&stats, 0)) < 0) {
360 fprintf(stderr, _("%s: getmntinfo() failed: %s\n"),
361 progname, strerror(errno));
362 return 0;
363 }
364
365 /* Use realpath to resolve symlinks, relative paths, etc */
366 if (path)
367 if (!realpath(path, rpath))
368 return errno;
369
370 for (i = 0; i < count; i++) {
371 if (!realpath(stats[i].f_mntfromname, rmntfromname))
372 continue;
373 if (!realpath(stats[i].f_mntonname, rmntonname))
374 continue;
375
376 if (path &&
377 ((strcmp(rpath, rmntonname) != 0) &&
378 (strcmp(rpath, rmntfromname) != 0)))
379 continue;
380 /* TODO: external log and realtime device? */
381 (void) fs_table_insert(stats[i].f_mntonname, 0,
382 FS_MOUNT_POINT, stats[i].f_mntfromname,
383 NULL, NULL);
384 if (path) {
385 found = 1;
386 break;
387 }
388 }
389 if (path && !found)
390 error = ENXIO;
391
392 return error;
393 }
394
395 #else
396 # error "How do I extract info about mounted filesystems on this platform?"
397 #endif
398
399 /*
400 * Given a directory, match it up to a filesystem mount point.
401 */
402 static struct fs_path *
403 fs_mount_point_from_path(
404 const char *dir)
405 {
406 fs_cursor_t cursor;
407 fs_path_t *fs;
408 dev_t dev = 0;
409
410 if (fs_device_number(dir, &dev))
411 return NULL;
412
413 fs_cursor_initialise(NULL, FS_MOUNT_POINT, &cursor);
414 while ((fs = fs_cursor_next_entry(&cursor))) {
415 if (fs->fs_datadev == dev)
416 break;
417 }
418 return fs;
419 }
420
421 static void
422 fs_table_insert_mount(
423 char *mount)
424 {
425 int error;
426
427 error = fs_table_initialise_mounts(mount);
428 if (error)
429 fprintf(stderr, _("%s: cannot setup path for mount %s: %s\n"),
430 progname, mount, strerror(error));
431 }
432
433 static int
434 fs_table_initialise_projects(
435 char *project)
436 {
437 fs_project_path_t *path;
438 fs_path_t *fs;
439 prid_t prid = 0;
440 int error = 0, found = 0;
441
442 if (project)
443 prid = prid_from_string(project);
444
445 setprpathent();
446 while ((path = getprpathent()) != NULL) {
447 if (project && prid != path->pp_prid)
448 continue;
449 fs = fs_mount_point_from_path(path->pp_pathname);
450 if (!fs) {
451 fprintf(stderr, _("%s: cannot find mount point for path `%s': %s\n"),
452 progname, path->pp_pathname, strerror(errno));
453 continue;
454 }
455 (void) fs_table_insert(path->pp_pathname, path->pp_prid,
456 FS_PROJECT_PATH, fs->fs_name,
457 NULL, NULL);
458 if (project) {
459 found = 1;
460 break;
461 }
462 }
463 endprpathent();
464
465 if (project && !found)
466 error = ENOENT;
467
468 return error;
469 }
470
471 static void
472 fs_table_insert_project(
473 char *project)
474 {
475 int error;
476
477 error = fs_table_initialise_projects(project);
478 if (error)
479 fprintf(stderr, _("%s: cannot setup path for project %s: %s\n"),
480 progname, project, strerror(error));
481 }
482
483 /*
484 * Initialize fs_table to contain the given set of mount points and
485 * projects. If mount_count is zero, mounts is ignored and the
486 * table is populated with mounted filesystems. If project_count is
487 * zero, projects is ignored and the table is populated with all
488 * projects defined in the projects file.
489 */
490 void
491 fs_table_initialise(
492 int mount_count,
493 char *mounts[],
494 int project_count,
495 char *projects[])
496 {
497 int error;
498 int i;
499
500 if (mount_count) {
501 for (i = 0; i < mount_count; i++)
502 fs_table_insert_mount(mounts[i]);
503 } else {
504 error = fs_table_initialise_mounts(NULL);
505 if (error)
506 goto out_error;
507 }
508 if (project_count) {
509 for (i = 0; i < project_count; i++)
510 fs_table_insert_project(projects[i]);
511 } else {
512 error = fs_table_initialise_projects(NULL);
513 if (error)
514 goto out_error;
515 }
516
517 return;
518
519 out_error:
520 fprintf(stderr, _("%s: cannot initialise path table: %s\n"),
521 progname, strerror(error));
522 }
523
524 void
525 fs_table_insert_project_path(
526 char *dir,
527 prid_t prid)
528 {
529 fs_path_t *fs;
530 int error = 0;
531
532 fs = fs_mount_point_from_path(dir);
533 if (fs)
534 error = fs_table_insert(dir, prid, FS_PROJECT_PATH,
535 fs->fs_name, NULL, NULL);
536 else
537 error = ENOENT;
538
539 if (error) {
540 fprintf(stderr, _("%s: cannot setup path for project dir %s: %s\n"),
541 progname, dir, strerror(error));
542 exit(1);
543 }
544 }