bookmarkctl: add sub-commands for xattr

Provides platform-agnostic command-line interface for managing
extended attributes on a BookmarkFS filesystem.
This commit is contained in:
CismonX 2025-03-15 17:25:26 +08:00
parent 402cc1a304
commit 7ae2b283c3
No known key found for this signature in database
GPG key ID: 3094873E29A482FB
4 changed files with 572 additions and 9 deletions

View file

@ -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

View file

@ -24,6 +24,7 @@
# include "config.h"
#endif
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@ -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 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)) {
@ -82,6 +109,9 @@ print_help (void)
"Main commands:\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,

254
src/xattr.c Normal file
View file

@ -0,0 +1,254 @@
/**
* bookmarkfs/src/xattr.c
* ----
*
* Copyright (C) 2025 CismonX <admin@cismon.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "xattr.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#if defined(__linux__)
# include <sys/xattr.h>
#elif defined(__FreeBSD__)
# include <sys/extattr.h>
#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;
}

62
src/xattr.h Normal file
View file

@ -0,0 +1,62 @@
/**
* bookmarkfs/src/xattr.h
* ----
*
* Copyright (C) 2025 CismonX <admin@cismon.net>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef BOOKMARKFS_XATTR_H_
#define BOOKMARKFS_XATTR_H_
#include <stddef.h>
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_) */