3 * UNFS3 low-level filehandle routines
4 * (C) 2004, Pascal Schmidt
5 * see file LICENSE for license details
10 #include <sys/types.h>
13 #include <sys/ioctl.h>
24 #if HAVE_LINUX_EXT2_FS_H == 1
27 * presence of linux/ext2_fs.h is a hint that we are on Linux, really
28 * including that file doesn't work on Debian, so define the ioctl
31 #define EXT2_IOC_GETVERSION 0x80047601
39 #include "Config/exports.h"
42 * hash function for inode numbers
44 #define FH_HASH(n) ((n ^ (n >> 8) ^ (n >> 16) ^ (n >> 24) ^ (n >> 32) ^ (n >> 40) ^ (n >> 48) ^ (n >> 56)) & 0xFF)
49 int st_cache_valid = FALSE;
50 backend_statstruct st_cache;
53 * --------------------------------
54 * INODE GENERATION NUMBER HANDLING
55 * --------------------------------
59 * obtain inode generation number if possible
61 * obuf: filled out stat buffer (must be given!)
62 * fd: open fd to file or FD_NONE (-1) if no fd open
63 * path: path to object in case we need to open it here
65 * returns 0 on failure
67 uint32 get_gen(backend_statstruct obuf, U(int fd), U(const char *path))
69 #if HAVE_STRUCT_STAT_ST_GEN == 1
73 #if HAVE_STRUCT_STAT_ST_GEN == 0 && HAVE_LINUX_EXT2_FS_H == 1
79 if (!S_ISREG(obuf.st_mode) && !S_ISDIR(obuf.st_mode))
82 euid = backend_geteuid();
83 egid = backend_getegid();
88 res = ioctl(fd, EXT2_IOC_GETVERSION, &gen);
92 newfd = backend_open(path, O_RDONLY);
96 res = ioctl(newfd, EXT2_IOC_GETVERSION, &gen);
104 backend_setegid(egid);
105 backend_seteuid(euid);
107 if (backend_geteuid() != euid || backend_getegid() != egid) {
108 logmsg(LOG_EMERG, "euid/egid switching failed, aborting");
115 #if HAVE_STRUCT_STAT_ST_GEN == 0 && HAVE_LINUX_EXT2_FS_H == 0
121 * --------------------------------
122 * FILEHANDLE COMPOSITION FUNCTIONS
123 * --------------------------------
127 * check whether an NFS filehandle is valid
129 int nfh_valid(nfs_fh3 fh)
131 unfs3_fh_t *obj = (void *) fh.data.data_val;
134 if (fh.data.data_len < FH_MINLEN)
137 /* encoded length different from real length? */
138 if (fh.data.data_len != fh_length(obj))
145 * check whether a filehandle is valid
147 int fh_valid(unfs3_fh_t fh)
149 /* invalid filehandles have zero device and inode */
150 return (int) (fh.dev != 0 || fh.ino != 0);
154 * invalid fh for error returns
157 static const unfs3_fh_t invalid_fh = {.dev = 0,.ino = 0,.gen = 0,.len =
161 static const unfs3_fh_t invalid_fh = { 0, 0, 0, 0, {0} };
165 * compose a filehandle for a given path
166 * path: path to compose fh for
167 * rqstp: If not NULL, generate special FHs for removables
168 * need_dir: if not 0, path must point to a directory
170 unfs3_fh_t fh_comp_raw(const char *path, struct svc_req *rqstp, int need_dir)
172 char work[NFS_MAXPATHLEN];
174 backend_statstruct buf;
181 /* special case for removable device export point: return preset fsid and
183 if (rqstp && export_point(path)) {
186 if (exports_options(path, rqstp, NULL, &fsid) == -1) {
187 /* Shouldn't happen, unless the exports file changed after the
188 call to export_point() */
191 if (exports_opts & OPT_REMOVABLE) {
193 /* There's a small risk that the file system contains other file
194 objects with st_ino = 1. This should be fairly uncommon,
195 though. The FreeBSD fs(5) man page says:
197 "The root inode is the root of the file system. Inode 0
198 cannot be used for normal purposes and historically bad blocks
199 were linked to inode 1, thus the root inode is 2 (inode 1 is
200 no longer used for this purpose, however numerous dump tapes
201 make this assumption, so we are stuck with it)."
203 In Windows, there's also a small risk that the hash ends up
211 res = backend_lstat(path, &buf);
215 /* check for dir if need_dir is set */
216 if (need_dir != 0 && !S_ISDIR(buf.st_mode))
221 fh.gen = backend_get_gen(buf, FD_NONE, path);
223 /* special case for root directory */
224 if (strcmp(path, "/") == 0)
232 last = strchr(last + 1, '/');
236 res = backend_lstat(work, &buf);
241 /* store 8 bit hash of the component's inode */
242 fh.inos[pos] = FH_HASH(buf.st_ino);
245 } while (last && pos < FH_MAXLEN);
247 if (last) /* path too deep for filehandle */
256 * get real length of a filehandle
258 u_int fh_length(const unfs3_fh_t * fh)
260 return fh->len + sizeof(fh->len) + sizeof(fh->dev) + sizeof(fh->ino) +
261 sizeof(fh->gen) + sizeof(fh->pwhash);
265 * extend a filehandle with a given device, inode, and generation number
267 unfs3_fh_t *fh_extend(nfs_fh3 nfh, uint32 dev, uint64 ino, uint32 gen)
269 static unfs3_fh_t new;
270 unfs3_fh_t *fh = (void *) nfh.data.data_val;
272 memcpy(&new, fh, fh_length(fh));
277 path = export_point_from_fsid(new.dev, NULL, NULL);
279 /* Our FH to extend refers to a removable device export point,
280 which lacks .inos. We need to construct a real FH to extend,
281 which can be done by passing rqstp=NULL to fh_comp_raw. */
282 new = fh_comp_raw(path, NULL, FH_ANY);
288 if (new.len == FH_MAXLEN)
294 new.pwhash = export_password_hash;
295 new.inos[new.len] = FH_HASH(ino);
302 * get post_op_fh3 extended by device, inode, and generation number
304 post_op_fh3 fh_extend_post(nfs_fh3 fh, uint32 dev, uint64 ino, uint32 gen)
309 new = fh_extend(fh, dev, ino, gen);
312 post.handle_follows = TRUE;
313 post.post_op_fh3_u.handle.data.data_len = fh_length(new);
314 post.post_op_fh3_u.handle.data.data_val = (char *) new;
316 post.handle_follows = FALSE;
322 * extend a filehandle given a path and needed type
324 post_op_fh3 fh_extend_type(nfs_fh3 fh, const char *path, unsigned int type)
327 backend_statstruct buf;
330 res = backend_lstat(path, &buf);
331 if (res == -1 || (buf.st_mode & type) != type) {
332 st_cache_valid = FALSE;
333 result.handle_follows = FALSE;
337 st_cache_valid = TRUE;
340 return fh_extend_post(fh, buf.st_dev, buf.st_ino,
341 backend_get_gen(buf, FD_NONE, path));
345 * -------------------------------
346 * FILEHANDLE RESOLUTION FUNCTIONS
347 * -------------------------------
351 * filehandles have the following fields:
352 * dev: device of the file system object fh points to
353 * ino: inode of the file system object fh points to
354 * gen: inode generation number, if available
355 * len: number of entries in following inos array
356 * inos: array of max FH_MAXLEN directories needed to traverse to reach
357 * object, for each name, an 8 bit hash of the inode number is stored
359 * - search functions traverse directory structure from the root looking
360 * for directories matching the inode information stored
361 * - if such a directory is found, we descend into it trying to locate the
366 * recursive directory search
367 * fh: filehandle being resolved
368 * pos: position in filehandles path inode array
369 * lead: current directory for search
370 * result: where to store path if seach is complete
372 static int fh_rec(const unfs3_fh_t * fh, int pos, const char *lead,
375 backend_dirstream *search;
376 struct dirent *entry;
377 backend_statstruct buf;
379 char obj[NFS_MAXPATHLEN];
381 /* There's a slight risk of multiple files with the same st_ino on
382 Windows. Take extra care and make sure that there are no collisions */
383 unsigned short matches = 0;
385 /* went in too deep? */
389 search = backend_opendir(lead);
393 entry = backend_readdir(search);
396 if (strlen(lead) + strlen(entry->d_name) + 1 < NFS_MAXPATHLEN) {
398 sprintf(obj, "%s/%s", lead, entry->d_name);
400 res = backend_lstat(obj, &buf);
406 if (buf.st_dev == fh->dev && buf.st_ino == fh->ino) {
407 /* found the object */
408 sprintf(result, "%s/%s", lead + 1, entry->d_name);
409 /* update stat cache */
410 st_cache_valid = TRUE;
418 if (strcmp(entry->d_name, "..") != 0 &&
419 strcmp(entry->d_name, ".") != 0 &&
420 FH_HASH(buf.st_ino) == fh->inos[pos]) {
422 * might be directory we're looking for,
423 * try descending into it
425 rec = fh_rec(fh, pos + 1, obj, result);
427 /* object was found in dir */
428 backend_closedir(search);
433 entry = backend_readdir(search);
436 backend_closedir(search);
444 logmsg(LOG_CRIT, "Hash collision detected for file %s!", result);
451 * resolve a filehandle into a path
453 char *fh_decomp_raw(const unfs3_fh_t * fh)
456 static char result[NFS_MAXPATHLEN];
462 /* special case for root directory */
466 rec = fh_rec(fh, 0, "/", result);
471 /* could not find object */