Added initial unfs3 sources for version 0.9.22+dfsg-1maemo2
[unfs3] / unfs3 / fh.c
1
2 /*
3  * UNFS3 low-level filehandle routines
4  * (C) 2004, Pascal Schmidt
5  * see file LICENSE for license details
6  */
7
8 #include "config.h"
9
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #ifndef WIN32
13 #include <sys/ioctl.h>
14 #include <syslog.h>
15 #endif                                 /* WIN32 */
16 #include <rpc/rpc.h>
17 #include <dirent.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23
24 #if HAVE_LINUX_EXT2_FS_H == 1
25
26 /*
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
29  * number here
30  */
31 #define EXT2_IOC_GETVERSION     0x80047601
32 #endif
33
34 #include "nfs.h"
35 #include "mount.h"
36 #include "daemon.h"
37 #include "fh.h"
38 #include "backend.h"
39 #include "Config/exports.h"
40
41 /*
42  * hash function for inode numbers
43  */
44 #define FH_HASH(n) ((n ^ (n >> 8) ^ (n >> 16) ^ (n >> 24) ^ (n >> 32) ^ (n >> 40) ^ (n >> 48) ^ (n >> 56)) & 0xFF)
45
46 /*
47  * stat cache
48  */
49 int st_cache_valid = FALSE;
50 backend_statstruct st_cache;
51
52 /*
53  * --------------------------------
54  * INODE GENERATION NUMBER HANDLING
55  * --------------------------------
56  */
57
58 /*
59  * obtain inode generation number if possible
60  *
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
64  *
65  * returns 0 on failure
66  */
67 uint32 get_gen(backend_statstruct obuf, U(int fd), U(const char *path))
68 {
69 #if HAVE_STRUCT_STAT_ST_GEN == 1
70     return obuf.st_gen;
71 #endif
72
73 #if HAVE_STRUCT_STAT_ST_GEN == 0 && HAVE_LINUX_EXT2_FS_H == 1
74     int newfd, res;
75     uint32 gen;
76     uid_t euid;
77     gid_t egid;
78
79     if (!S_ISREG(obuf.st_mode) && !S_ISDIR(obuf.st_mode))
80         return 0;
81
82     euid = backend_geteuid();
83     egid = backend_getegid();
84     backend_setegid(0);
85     backend_seteuid(0);
86
87     if (fd != FD_NONE) {
88         res = ioctl(fd, EXT2_IOC_GETVERSION, &gen);
89         if (res == -1)
90             gen = 0;
91     } else {
92         newfd = backend_open(path, O_RDONLY);
93         if (newfd == -1)
94             gen = 0;
95         else {
96             res = ioctl(newfd, EXT2_IOC_GETVERSION, &gen);
97             close(newfd);
98
99             if (res == -1)
100                 gen = 0;
101         }
102     }
103
104     backend_setegid(egid);
105     backend_seteuid(euid);
106
107     if (backend_geteuid() != euid || backend_getegid() != egid) {
108         logmsg(LOG_EMERG, "euid/egid switching failed, aborting");
109         daemon_exit(CRISIS);
110     }
111
112     return gen;
113 #endif
114
115 #if HAVE_STRUCT_STAT_ST_GEN == 0 && HAVE_LINUX_EXT2_FS_H == 0
116     return obuf.st_ino;
117 #endif
118 }
119
120 /*
121  * --------------------------------
122  * FILEHANDLE COMPOSITION FUNCTIONS
123  * --------------------------------
124  */
125
126 /*
127  * check whether an NFS filehandle is valid
128  */
129 int nfh_valid(nfs_fh3 fh)
130 {
131     unfs3_fh_t *obj = (void *) fh.data.data_val;
132
133     /* too small? */
134     if (fh.data.data_len < FH_MINLEN)
135         return FALSE;
136
137     /* encoded length different from real length? */
138     if (fh.data.data_len != fh_length(obj))
139         return FALSE;
140
141     return TRUE;
142 }
143
144 /*
145  * check whether a filehandle is valid
146  */
147 int fh_valid(unfs3_fh_t fh)
148 {
149     /* invalid filehandles have zero device and inode */
150     return (int) (fh.dev != 0 || fh.ino != 0);
151 }
152
153 /*
154  * invalid fh for error returns
155  */
156 #ifdef __GNUC__
157 static const unfs3_fh_t invalid_fh = {.dev = 0,.ino = 0,.gen = 0,.len =
158         0,.inos = {0}
159 };
160 #else
161 static const unfs3_fh_t invalid_fh = { 0, 0, 0, 0, {0} };
162 #endif
163
164 /*
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
169  */
170 unfs3_fh_t fh_comp_raw(const char *path, struct svc_req *rqstp, int need_dir)
171 {
172     char work[NFS_MAXPATHLEN];
173     unfs3_fh_t fh;
174     backend_statstruct buf;
175     int res;
176     char *last;
177     int pos = 0;
178
179     fh.len = 0;
180
181     /* special case for removable device export point: return preset fsid and 
182        inod 1. */
183     if (rqstp && export_point(path)) {
184         uint32 fsid;
185
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() */
189             return invalid_fh;
190         }
191         if (exports_opts & OPT_REMOVABLE) {
192             fh.dev = fsid;
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:
196
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)."
202
203                In Windows, there's also a small risk that the hash ends up
204                being exactly 1. */
205             fh.ino = 0x1;
206             fh.gen = 0;
207             return fh;
208         }
209     }
210
211     res = backend_lstat(path, &buf);
212     if (res == -1)
213         return invalid_fh;
214
215     /* check for dir if need_dir is set */
216     if (need_dir != 0 && !S_ISDIR(buf.st_mode))
217         return invalid_fh;
218
219     fh.dev = buf.st_dev;
220     fh.ino = buf.st_ino;
221     fh.gen = backend_get_gen(buf, FD_NONE, path);
222
223     /* special case for root directory */
224     if (strcmp(path, "/") == 0)
225         return fh;
226
227     strcpy(work, path);
228     last = work;
229
230     do {
231         *last = '/';
232         last = strchr(last + 1, '/');
233         if (last != NULL)
234             *last = 0;
235
236         res = backend_lstat(work, &buf);
237         if (res == -1) {
238             return invalid_fh;
239         }
240
241         /* store 8 bit hash of the component's inode */
242         fh.inos[pos] = FH_HASH(buf.st_ino);
243         pos++;
244
245     } while (last && pos < FH_MAXLEN);
246
247     if (last)                          /* path too deep for filehandle */
248         return invalid_fh;
249
250     fh.len = pos;
251
252     return fh;
253 }
254
255 /*
256  * get real length of a filehandle
257  */
258 u_int fh_length(const unfs3_fh_t * fh)
259 {
260     return fh->len + sizeof(fh->len) + sizeof(fh->dev) + sizeof(fh->ino) +
261         sizeof(fh->gen) + sizeof(fh->pwhash);
262 }
263
264 /*
265  * extend a filehandle with a given device, inode, and generation number
266  */
267 unfs3_fh_t *fh_extend(nfs_fh3 nfh, uint32 dev, uint64 ino, uint32 gen)
268 {
269     static unfs3_fh_t new;
270     unfs3_fh_t *fh = (void *) nfh.data.data_val;
271
272     memcpy(&new, fh, fh_length(fh));
273
274     if (new.len == 0) {
275         char *path;
276
277         path = export_point_from_fsid(new.dev, NULL, NULL);
278         if (path != 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);
283             if (!fh_valid(new))
284                 return NULL;
285         }
286     }
287
288     if (new.len == FH_MAXLEN)
289         return NULL;
290
291     new.dev = dev;
292     new.ino = ino;
293     new.gen = gen;
294     new.pwhash = export_password_hash;
295     new.inos[new.len] = FH_HASH(ino);
296     new.len++;
297
298     return &new;
299 }
300
301 /*
302  * get post_op_fh3 extended by device, inode, and generation number
303  */
304 post_op_fh3 fh_extend_post(nfs_fh3 fh, uint32 dev, uint64 ino, uint32 gen)
305 {
306     post_op_fh3 post;
307     unfs3_fh_t *new;
308
309     new = fh_extend(fh, dev, ino, gen);
310
311     if (new) {
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;
315     } else
316         post.handle_follows = FALSE;
317
318     return post;
319 }
320
321 /*
322  * extend a filehandle given a path and needed type
323  */
324 post_op_fh3 fh_extend_type(nfs_fh3 fh, const char *path, unsigned int type)
325 {
326     post_op_fh3 result;
327     backend_statstruct buf;
328     int res;
329
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;
334         return result;
335     }
336
337     st_cache_valid = TRUE;
338     st_cache = buf;
339
340     return fh_extend_post(fh, buf.st_dev, buf.st_ino,
341                           backend_get_gen(buf, FD_NONE, path));
342 }
343
344 /*
345  * -------------------------------
346  * FILEHANDLE RESOLUTION FUNCTIONS
347  * -------------------------------
348  */
349
350 /*
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
358  *
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
362  *   object
363  */
364
365 /*
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
371  */
372 static int fh_rec(const unfs3_fh_t * fh, int pos, const char *lead,
373                   char *result)
374 {
375     backend_dirstream *search;
376     struct dirent *entry;
377     backend_statstruct buf;
378     int res, rec;
379     char obj[NFS_MAXPATHLEN];
380
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;
384
385     /* went in too deep? */
386     if (pos == fh->len)
387         return FALSE;
388
389     search = backend_opendir(lead);
390     if (!search)
391         return FALSE;
392
393     entry = backend_readdir(search);
394
395     while (entry) {
396         if (strlen(lead) + strlen(entry->d_name) + 1 < NFS_MAXPATHLEN) {
397
398             sprintf(obj, "%s/%s", lead, entry->d_name);
399
400             res = backend_lstat(obj, &buf);
401             if (res == -1) {
402                 buf.st_dev = 0;
403                 buf.st_ino = 0;
404             }
405
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;
411                 st_cache = buf;
412                 matches++;
413 #ifndef WIN32
414                 break;
415 #endif
416             }
417
418             if (strcmp(entry->d_name, "..") != 0 &&
419                 strcmp(entry->d_name, ".") != 0 &&
420                 FH_HASH(buf.st_ino) == fh->inos[pos]) {
421                 /* 
422                  * might be directory we're looking for,
423                  * try descending into it
424                  */
425                 rec = fh_rec(fh, pos + 1, obj, result);
426                 if (rec) {
427                     /* object was found in dir */
428                     backend_closedir(search);
429                     return TRUE;
430                 }
431             }
432         }
433         entry = backend_readdir(search);
434     }
435
436     backend_closedir(search);
437     switch (matches) {
438         case 0:
439             return FALSE;
440         case 1:
441             return TRUE;
442         default:
443 #ifdef WIN32
444             logmsg(LOG_CRIT, "Hash collision detected for file %s!", result);
445 #endif
446             return FALSE;
447     }
448 }
449
450 /*
451  * resolve a filehandle into a path
452  */
453 char *fh_decomp_raw(const unfs3_fh_t * fh)
454 {
455     int rec = 0;
456     static char result[NFS_MAXPATHLEN];
457
458     /* valid fh? */
459     if (!fh)
460         return NULL;
461
462     /* special case for root directory */
463     if (fh->len == 0)
464         return "/";
465
466     rec = fh_rec(fh, 0, "/", result);
467
468     if (rec)
469         return result;
470
471     /* could not find object */
472     return NULL;
473 }