diff --git a/src/Makefile.am b/src/Makefile.am index 6f1f63e..4c70eb5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,7 +9,8 @@ bin_PROGRAMS = pkginclude_HEADERS = backend.h common.h ioctl.h fsck_handler.h version.h noinst_HEADERS = backend_util.h db.h defs.h frontend_util.h fs_ops.h \ - fsck_ops.h fsck_util.h json.h lib.h macros.h uuid.h xstd.h + fsck_ops.h fsck_util.h json.h lib.h macros.h uuid.h \ + xattr.h xstd.h lib_LTLIBRARIES = pkglib_LTLIBRARIES = @@ -56,7 +57,7 @@ if BOOKMARKCTL bin_PROGRAMS += bookmarkctl bookmarkctl_CPPFLAGS = $(BASE_CPPFLAGS_) - bookmarkctl_SOURCES = bookmarkctl.c fsck_util.c + bookmarkctl_SOURCES = bookmarkctl.c fsck_util.c xattr.c xstd.c endif # BOOKMARKCTL if BOOKMARKFS_FSCK diff --git a/src/bookmarkctl.c b/src/bookmarkctl.c index c30dbd5..fd9e1a3 100644 --- a/src/bookmarkctl.c +++ b/src/bookmarkctl.c @@ -24,6 +24,7 @@ # include "config.h" #endif +#include #include #include #include @@ -37,14 +38,34 @@ #include "ioctl.h" #include "macros.h" #include "version.h" +#include "xattr.h" #include "xstd.h" +#define BMCTL_XATTR_GET_NOEOL (1u << 0) +#define BMCTL_XATTR_GET_BINARY (1u << 1) +#define BMCTL_XATTR_GET_MULTI (1u << 2) +#define BMCTL_XATTR_GET_QUIET (1u << 3) + +struct xattr_get_ctx { + char const *prefix; + char sep; + char eol; + uint32_t flags; +}; + // Forward declaration start -static int dispatch_subcmds (int, char *[]); -static void print_help (void); -static void print_version (void); -static int subcmd_fsck (int, char *[]); -static int subcmd_permd (int, char *[]); +static int dispatch_subcmds (int, char *[]); +static void print_help (void); +static void print_version (void); +static int subcmd_fsck (int, char *[]); +static int subcmd_permd (int, char *[]); +static int subcmd_xattr_get (int, char *[]); +static int subcmd_xattr_list (int, char *[]); +static int subcmd_xattr_set (int, char *[]); +static int xattr_get_cb (void *, void *, size_t); +static int xattr_get_one (char const *, char *[], int, + struct xattr_get_ctx *); +static int xattr_list_cb (void *, void *, size_t); // Forward declaration end static int @@ -61,6 +82,12 @@ dispatch_subcmds ( int status = 0; if (0 == strcmp("permd", cmd)) { status = subcmd_permd(argc, argv); + } else if (0 == strcmp("xattr-get", cmd)) { + status = subcmd_xattr_get(argc, argv); + } else if (0 == strcmp("xattr-set", cmd)) { + status = subcmd_xattr_set(argc, argv); + } else if (0 == strcmp("xattr-list", cmd)) { + status = subcmd_xattr_list(argc, argv); } else if (0 == strcmp("fsck", cmd)) { status = subcmd_fsck(argc, argv); } else if (0 == strcmp("help", cmd)) { @@ -80,8 +107,11 @@ print_help (void) puts("Usage: bookmarkctl [args]\n" "\n" "Main commands:\n" - " permd Permute directory entries\n" - " fsck Check filesystem\n" + " permd Permute directory entries\n" + " fsck Check filesystem\n" + " xattr-list List extended attribute names\n" + " xattr-get Get extended attribute value\n" + " xattr-set Set extended attribute value\n" "\n" "Other commands:\n" " help Print help message\n" @@ -202,6 +232,222 @@ subcmd_permd ( return status; } +static int +subcmd_xattr_get ( + int argc, + char *argv[] +) { + struct xattr_get_ctx ctx = { + .sep = '\t', + .eol = '\n', + }; + + OPT_START(argc, argv, "n:Nbmqs:") + OPT_OPT('n') { + ctx.eol = optarg[0]; + break; + } + OPT_OPT('N') { + ctx.flags |= BMCTL_XATTR_GET_NOEOL; + break; + } + OPT_OPT('b') { + ctx.flags |= BMCTL_XATTR_GET_BINARY; + break; + } + OPT_OPT('m') { + ctx.flags |= BMCTL_XATTR_GET_MULTI; + break; + } + OPT_OPT('q') { + ctx.flags |= BMCTL_XATTR_GET_QUIET; + break; + } + OPT_OPT('s') { + ctx.sep = optarg[0]; + break; + } + OPT_NOVAL + OPT_END + + if (argc < 2) { + log_puts("xattr-get: xattr name and file path must be specified"); + return -1; + } + + if (ctx.flags & BMCTL_XATTR_GET_MULTI) { + return xattr_get_one(argv[argc - 1], argv, argc - 1, &ctx); + } + for (int i = 1; i < argc; ++i) { + char const *path = argv[i]; + if (0 != xattr_get_one(path, argv, 1, &ctx)) { + return -1; + } + } + return 0; +} + +static int +subcmd_xattr_list ( + int argc, + char *argv[] +) { + if (--argc != 1) { + if (argc < 1) { + log_puts("xattr-set: file path must be provided"); + } else { + log_puts("xattr-set: too many arguments"); + } + return -1; + } + char const *path = *(++argv); + + int fd = bookmarkfs_xattr_open(path); + if (fd < 0) { + return -1; + } + int status = bookmarkfs_xattr_list(fd, xattr_list_cb, NULL); + close(fd); + return status; +} + +static int +subcmd_xattr_set ( + int argc, + char *argv[] +) { + char *val = NULL; + + OPT_START(argc, argv, "v:") + OPT_OPT('v') { + val = optarg; + break; + } + OPT_NOVAL + OPT_END + + if (argc != 2) { + if (argc < 2) { + log_puts("xattr-set: xattr name and file path must be specified"); + } else { + log_puts("xattr-set: too many arguments"); + } + return -1; + } + char const *name = argv[0]; + char const *path = argv[1]; + + int fd = bookmarkfs_xattr_open(path); + if (fd < 0) { + return -1; + } + int status = -1; + + size_t val_size = 0; + char *buf = val; + if (buf == NULL) { + size_t buf_size = 4096; + buf = xmalloc(buf_size); + do { + if (val_size == buf_size) { + buf_size += buf_size >> 1; + buf = xrealloc(buf, buf_size); + } + val_size += fread(buf + val_size, 1, buf_size - val_size, stdin); + if (ferror(stdin)) { + log_printf("fread(): %s", strerror(errno)); + goto end; + } + } while (!feof(stdin)); + } else { + val_size = strlen(buf); + } + status = bookmarkfs_xattr_set(fd, name, buf, val_size); + + end: + if (val == NULL) { + free(buf); + } + close(fd); + return status; +} + +static int +xattr_get_cb ( + void *user_data, + void *buf, + size_t buf_len +) { + struct xattr_get_ctx const *ctx = user_data; + + uint32_t flags = ctx->flags; + if (!(flags & BMCTL_XATTR_GET_QUIET)) { + if (0 > printf("%s%c", ctx->prefix, ctx->sep)) { + log_printf("printf(): %s", strerror(errno)); + return -1; + } + } + if (!(flags & BMCTL_XATTR_GET_BINARY)) { + for (unsigned char *s = buf, *end = s; s < end; ++s) { + if (iscntrl(*s)) { + *s = '?'; + } + } + } + if (buf_len != fwrite(buf, 1, buf_len, stdout)) { + log_printf("fwrite(): %s", strerror(errno)); + return -1; + } + if (!(flags & BMCTL_XATTR_GET_NOEOL)) { + if (EOF == fputc(ctx->eol, stdout)) { + log_printf("fputc(): %s", strerror(errno)); + return -1; + } + } + return 0; +} + +static int +xattr_get_one ( + char const *path, + char *names[], + int names_cnt, + struct xattr_get_ctx *ctx +) { + int fd = bookmarkfs_xattr_open(path); + if (fd < 0) { + return -1; + } + + int status; + for (int i = 0; i < names_cnt; ++i) { + char const *name = names[i]; + + ctx->prefix = (ctx->flags & BMCTL_XATTR_GET_MULTI) ? name : path; + status = bookmarkfs_xattr_get(fd, name, xattr_get_cb, ctx); + if (status < 0) { + goto end; + } + } + + end: + close(fd); + return status; +} + +static int +xattr_list_cb ( + void *UNUSED_VAR(user_data), + void *buf, + size_t buf_len +) { + if (0 > printf("%.*s\n", (int)buf_len, (char *)buf)) { + log_printf("printf(): %s", strerror(errno)); + return -1; + } + return 0; +} + int main ( int argc, diff --git a/src/xattr.c b/src/xattr.c new file mode 100644 index 0000000..f7e696c --- /dev/null +++ b/src/xattr.c @@ -0,0 +1,254 @@ +/** + * bookmarkfs/src/xattr.c + * ---- + * + * Copyright (C) 2025 CismonX + * + * This file is part of BookmarkFS. + * + * BookmarkFS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BookmarkFS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BookmarkFS. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "xattr.h" + +#include +#include +#include + +#include +#if defined(__linux__) +# include +#elif defined(__FreeBSD__) +# include +#endif + +#include "xstd.h" + +#if defined(__linux__) +# define XATTR_NAME_PREFIX BOOKMARKFS_XATTR_PREFIX +#elif defined(__FreeBSD__) +# define XATTR_NAME_PREFIX ( &BOOKMARKFS_XATTR_PREFIX[strlen("user.")] ) +#endif + +// Forward declaration start +static ssize_t xattr_do_get (int, char const *, void *, size_t); +static ssize_t xattr_do_list (int, void *, size_t); +static int xattr_do_set (int, char const *, void const *, size_t); +static char * xattr_normalize_name (char const *); +// Forward declaration end + +static ssize_t +xattr_do_get ( + int fd, + char const *name, + void *buf, + size_t buf_len +) { + ssize_t result; +#if defined(__linux__) + result = fgetxattr(fd, name, buf, buf_len); + if (result < 0) { + log_printf("fgetxattr(): %s", strerror(errno)); + } +#elif defined(__FreeBSD__) + result = extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name, buf, buf_len); + if (result < 0) { + log_printf("extattr_get_fd(): %s", strerror(errno)); + } +#else +# error "not implemented" +#endif + return result; +} + +static ssize_t +xattr_do_list ( + int fd, + void *buf, + size_t buf_len +) { + ssize_t result; +#if defined(__linux__) + result = flistxattr(fd, buf, buf_len); + if (result < 0) { + log_printf("flistxattr(): %s", strerror(errno)); + } +#elif defined(__FreeBSD__) + result = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, buf, buf_len); + if (result < 0) { + log_printf("extattr_list_fd(): %s", strerror(errno)); + } +#else +# error "not implemented" +#endif + return result; +} + +static int +xattr_do_set ( + int fd, + char const *name, + void const *buf, + size_t buf_len +) { +#if defined(__linux__) + if (0 != fsetxattr(fd, name, buf, buf_len, 0)) { + log_printf("fsetxattr(): %s", strerror(errno)); + return -1; + } +#elif defined(__FreeBSD__) + if (0 > extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, name, buf, buf_len)) { + log_printf("extattr_set_fd(): %s", strerror(errno)); + return -1; + } +#else +# error "not implemented" +#endif + return 0; +} + +static char * +xattr_normalize_name ( + char const *name +) { + char const *prefix = XATTR_NAME_PREFIX; + size_t prefix_len = strlen(prefix); + size_t name_len = strlen(name); + + char *new_name = xmalloc(prefix_len + name_len + 1); + memcpy(new_name, prefix, prefix_len); + memcpy(new_name + prefix_len, name, name_len + 1); + return new_name; +} + +int +bookmarkfs_xattr_get ( + int fd, + char const *name, + bookmarkfs_xattr_cb *callback, + void *user_data +) { + char *real_name = xattr_normalize_name(name); + void *buf = NULL; + ssize_t result; + do { + result = xattr_do_get(fd, real_name, NULL, 0); + if (result < 0) { + break; + } + + buf = xrealloc(buf, result); + result = xattr_do_get(fd, real_name, buf, result); + if (unlikely(result < 0)) { + if (result == -ERANGE) { + continue; + } + break; + } + + result = callback(user_data, buf, result); + } while (0); + + free(real_name); + free(buf); + return result; +} + +int +bookmarkfs_xattr_list ( + int fd, + bookmarkfs_xattr_cb *callback, + void *user_data +) { + char *buf = NULL; + ssize_t result; + do { + result = xattr_do_list(fd, NULL, 0); + if (result < 0) { + goto end; + } + + buf = xrealloc(buf, result); + result = xattr_do_list(fd, buf, result); + if (unlikely(result < 0)) { + if (result == -ERANGE) { + continue; + } + goto end; + } + } while (0); + + char const *prefix = XATTR_NAME_PREFIX; + size_t prefix_len = strlen(prefix); + for (char *curr = buf, *end = curr + result; curr < end; ) { + char *name; + size_t name_len; +#if defined(__linux__) + name = curr; + name_len = strlen(name); + curr += name_len + 1; +#elif defined(__FreeBSD__) + name_len = (unsigned char)(*(curr++)); + name = curr; + curr += name_len; +#else +# error "not implemented" +#endif + if (name_len <= prefix_len || 0 != memcmp(prefix, name, prefix_len)) { + continue; + } + result = callback(user_data, name + prefix_len, name_len - prefix_len); + if (result != 0) { + goto end; + } + } + + end: + free(buf); + return result; +} + +int +bookmarkfs_xattr_open ( + char const *path +) { + int flags = O_RDONLY; +#if defined(__FreeBSD__) + // Linux does not accept O_PATH fd for f*xattr() calls. + flags |= O_PATH; +#endif + int fd = open(path, flags); + if (fd < 0) { + log_printf("open(): %s: %s", path, strerror(errno)); + return -1; + } + return fd; +} + +int +bookmarkfs_xattr_set ( + int fd, + char const *name, + void const *buf, + size_t buf_len +) { + char *real_name = xattr_normalize_name(name); + int status = xattr_do_set(fd, real_name, buf, buf_len); + free(real_name); + return status; +} diff --git a/src/xattr.h b/src/xattr.h new file mode 100644 index 0000000..683e357 --- /dev/null +++ b/src/xattr.h @@ -0,0 +1,62 @@ +/** + * bookmarkfs/src/xattr.h + * ---- + * + * Copyright (C) 2025 CismonX + * + * This file is part of BookmarkFS. + * + * BookmarkFS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * BookmarkFS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with BookmarkFS. If not, see . + */ + +#ifndef BOOKMARKFS_XATTR_H_ +#define BOOKMARKFS_XATTR_H_ + +#include + +typedef int (bookmarkfs_xattr_cb) ( + void *user_data, + void *buf, + size_t buf_len +); + +int +bookmarkfs_xattr_get ( + int fd, + char const *name, + bookmarkfs_xattr_cb *callback, + void *user_data +); + +int +bookmarkfs_xattr_list ( + int fd, + bookmarkfs_xattr_cb *callback, + void *user_data +); + +int +bookmarkfs_xattr_open ( + char const *path +); + +int +bookmarkfs_xattr_set ( + int fd, + char const *name, + void const *buf, + size_t buf_len +); + +#endif /* !defined(BOOKMARKFS_XATTR_H_) */