284 lines
6.7 KiB
C
284 lines
6.7 KiB
C
#include <sys/param.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
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);
|
|
}
|