Compare commits

...

2 commits

Author SHA1 Message Date
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
5 changed files with 124 additions and 28 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;
} }
@ -2175,8 +2272,7 @@ bookmark_do_list (
"SELECT min(`b`.`id`), `k`.`id`, `k`.`keyword`" cols " " \ "SELECT min(`b`.`id`), `k`.`id`, `k`.`keyword`" cols " " \
"FROM `moz_keywords` `k` " join \ "FROM `moz_keywords` `k` " join \
"JOIN `moz_bookmarks` `b` ON `k`.`place_id` = `b`.`fk` " \ "JOIN `moz_bookmarks` `b` ON `k`.`place_id` = `b`.`fk` " \
"WHERE `k`.`id` >= ?2 AND `b`.`title` IS NOT NULL " \ "WHERE `k`.`id` >= ?2 GROUP BY `k`.`place_id` ORDER BY `k`.`id`"
"GROUP BY `k`.`place_id` ORDER BY `k`.`id`"
#define BOOKMARK_LIST_KEYWORD BOOKMARK_LIST_KEYWORD_(,) #define BOOKMARK_LIST_KEYWORD BOOKMARK_LIST_KEYWORD_(,)
#define BOOKMARK_LIST_KEYWORD_EX \ #define BOOKMARK_LIST_KEYWORD_EX \
BOOKMARK_LIST_KEYWORD_(", " BOOKMARK_LIST_EX_COLS_, \ BOOKMARK_LIST_KEYWORD_(", " BOOKMARK_LIST_EX_COLS_, \
@ -2268,8 +2364,7 @@ bookmark_do_lookup (
#define PLACE_ID_BY_KEYWORD(val) \ #define PLACE_ID_BY_KEYWORD(val) \
"SELECT `place_id` FROM `moz_keywords` WHERE `keyword` = " val "SELECT `place_id` FROM `moz_keywords` WHERE `keyword` = " val
#define BOOKMARK_LOOKUP_PLACE_ID_(val) \ #define BOOKMARK_LOOKUP_PLACE_ID_(val) \
BOOKMARK_LOOKUP_(, "`b`.`fk` = " val " AND `b`.`title` IS NOT NULL " \ BOOKMARK_LOOKUP_(, "`b`.`fk` = " val " ORDER BY `b`.`id` LIMIT 1")
"ORDER BY `b`.`id` LIMIT 1")
#define BOOKMARK_LOOKUP_KEYWORD_(val) \ #define BOOKMARK_LOOKUP_KEYWORD_(val) \
BOOKMARK_LOOKUP_PLACE_ID_("(" PLACE_ID_BY_KEYWORD(val) ")") BOOKMARK_LOOKUP_PLACE_ID_("(" PLACE_ID_BY_KEYWORD(val) ")")
#define BOOKMARK_LOOKUP_KEYWORD BOOKMARK_LOOKUP_KEYWORD_("?2") #define BOOKMARK_LOOKUP_KEYWORD BOOKMARK_LOOKUP_KEYWORD_("?2")
@ -3619,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;
} }

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;

View file

@ -57,6 +57,7 @@ ATX_CHECK_FS_NEW_ASSOC([eol], , [
test ! -e "$atx_tags/$tag-2/$name-3" test ! -e "$atx_tags/$tag-2/$name-3"
test ! -e "$atx_tags/$tag-3/$name-1" test ! -e "$atx_tags/$tag-3/$name-1"
test ! -e "$atx_keywords/$keyword-3" test ! -e "$atx_keywords/$keyword-3"
rmdir "$atx_tags/$tag-1" "$atx_tags/$tag-2" "$atx_tags/$tag-3"
]) ])
]) ])
]) ])