#include #include #include #include #include #include #include #include #include #include #include #include #include struct erofs_test_fid { uint16_t len; uint16_t pad; uint32_t nid_hi; uint32_t nid_lo; uint32_t gen; }; _Static_assert(sizeof(struct erofs_test_fid) == 16, "unexpected EROFS test file handle size"); _Static_assert(sizeof(struct erofs_test_fid) <= sizeof(struct fid), "EROFS test file handle does not fit struct fid"); static void usage(void) { fprintf(stderr, "usage:\n" " nfs_fh_tool capture path handle\n" " nfs_fh_tool lcapture path handle\n" " nfs_fh_tool describe handle\n" " nfs_fh_tool compare handle1 handle2\n" " nfs_fh_tool stat handle\n" " nfs_fh_tool cat handle output\n" " nfs_fh_tool mutate input output field value\n" " nfs_fh_tool expect-stat handle errno\n" " nfs_fh_tool expect-open handle errno\n"); exit(2); } static void read_handle(const char *path, fhandle_t *fh) { FILE *fp; fp = fopen(path, "rb"); if (fp == NULL) err(1, "fopen %s", path); if (fread(fh, sizeof(*fh), 1, fp) != 1) err(1, "fread %s", path); if (fgetc(fp) != EOF) errx(1, "%s has trailing data", path); if (fclose(fp) != 0) err(1, "fclose %s", path); } static void write_handle(const char *path, const fhandle_t *fh) { FILE *fp; fp = fopen(path, "wb"); if (fp == NULL) err(1, "fopen %s", path); if (fwrite(fh, sizeof(*fh), 1, fp) != 1) err(1, "fwrite %s", path); if (fclose(fp) != 0) err(1, "fclose %s", path); } static int parse_errno(const char *name) { char *end; long value; if (strcmp(name, "EINVAL") == 0) return (EINVAL); if (strcmp(name, "ESTALE") == 0) return (ESTALE); errno = 0; value = strtol(name, &end, 0); if (errno != 0 || *end != '\0' || value <= 0 || value > INT_MAX) errx(2, "invalid errno: %s", name); return ((int)value); } static uint64_t parse_value(const char *text) { char *end; uintmax_t value; errno = 0; value = strtoumax(text, &end, 0); if (errno != 0 || *end != '\0' || value > UINT64_MAX) errx(2, "invalid value: %s", text); return ((uint64_t)value); } static void capture(const char *path, const char *output, int nofollow) { fhandle_t fh; int error; bzero(&fh, sizeof(fh)); error = nofollow ? lgetfh(path, &fh) : getfh(path, &fh); if (error != 0) err(1, "%s %s", nofollow ? "lgetfh" : "getfh", path); write_handle(output, &fh); } static void describe(const char *path) { struct erofs_test_fid efid; fhandle_t fh; uint64_t nid; read_handle(path, &fh); bzero(&efid, sizeof(efid)); memcpy(&efid, &fh.fh_fid, sizeof(efid)); nid = ((uint64_t)efid.nid_hi << 32) | efid.nid_lo; printf("fsid=%08x:%08x len=%u pad=%u nid=%016" PRIx64 " gen=%u\n", (unsigned int)fh.fh_fsid.val[0], (unsigned int)fh.fh_fsid.val[1], efid.len, efid.pad, nid, efid.gen); } static void compare(const char *left, const char *right) { fhandle_t a, b; read_handle(left, &a); read_handle(right, &b); if (memcmp(&a, &b, sizeof(a)) != 0) errx(1, "file handles differ"); } static void stat_handle(const char *path) { fhandle_t fh; struct stat sb; read_handle(path, &fh); if (fhstat(&fh, &sb) != 0) err(1, "fhstat %s", path); printf("mode=%#o ino=%ju gen=%u size=%jd\n", (unsigned int)sb.st_mode, (uintmax_t)sb.st_ino, (unsigned int)sb.st_gen, (intmax_t)sb.st_size); } static void cat_handle(const char *handle, const char *output) { char buf[65536]; fhandle_t fh; ssize_t nr, nw; int fd, outfd; read_handle(handle, &fh); fd = fhopen(&fh, O_RDONLY); if (fd < 0) err(1, "fhopen %s", handle); outfd = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (outfd < 0) err(1, "open %s", output); while ((nr = read(fd, buf, sizeof(buf))) > 0) { for (ssize_t done = 0; done < nr; done += nw) { nw = write(outfd, buf + done, nr - done); if (nw < 0) err(1, "write %s", output); } } if (nr < 0) err(1, "read %s", handle); if (close(outfd) != 0) err(1, "close %s", output); if (close(fd) != 0) err(1, "close fhopen"); } static void mutate(const char *input, const char *output, const char *field, const char *text) { struct erofs_test_fid efid; fhandle_t fh; uint64_t value; read_handle(input, &fh); bzero(&efid, sizeof(efid)); memcpy(&efid, &fh.fh_fid, sizeof(efid)); value = parse_value(text); if (strcmp(field, "len") == 0) { if (value > UINT16_MAX) errx(2, "len is too large"); efid.len = value; } else if (strcmp(field, "pad") == 0) { if (value > UINT16_MAX) errx(2, "pad is too large"); efid.pad = value; } else if (strcmp(field, "nid_hi") == 0) { if (value > UINT32_MAX) errx(2, "nid_hi is too large"); efid.nid_hi = value; } else if (strcmp(field, "nid_lo") == 0) { if (value > UINT32_MAX) errx(2, "nid_lo is too large"); efid.nid_lo = value; } else if (strcmp(field, "gen") == 0) { if (value > UINT32_MAX) errx(2, "gen is too large"); efid.gen = value; } else if (strcmp(field, "gen_xor") == 0) { if (value == 0 || value > UINT32_MAX) errx(2, "gen_xor must be a non-zero uint32_t"); efid.gen ^= value; } else { errx(2, "unknown field: %s", field); } memcpy(&fh.fh_fid, &efid, sizeof(efid)); write_handle(output, &fh); } static void expect_failure(const char *path, const char *error_name, int use_open) { fhandle_t fh; struct stat sb; int expected, fd, result; read_handle(path, &fh); expected = parse_errno(error_name); errno = 0; if (use_open) { fd = fhopen(&fh, O_RDONLY); result = fd; if (fd >= 0) close(fd); } else { result = fhstat(&fh, &sb); } if (result != -1) errx(1, "%s unexpectedly succeeded", use_open ? "fhopen" : "fhstat"); if (errno != expected) errx(1, "%s returned %s, expected %s", use_open ? "fhopen" : "fhstat", strerror(errno), strerror(expected)); } int main(int argc, char **argv) { if (argc == 4 && strcmp(argv[1], "capture") == 0) capture(argv[2], argv[3], 0); else if (argc == 4 && strcmp(argv[1], "lcapture") == 0) capture(argv[2], argv[3], 1); else if (argc == 3 && strcmp(argv[1], "describe") == 0) describe(argv[2]); else if (argc == 4 && strcmp(argv[1], "compare") == 0) compare(argv[2], argv[3]); else if (argc == 3 && strcmp(argv[1], "stat") == 0) stat_handle(argv[2]); else if (argc == 4 && strcmp(argv[1], "cat") == 0) cat_handle(argv[2], argv[3]); else if (argc == 6 && strcmp(argv[1], "mutate") == 0) mutate(argv[2], argv[3], argv[4], argv[5]); else if (argc == 4 && strcmp(argv[1], "expect-stat") == 0) expect_failure(argv[2], argv[3], 0); else if (argc == 4 && strcmp(argv[1], "expect-open") == 0) expect_failure(argv[2], argv[3], 1); else usage(); return (0); }