/* * UNFS3 readdir routine * (C) 2004, Pascal Schmidt * see file LICENSE for license details */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "nfs.h" #include "mount.h" #include "fh.h" #include "readdir.h" #include "backend.h" #include "Config/exports.h" #include "error.h" /* * maximum number of entries in readdir results * * this is 4096 / 28 (the minimum size of an entry3) */ #define MAX_ENTRIES 143 /* * static READDIR3resok size with XDR overhead * * 88 bytes attributes, 8 bytes verifier, 4 bytes value_follows for * first entry, 4 bytes eof flag */ #define RESOK_SIZE 104 /* * static entry3 size with XDR overhead * * 8 bytes fileid, 4 bytes name length, 8 bytes cookie, 4 byte value_follows */ #define ENTRY_SIZE 24 /* * size of a name with XDR overhead * * XDR pads to multiple of 4 bytes */ #define NAME_SIZE(x) (((strlen((x))+3)/4)*4) /* * check if directory cookie is still valid */ static int cookie_check(time_t time, cookieverf3 verf) { return (int) (time == *(time_t *) verf); } uint32 directory_hash(const char *path) { DIR *search; struct dirent *this; uint32 hval = 0; search = backend_opendir(path); if (!search) { return 0; } while ((this = backend_readdir(search)) != NULL) { hval = fnv1a_32(this->d_name, hval); } backend_closedir(search); return hval; } /* * perform a READDIR operation * * fh_decomp must be called directly before to fill the stat cache */ READDIR3res read_dir(const char *path, cookie3 cookie, cookieverf3 verf, count3 count) { READDIR3res result; READDIR3resok resok; static entry3 entry[MAX_ENTRIES]; struct stat buf; int res; DIR *search; struct dirent *this; count3 i, real_count; static char obj[NFS_MAXPATHLEN * MAX_ENTRIES]; char scratch[NFS_MAXPATHLEN]; /* we refuse to return more than 4k from READDIR */ if (count > 4096) count = 4096; /* account for size of information heading resok structure */ real_count = RESOK_SIZE; /* check verifier against directory's modification time */ if (cookie != 0 && !cookie_check(st_cache.st_mtime, verf)) { result.status = NFS3ERR_BAD_COOKIE; return result; } /* compute new cookie verifier */ memset(verf, 0, NFS3_COOKIEVERFSIZE); *(time_t *) verf = st_cache.st_mtime; search = backend_opendir(path); if (!search) { if ((exports_opts & OPT_REMOVABLE) && (export_point(path))) { /* Removable media export point; probably no media inserted. Return empty directory. */ memset(resok.cookieverf, 0, NFS3_COOKIEVERFSIZE); resok.reply.entries = NULL; resok.reply.eof = TRUE; result.status = NFS3_OK; result.READDIR3res_u.resok = resok; return result; } else { result.status = readdir_err(); return result; } } this = backend_readdir(search); for (i = 0; i < cookie; i++) if (this) this = backend_readdir(search); i = 0; entry[0].name = NULL; while (this && real_count < count && i < MAX_ENTRIES) { if (i > 0) entry[i - 1].nextentry = &entry[i]; if (strlen(path) + strlen(this->d_name) + 1 < NFS_MAXPATHLEN) { if (strcmp(path, "/") == 0) sprintf(scratch, "/%s", this->d_name); else sprintf(scratch, "%s/%s", path, this->d_name); res = backend_lstat(scratch, &buf); if (res == -1) { result.status = readdir_err(); backend_closedir(search); return result; } strcpy(&obj[i * NFS_MAXPATHLEN], this->d_name); entry[i].fileid = ((uint64) buf.st_dev << 32) + buf.st_ino; entry[i].name = &obj[i * NFS_MAXPATHLEN]; entry[i].cookie = cookie + 1 + i; entry[i].nextentry = NULL; /* account for entry size */ real_count += ENTRY_SIZE + NAME_SIZE(this->d_name); /* whoops, overflowed the maximum size */ if (real_count > count && i > 0) entry[i - 1].nextentry = NULL; else { /* advance to next entry */ this = backend_readdir(search); } i++; } else { result.status = NFS3ERR_IO; backend_closedir(search); return result; } } backend_closedir(search); if (entry[0].name) resok.reply.entries = &entry[0]; else resok.reply.entries = NULL; if (this) resok.reply.eof = FALSE; else resok.reply.eof = TRUE; memcpy(resok.cookieverf, verf, NFS3_COOKIEVERFSIZE); result.status = NFS3_OK; result.READDIR3res_u.resok = resok; return result; }