Compare commits

...

12 commits

Author SHA1 Message Date
CismonX
6f2a00dd6f
fs_ops: refactor setattr
- Only perform one operation per call.
- Always fail with EPERM if given unsupported flags.
2025-06-09 19:51:25 +08:00
CismonX
c23623b5a3
test: add tests for file atime/mtime 2025-06-08 12:34:10 +08:00
CismonX
fdfd6fe069
backend_firefox: fix directory attr update 2025-06-08 07:24:39 +08:00
CismonX
5082e7c67c
fs_ops: fix mtime update of regular files
Explicit mtime update (e.g., via futimens(2)) should override
file handle mtime.
2025-06-07 19:55:27 +08:00
CismonX
72b9e200d9
fs_ops: misc refactor
- Use nodeid for fh_map keys as well as bm_*() and intfs_*() args.
- bm_readdir(), fs_op_rename(): check subsys type more gracefully.
- ...
2025-06-07 08:57:48 +08:00
CismonX
29338ca02b
backend_firefox: purge dangling refs on delete
When a bookmark is deleted, if there are no other bookmarks
referencing the corresponding `moz_place` entry, tag and keyword
references to that entry are considered "dangling" references,
and shall be automatically removed.

Also reverts commit b5fa6960ef,
since the NULL title check is no longer necessary.
2025-06-06 22:46:44 +08:00
CismonX
eb426f9fc4
bookmarkctl: xattr-get: rename -m option to -a 2025-06-06 19:41:09 +08:00
CismonX
b5fa6960ef
backend_firefox: hide dangling keywords
When a bookmark associated with a keyword is deleted,
there may still be dangling references (e.g., tags) to the
corresponding `moz_places` entry.

By filtering out NULL titles, `bookmark_lookup()` and
`bookmark_list()` now only give keywords associated with
valid bookmarks.
2025-06-05 08:27:59 +08:00
CismonX
6fb9438d3c
fs_ops: fix opendir for keyword directory 2025-06-05 07:39:53 +08:00
CismonX
c1cf9db2a1
test: add tests for tags and keywords 2025-06-02 09:20:19 +08:00
CismonX
495f8592e6
fs_ops: fix deletion of tag directories 2025-06-02 08:41:33 +08:00
CismonX
d5eae85774
test: refactor filesystem tests 2025-06-01 21:35:07 +08:00
13 changed files with 766 additions and 365 deletions

View file

@ -32,7 +32,7 @@ bookmarkctl - manage a mounted BookmarkFS filesystem
.B bookmarkctl .B bookmarkctl
.B xattr\-get .B xattr\-get
.RI [ options ] .RI [ options ]
.B \-m .B \-a
.IR attrname "... " pathname .IR attrname "... " pathname
.PP .PP
.B bookmarkctl .B bookmarkctl
@ -145,7 +145,7 @@ Treat the value as binary, and print it verbatim.
.IP .IP
If this option is not provided, non-printable characters are replaced with '?'. If this option is not provided, non-printable characters are replaced with '?'.
.TP .TP
.B \-m .B \-a
Switch to multi-attrname mode, where multiple extended attribute names Switch to multi-attrname mode, where multiple extended attribute names
can be specified instead of multiple files. can be specified instead of multiple files.
.IP .IP

View file

@ -630,7 +630,7 @@ Displays extended attribute values.
@example @example
bookmarkctl xattr-get [@var{options}] @var{attrname} @var{pathname}... bookmarkctl xattr-get [@var{options}] @var{attrname} @var{pathname}...
bookmarkctl xattr-get [@var{options}] -m @var{attrname}... @var{pathname} bookmarkctl xattr-get [@var{options}] -a @var{attrname}... @var{pathname}
@end example @end example
@table @var @table @var
@ -650,7 +650,7 @@ Treat the value as binary, and print it verbatim.
If this option is not provided, non-printable characters are replaced with If this option is not provided, non-printable characters are replaced with
@samp{?}. @samp{?}.
@item -m @item -a
Switch to multi-attrname mode, where multiple extended attribute names Switch to multi-attrname mode, where multiple extended attribute names
can be specified instead of multiple files. can be specified instead of multiple files.

View file

@ -133,11 +133,14 @@ enum {
STMT_MOZBM_MTIME_UPDATE, STMT_MOZBM_MTIME_UPDATE,
STMT_MOZBM_POS_SHIFT, STMT_MOZBM_POS_SHIFT,
STMT_MOZBM_POS_UPDATE, STMT_MOZBM_POS_UPDATE,
STMT_MOZBM_PURGE,
STMT_MOZBM_PURGE_CHECK,
STMT_MOZBM_UPDATE, STMT_MOZBM_UPDATE,
STMT_MOZBMDEL_INSERT, STMT_MOZBMDEL_INSERT,
STMT_MOZKW_DELETE, STMT_MOZKW_DELETE,
STMT_MOZKW_INSERT, STMT_MOZKW_INSERT,
STMT_MOZKW_LOOKUP, STMT_MOZKW_LOOKUP,
STMT_MOZKW_PURGE,
STMT_MOZKW_RENAME, STMT_MOZKW_RENAME,
STMT_TAG_ENTRY_LOOKUP, STMT_TAG_ENTRY_LOOKUP,
#endif /* defined(BOOKMARKFS_BACKEND_FIREFOX_WRITE) */ #endif /* defined(BOOKMARKFS_BACKEND_FIREFOX_WRITE) */
@ -288,7 +291,7 @@ static bool is_valid_guid (char const *, size_t);
static int keyword_create (struct backend_ctx *, char const *, size_t, static int keyword_create (struct backend_ctx *, char const *, size_t,
struct bookmarkfs_bookmark_stat *); struct bookmarkfs_bookmark_stat *);
static int mozbm_check_cb (void *, sqlite3_stmt *); static int mozbm_check_cb (void *, sqlite3_stmt *);
static int mozbm_delete (struct backend_ctx *, int64_t, bool); static int mozbm_delete (struct backend_ctx *, int64_t, bool, bool);
static int mozbm_delete_cb (void *, sqlite3_stmt *); static int mozbm_delete_cb (void *, sqlite3_stmt *);
static int mozbm_get_title (struct backend_ctx *, int64_t, int64_t, static int mozbm_get_title (struct backend_ctx *, int64_t, int64_t,
db_query_row_func *, void *); db_query_row_func *, void *);
@ -302,11 +305,14 @@ static int mozbm_mtime_update (struct backend_ctx *, int64_t, int64_t *);
static int mozbm_pos_shift (struct backend_ctx *, int64_t, int64_t, static int mozbm_pos_shift (struct backend_ctx *, int64_t, int64_t,
int64_t *, enum bookmarkfs_permd_op); int64_t *, enum bookmarkfs_permd_op);
static int mozbm_pos_update (struct backend_ctx *, int64_t, int64_t); static int mozbm_pos_update (struct backend_ctx *, int64_t, int64_t);
static int mozbm_purge (struct backend_ctx *, int64_t);
static int mozbm_purge_check (struct backend_ctx *, int64_t);
static int mozbm_update (struct backend_ctx *, struct mozbm *); static int mozbm_update (struct backend_ctx *, struct mozbm *);
static int mozbmdel_insert (struct backend_ctx *, char const *); static int mozbmdel_insert (struct backend_ctx *, char const *);
static int mozkw_delete (struct backend_ctx *, char const *, size_t); static int mozkw_delete (struct backend_ctx *, char const *, size_t);
static int mozkw_insert (struct backend_ctx *, struct mozkw *); static int mozkw_insert (struct backend_ctx *, struct mozkw *);
static int mozkw_lookup (struct backend_ctx *, struct mozkw *); static int mozkw_lookup (struct backend_ctx *, struct mozkw *);
static int mozkw_purge (struct backend_ctx *, int64_t);
static int mozkw_rename (struct backend_ctx *, char const *, static int mozkw_rename (struct backend_ctx *, char const *,
char const *, uint32_t); char const *, uint32_t);
static int mozorigin_delete (struct backend_ctx *, int64_t); static int mozorigin_delete (struct backend_ctx *, int64_t);
@ -318,8 +324,9 @@ static int mozplace_addref (struct backend_ctx *, char const *, size_t,
static int mozplace_addref_cb (void *, sqlite3_stmt *); static int mozplace_addref_cb (void *, sqlite3_stmt *);
static int mozplace_addref_id (struct backend_ctx *, int64_t); static int mozplace_addref_id (struct backend_ctx *, int64_t);
static int mozplace_delete (struct backend_ctx *, int64_t, int64_t); static int mozplace_delete (struct backend_ctx *, int64_t, int64_t);
static int mozplace_delref (struct backend_ctx *, int64_t); static int mozplace_delref (struct backend_ctx *, int64_t, int);
static int mozplace_insert (struct backend_ctx *, struct mozplace *); static int mozplace_insert (struct backend_ctx *, struct mozplace *);
static int mozplace_purge (struct backend_ctx *, int64_t);
static int mozplace_update (struct backend_ctx *, struct mozplace *); static int mozplace_update (struct backend_ctx *, struct mozplace *);
static int64_t mozplace_url_hash (char const *, size_t); static int64_t mozplace_url_hash (char const *, size_t);
static int parse_mkfsopts (struct bookmarkfs_conf_opt const *, static int parse_mkfsopts (struct bookmarkfs_conf_opt const *,
@ -457,7 +464,7 @@ bookmark_do_delete (
return is_dir ? -ENOTDIR : -EISDIR; return is_dir ? -ENOTDIR : -EISDIR;
} }
status = mozbm_delete(ctx, cols.id, is_dir); status = mozbm_delete(ctx, cols.id, is_dir, true);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -675,7 +682,8 @@ static int
mozbm_delete ( mozbm_delete (
struct backend_ctx *ctx, struct backend_ctx *ctx,
int64_t id, int64_t id,
bool is_dir bool is_dir,
bool purge
) { ) {
#define MOZBM_DELETE_(cond) \ #define MOZBM_DELETE_(cond) \
"DELETE FROM `moz_bookmarks` WHERE `id` = ? " cond \ "DELETE FROM `moz_bookmarks` WHERE `id` = ? " cond \
@ -707,7 +715,7 @@ mozbm_delete (
// The ID is alwayed obtained from a previous query in // The ID is alwayed obtained from a previous query in
// the same transaction. This shall not happen. // the same transaction. This shall not happen.
xassert(nrows > 0); xassert(nrows > 0);
int status = mozplace_delref(ctx, qctx.place_id); int status = mozplace_delref(ctx, qctx.place_id, purge ? 0 : 1);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -1020,6 +1028,45 @@ mozbm_pos_update (
return 1; return 1;
} }
static int
mozbm_purge (
struct backend_ctx *ctx,
int64_t place_id
) {
sqlite3_stmt **stmt_ptr = &ctx->stmts[STMT_MOZBM_PURGE];
char const *sql = "DELETE FROM `moz_bookmarks` WHERE `fk` = ?";
int status;
DO_QUERY(ctx, stmt_ptr, sql, NULL, NULL, status, , ,
DB_QUERY_BIND_INT64(place_id),
);
if (status < 0) {
return status;
}
return sqlite3_changes(ctx->db);
}
static int
mozbm_purge_check (
struct backend_ctx *ctx,
int64_t place_id
) {
sqlite3_stmt **stmt_ptr = &ctx->stmts[STMT_MOZBM_PURGE_CHECK];
char const *sql = "SELECT COUNT(*) FROM `moz_bookmarks` "
"WHERE `fk` = ? AND `title` IS NOT NULL";
int64_t result;
ssize_t nrows;
DO_QUERY(ctx, stmt_ptr, sql, db_query_i64_cb, &result, nrows, , ,
DB_QUERY_BIND_INT64(place_id),
);
if (nrows < 0) {
return nrows;
}
debug_assert(nrows == 1);
return result == 0;
}
static int static int
mozbm_update ( mozbm_update (
struct backend_ctx *ctx, struct backend_ctx *ctx,
@ -1098,7 +1145,7 @@ mozkw_delete (
if (status < 0) { if (status < 0) {
return status; return status;
} }
status = mozplace_delref(ctx, place_id); status = mozplace_delref(ctx, place_id, 1);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -1153,6 +1200,24 @@ mozkw_lookup (
return 0; return 0;
} }
static int
mozkw_purge (
struct backend_ctx *ctx,
int64_t place_id
) {
sqlite3_stmt **stmt_ptr = &ctx->stmts[STMT_MOZKW_PURGE];
char const *sql = "DELETE FROM `moz_keywords` WHERE `place_id` = ?";
int status;
DO_QUERY(ctx, stmt_ptr, sql, NULL, NULL, status, , ,
DB_QUERY_BIND_INT64(place_id),
);
if (status < 0) {
return status;
}
return sqlite3_changes(ctx->db);
}
static int static int
mozkw_rename ( mozkw_rename (
struct backend_ctx *ctx, struct backend_ctx *ctx,
@ -1173,7 +1238,7 @@ mozkw_rename (
if (flags & BOOKMARKFS_BOOKMARK_RENAME_NOREPLACE) { if (flags & BOOKMARKFS_BOOKMARK_RENAME_NOREPLACE) {
return -EEXIST; return -EEXIST;
} }
status = mozplace_delref(ctx, old_cols.place_id); status = mozplace_delref(ctx, old_cols.place_id, 1);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -1412,16 +1477,18 @@ mozplace_delete (
static int static int
mozplace_delref ( mozplace_delref (
struct backend_ctx *ctx, struct backend_ctx *ctx,
int64_t id int64_t id,
int purge
) { ) {
sqlite3_stmt **stmt_ptr = &ctx->stmts[STMT_MOZPLACE_DELREF]; sqlite3_stmt **stmt_ptr = &ctx->stmts[STMT_MOZPLACE_DELREF];
char const *sql = char const *sql =
"UPDATE `moz_places` SET `foreign_count` = `foreign_count` - 1 " "UPDATE `moz_places` SET `foreign_count` = `foreign_count` - ? "
"WHERE `id` = ? RETURNING `foreign_count`, `origin_id`"; "WHERE `id` = ? RETURNING `foreign_count`, `origin_id`";
ssize_t nrows; ssize_t nrows;
int64_t result[2]; // `foreign_count`, `origin_id` int64_t result[2]; // `foreign_count`, `origin_id`
DO_QUERY(ctx, stmt_ptr, sql, db_query_i64_cb, result, nrows, , , DO_QUERY(ctx, stmt_ptr, sql, db_query_i64_cb, result, nrows, , ,
DB_QUERY_BIND_INT64(purge == 0 ? 1 : purge),
DB_QUERY_BIND_INT64(id), DB_QUERY_BIND_INT64(id),
); );
if (nrows < 0) { if (nrows < 0) {
@ -1430,11 +1497,14 @@ mozplace_delref (
if (unlikely(nrows == 0)) { if (unlikely(nrows == 0)) {
return -EIO; return -EIO;
} }
if (result[0] > 0) { if (result[0] == 0) {
// `foreign_count` reaches 0, delete row.
return mozplace_delete(ctx, id, result[1]);
}
if (purge > 0) {
return 0; return 0;
} }
// `foreign_count` reaches 0, delete row. return mozplace_purge(ctx, id);
return mozplace_delete(ctx, id, result[1]);
} }
static int static int
@ -1469,6 +1539,33 @@ mozplace_insert (
return 0; return 0;
} }
static int
mozplace_purge (
struct backend_ctx *ctx,
int64_t id
) {
int status = mozbm_purge_check(ctx, id);
if (status <= 0) {
return status;
}
status = mozbm_purge(ctx, id);
if (status < 0) {
return status;
}
int changes = status;
status = mozkw_purge(ctx, id);
if (status < 0) {
return status;
}
changes += status;
if (changes == 0) {
return 0;
}
return mozplace_delref(ctx, id, changes);
}
static int static int
mozplace_update ( mozplace_update (
struct backend_ctx *ctx, struct backend_ctx *ctx,
@ -1482,7 +1579,7 @@ mozplace_update (
if (status < 0) { if (status < 0) {
return status; return status;
} }
status = mozplace_delref(ctx, cols->id); status = mozplace_delref(ctx, cols->id, 1);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -2000,7 +2097,7 @@ tag_entry_delete (
return status; return status;
} }
status = mozbm_delete(ctx, cols.id, false); status = mozbm_delete(ctx, cols.id, false, false);
if (status < 0) { if (status < 0) {
return status; return status;
} }
@ -3617,7 +3714,7 @@ bookmark_rename (
status = (old_cols.place_id == 0) ? -ENOTDIR : -EISDIR; status = (old_cols.place_id == 0) ? -ENOTDIR : -EISDIR;
goto fail; goto fail;
} }
status = mozbm_delete(ctx, new_cols.id, old_cols.place_id == 0); status = mozbm_delete(ctx, new_cols.id, old_cols.place_id == 0, true);
if (status < 0) { if (status < 0) {
goto fail; goto fail;
} }
@ -3752,14 +3849,14 @@ bookmark_set (
if (status < 0) { if (status < 0) {
goto fail; goto fail;
} }
if (xattr_id >= MOZBM_XATTR_START) {
goto end;
}
if (bm_cols.place_id == 0) { if (bm_cols.place_id == 0) {
// Attempting to update moz_places fields on a directory. // Attempting to update moz_places fields on a directory.
status = -EPERM; status = -EPERM;
goto fail; goto fail;
} }
if (xattr_id >= MOZBM_XATTR_START) {
goto end;
}
place_cols.id = bm_cols.place_id; place_cols.id = bm_cols.place_id;
status = mozplace_update(ctx, &place_cols); status = mozplace_update(ctx, &place_cols);

View file

@ -48,7 +48,7 @@ struct xattr_get_ctx {
char eol; char eol;
unsigned binary : 1; unsigned binary : 1;
unsigned multi_name : 1; unsigned multi_attr : 1;
}; };
// Forward declaration start // Forward declaration start
@ -249,8 +249,8 @@ subcmd_xattr_get (
ctx.binary = 1; ctx.binary = 1;
break; break;
} }
OPT_OPT('m') { OPT_OPT('a') {
ctx.multi_name = 1; ctx.multi_attr = 1;
break; break;
} }
OPT_OPT('q') { OPT_OPT('q') {
@ -269,7 +269,7 @@ subcmd_xattr_get (
return -1; return -1;
} }
if (ctx.multi_name) { if (ctx.multi_attr) {
return xattr_get_one(argv[argc - 1], argv, argc - 1, &ctx); return xattr_get_one(argv[argc - 1], argv, argc - 1, &ctx);
} }
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@ -416,7 +416,7 @@ xattr_get_one (
for (int i = 0; i < names_cnt; ++i) { for (int i = 0; i < names_cnt; ++i) {
char const *name = names[i]; char const *name = names[i];
ctx->prefix = ctx->multi_name ? name : path; ctx->prefix = ctx->multi_attr ? name : path;
status = bookmarkfs_xattr_get(fd, name, xattr_get_cb, ctx); status = bookmarkfs_xattr_get(fd, name, xattr_get_cb, ctx);
if (status < 0) { if (status < 0) {
goto end; goto end;

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,8 @@
EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_) EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_)
TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at \ TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at \
lib_hashmap.at fs_basic.at fs_regrw.at fs_dents.at lib_hashmap.at fs_basic.at fs_regrw.at fs_dents.at fs_assoc.at \
fs_times.at
# Helper programs for testing # Helper programs for testing
@ -35,7 +36,8 @@ if BOOKMARKFS_MOUNT
if BOOKMARKFS_UTIL if BOOKMARKFS_UTIL
check_fs_CPPFLAGS += -DHAVE_BOOKMARKFS_UTIL check_fs_CPPFLAGS += -DHAVE_BOOKMARKFS_UTIL
check_fs_LDADD += $(top_builddir)/src/libbookmarkfs_util.la check_fs_LDADD += $(top_builddir)/src/libbookmarkfs_util.la
check_fs_SOURCES += check_fs_regrw.c check_fs_dents.c check_util.c check_fs_SOURCES += check_fs_regrw.c check_fs_dents.c check_util.c \
check_fs_times.c
endif # BOOKMARKFS_UTIL endif # BOOKMARKFS_UTIL
endif # BOOKMARKFS_MOUNT endif # BOOKMARKFS_MOUNT

View file

@ -60,6 +60,8 @@ dispatch_subcmds (
} else if (0 == strcmp("dents", cmd)) { } else if (0 == strcmp("dents", cmd)) {
status = check_fs_dents(argc, argv); status = check_fs_dents(argc, argv);
#endif #endif
} else if (0 == strcmp("times", cmd)) {
status = check_fs_times(argc, argv);
} else { } else {
log_printf("bad subcmd '%s'", cmd); log_printf("bad subcmd '%s'", cmd);
} }

194
tests/check_fs_times.c Normal file
View file

@ -0,0 +1,194 @@
/**
* bookmarkfs/tests/check_fs_times.c
* ----
*
* Copyright (C) 2025 CismonX <admin@cismon.net>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "check_util.h"
#include "frontend_util.h"
#include "prng.h"
// Forward declaration start
static int compare_timespec (struct timespec, struct timespec);
static int do_check_fs_times (int, int);
static void usecs_to_timespec (struct timespec *, uint32_t);
// Forward declaration end
static int
compare_timespec (
struct timespec ts1,
struct timespec ts2
) {
#define timespec_to_usecs(ts) ( (ts).tv_sec * 1000000 + (ts).tv_nsec / 1000 )
int64_t usecs1 = timespec_to_usecs(ts1);
int64_t usecs2 = timespec_to_usecs(ts2);
if (usecs1 == usecs2) {
return 0;
}
return usecs2 > usecs1 ? 1 : -1;
}
static int
do_check_fs_times (
int dirfd,
int rounds
) {
#define FILE1_NAME "foo.tmp"
#define FILE2_NAME "bar.tmp"
#define ASSERT_EQ(val, expr) ASSERT_EXPR_INT(expr, r_, (val) == r_, goto end;)
#define ASSERT_NE(val, expr) ASSERT_EXPR_INT(expr, r_, (val) != r_, goto end;)
int status = -1;
struct timespec now;
ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now));
int fd = openat(dirfd, FILE1_NAME, O_WRONLY | O_CREAT | O_EXCL);
ASSERT_NE(-1, fd);
struct stat stat_buf;
ASSERT_EQ(0, fstat(fd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
ASSERT_EQ(0, fstat(dirfd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
now = stat_buf.st_mtim;
ASSERT_EQ(11, write(fd, "foo:bar/baz", 11));
ASSERT_EQ(0, fstat(fd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
ASSERT_EQ(0, renameat(dirfd, FILE1_NAME, dirfd, FILE2_NAME));
ASSERT_EQ(0, fstat(dirfd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
now = stat_buf.st_mtim;
ASSERT_EQ(0, ftruncate(fd, 10));
ASSERT_EQ(0, fstat(fd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
struct timespec times[2];
times[0].tv_nsec = UTIME_OMIT;
times[1].tv_nsec = UTIME_NOW;
ASSERT_EQ(0, futimens(dirfd, times));
ASSERT_EQ(0, fstat(dirfd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
now = stat_buf.st_mtim;
times[0].tv_nsec = UTIME_NOW;
times[1].tv_nsec = UTIME_OMIT;
ASSERT_EQ(0, futimens(fd, times));
ASSERT_EQ(0, fstat(fd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_atim));
ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now));
ASSERT_NE(-1, compare_timespec(stat_buf.st_atim, now));
for (int i = 0; i < rounds; ++i) {
uint64_t bits = prng_rand();
usecs_to_timespec(&times[0], bits & 0xffffffff);
usecs_to_timespec(&times[1], bits >> 32);
ASSERT_EQ(0, futimens(fd, times));
ASSERT_EQ(0, fstat(fd, &stat_buf));
ASSERT_EQ(0, compare_timespec(times[0], stat_buf.st_atim));
ASSERT_EQ(0, compare_timespec(times[1], stat_buf.st_mtim));
}
ASSERT_EQ(0, unlinkat(dirfd, FILE2_NAME, 0));
ASSERT_EQ(0, fstat(dirfd, &stat_buf));
ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim));
ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now));
ASSERT_NE(-1, compare_timespec(stat_buf.st_mtim, now));
status = 0;
end:
if (fd >= 0) {
close(fd);
}
return status;
}
static void
usecs_to_timespec (
struct timespec *ts_buf,
uint32_t usecs
) {
ts_buf->tv_sec = usecs / 1000000;
ts_buf->tv_nsec = (usecs % 1000000) * 1000;
}
int
check_fs_times (
int argc,
char *argv[]
) {
uint64_t seed_buf[4], *seed = NULL;
int rounds = -1;
OPT_START(argc, argv, "r:s:")
OPT_OPT('r') {
rounds = atoi(optarg);
break;
}
OPT_OPT('s') {
if (0 != prng_seed_from_hex(seed_buf, optarg)) {
return -1;
}
seed = seed_buf;
break;
}
OPT_END
if (rounds < 0) {
log_printf("bad rounds cnt %d", rounds);
return -1;
}
if (argc < 1) {
log_puts("path not given");
return -1;
}
char const *path = argv[0];
if (0 != prng_seed(seed)) {
return -1;
}
int dirfd = open(path, O_RDONLY | O_DIRECTORY);
if (dirfd < 0) {
log_printf("open: %s: %s", path, strerror(errno));
return -1;
}
int status = do_check_fs_times(dirfd, rounds);
close(dirfd);
return status;
}

View file

@ -41,6 +41,12 @@ check_fs_dents (
char *argv[] char *argv[]
); );
int
check_fs_times (
int argc,
char *argv[]
);
int int
check_fs_regrw ( check_fs_regrw (
int argc, int argc,

65
tests/fs_assoc.at Normal file
View file

@ -0,0 +1,65 @@
dnl
dnl Copyright (C) 2025 CismonX <admin@cismon.net>
dnl
dnl Copying and distribution of this file, with or without modification,
dnl are permitted in any medium without royalty,
dnl provided the copyright notice and this notice are preserved.
dnl This file is offered as-is, without any warranty.
dnl
AT_SETUP([fs: tags and keywords])
AT_KEYWORDS([fs assoc tag keyword])
ATX_CHECK_FS_NEW_ASSOC([eol], , [
ATX_RUN_REPEAT([8], [
name=$(ath_fn_rand_u64_hex)
tag=$(ath_fn_rand_u64_hex)
keyword=$(ath_fn_rand_u64_hex)
content=foo:$(ath_fn_rand_u64_hex)
ATX_RUN([
echo "$content/1" > $name-1
echo "$content/2" > $name-2
echo "$content/3" > $name-3
mkdir "$atx_tags/$tag-1" "$atx_tags/$tag-2" "$atx_tags/$tag-3"
ln $name-1 $name-2 "$atx_tags/$tag-1"
ln $name-2 $name-3 "$atx_tags/$tag-2"
ln $name-3 $name-1 "$atx_tags/$tag-3"
test $name-1 -ef "$atx_tags/$tag-1/$name-1"
test $name-1 -ef "$atx_tags/$tag-3/$name-1"
test $name-2 -ef "$atx_tags/$tag-1/$name-2"
test $name-2 -ef "$atx_tags/$tag-2/$name-2"
test $name-3 -ef "$atx_tags/$tag-3/$name-3"
test $name-3 -ef "$atx_tags/$tag-2/$name-3"
ln $name-1 "$atx_keywords/$keyword-2"
ln $name-2 "$atx_keywords/$keyword-3"
ln $name-3 "$atx_keywords/$keyword-1"
test $name-1 -ef "$atx_keywords/$keyword-2"
test $name-2 -ef "$atx_keywords/$keyword-3"
test $name-3 -ef "$atx_keywords/$keyword-1"
rm "$atx_tags/$tag-1/$name-1"
rm "$atx_tags/$tag-2/$name-2"
rm "$atx_tags/$tag-3/$name-3"
test ! -e "$atx_tags/$tag-1/$name-1"
test ! -e "$atx_tags/$tag-2/$name-2"
test ! -e "$atx_tags/$tag-3/$name-3"
rm "$atx_keywords/$keyword-1"
rm "$atx_keywords/$keyword-2"
test ! -e "$atx_keywords/$keyword-1"
test ! -e "$atx_keywords/$keyword-2"
rm $name-1 $name-2 $name-3
test ! -e "$atx_tags/$tag-1/$name-2"
test ! -e "$atx_tags/$tag-2/$name-3"
test ! -e "$atx_tags/$tag-3/$name-1"
test ! -e "$atx_keywords/$keyword-3"
rmdir "$atx_tags/$tag-1" "$atx_tags/$tag-2" "$atx_tags/$tag-3"
])
])
])
AT_CLEANUP

View file

@ -15,53 +15,48 @@ AT_KEYWORDS([fs basic])
ATX_CHECK_FS_NEW_ANY([eol], , [ ATX_CHECK_FS_NEW_ANY([eol], , [
ATX_RUN_REPEAT([8], [ ATX_RUN_REPEAT([8], [
name=$(ath_fn_rand_u64_hex) name=$(ath_fn_rand_u64_hex)
name_1=${name}_1
name_2=${name}_2
content=foo:$(ath_fn_rand_u64_hex) content=foo:$(ath_fn_rand_u64_hex)
content_1=${content}/1
content_2=${content}/2
ATX_RUN([ ATX_RUN([
echo "$content_1" > $name_1 echo "$content/1" > $name-1
test "$(cat $name_1)" = "$content_1" test "$(cat $name-1)" = "$content/1"
echo "$content_2" > $name_2 echo "$content/2" > $name-2
test "$(cat $name_2)" = "$content_2" test "$(cat $name-2)" = "$content/2"
mv $name_1 $name_2 mv $name-1 $name-2
test ! -e $name_1 test ! -e $name-1
test "$(cat $name_2)" = "$content_1" test "$(cat $name-2)" = "$content/1"
mv $name_2 $name_1 mv $name-2 $name-1
test ! -e $name_2 test ! -e $name-2
test "$(cat $name_1)" = "$content_1" test "$(cat $name-1)" = "$content/1"
mkdir $name_2 mkdir $name-2
mv $name_1 $name_2/$name_2 mv $name-1 $name-2/$name-2
test ! -e $name_1 test ! -e $name-1
test "$(cat $name_2/$name_2)" = "$content_1" test "$(cat $name-2/$name-2)" = "$content/1"
! mkdir $name_2/$name_2 ! mkdir $name-2/$name-2
mkdir $name_2/$name_1 mkdir $name-2/$name-1
mv $name_2/$name_2 $name_2/$name_1/$name_1 mv $name-2/$name-2 $name-2/$name-1/$name-1
test "$(cat $name_2/$name_1/$name_1)" = "$content_1" test "$(cat $name-2/$name-1/$name-1)" = "$content/1"
mkdir $name_1 mkdir $name-1
! mv $name_1 $name_2/$name_1/$name_1 ! mv $name-1 $name-2/$name-1/$name-1
! mv $name_1 $name_2 ! mv $name-1 $name-2
! mv $name_2/$name_1/$name_1 $name_2 ! mv $name-2/$name-1/$name-1 $name-2
rm $name_2/$name_1/$name_1 rm $name-2/$name-1/$name-1
test ! -e $name_2/$name_1/$name_1 test ! -e $name-2/$name-1/$name-1
mv $name_1 $name_2 mv $name-1 $name-2
test ! -e $name_1 test ! -e $name-1
test -d $name_2/$name_1 test -d $name-2/$name-1
! rmdir $name_2 ! rmdir $name-2
rmdir $name_2/$name_1 rmdir $name-2/$name-1
rmdir $name_2 rmdir $name-2
test ! -e $name_2 test ! -e $name-2
]) ])
]) ])
]) ])

27
tests/fs_times.at Normal file
View file

@ -0,0 +1,27 @@
dnl
dnl Copyright (C) 2025 CismonX <admin@cismon.net>
dnl
dnl Copying and distribution of this file, with or without modification,
dnl are permitted in any medium without royalty,
dnl provided the copyright notice and this notice are preserved.
dnl This file is offered as-is, without any warranty.
dnl
AT_SETUP([fs: last access/modification times])
AT_KEYWORDS([fs times])
ATX_CHECK_FS_NEW_ANY(, [
# requires PRNG
ATX_FEAT_PREREQ([bookmarkfs-util])
], [
name=$(ath_fn_rand_u64_hex)
seed=$(ath_fn_prng_seed)
echo "prng seed: $seed"
mkdir $name
ATX_RUN([
check-fs times -s "$seed" -r 512 $name
])
])
AT_CLEANUP

View file

@ -174,6 +174,17 @@ m4_define([ATX_CHECK_FS_NEW_ANY], [
]) ])
]) ])
dnl
dnl ATX_CHECK_FS_NEW_ASSOC([options], [prepare], [check])
dnl
m4_define([ATX_CHECK_FS_NEW_ASSOC], [
ATX_CHECK_FS_NEW([firefox], [$1], [mnt.tmp], [$2], [
atx_tags=../../tags
atx_keywords=../../keywords
ATX_RUN_IN_DIR([mnt.tmp/bookmarks/unfiled], [$3])
])
])
dnl -- Helper functions -- dnl -- Helper functions --
AT_TEST_HELPER_FN([rand_u64_hex], , , [ AT_TEST_HELPER_FN([rand_u64_hex], , , [
@ -208,3 +219,5 @@ AT_BANNER([The Filesystem])
m4_include([fs_basic.at]) m4_include([fs_basic.at])
m4_include([fs_regrw.at]) m4_include([fs_regrw.at])
m4_include([fs_dents.at]) m4_include([fs_dents.at])
m4_include([fs_assoc.at])
m4_include([fs_times.at])