#include "checksum.h"
#include "disk_groups.h"
#include "error.h"
-#include "io.h"
#include "journal.h"
#include "journal_io.h"
#include "journal_reclaim.h"
#include "journal_seq_blacklist.h"
#include "replicas.h"
+#include "sb-clean.h"
#include "trace.h"
static struct nonce journal_nonce(const struct jset *jset)
#define JOURNAL_ENTRY_BAD 7
static void journal_entry_err_msg(struct printbuf *out,
+ u32 version,
struct jset *jset,
struct jset_entry *entry)
{
- prt_str(out, "invalid journal entry ");
- if (entry)
- prt_printf(out, "%s ", bch2_jset_entry_types[entry->type]);
-
- if (!jset)
- prt_printf(out, "in superblock");
- else if (!entry)
- prt_printf(out, "at seq %llu", le64_to_cpu(jset->seq));
- else
- prt_printf(out, "at offset %zi/%u seq %llu",
- (u64 *) entry - jset->_data,
- le32_to_cpu(jset->u64s),
- le64_to_cpu(jset->seq));
+ prt_str(out, "invalid journal entry, version=");
+ bch2_version_to_text(out, version);
+
+ if (entry) {
+ prt_str(out, " type=");
+ prt_str(out, bch2_jset_entry_types[entry->type]);
+ }
+
+ if (!jset) {
+ prt_printf(out, " in superblock");
+ } else {
+
+ prt_printf(out, " seq=%llu", le64_to_cpu(jset->seq));
+
+ if (entry)
+ prt_printf(out, " offset=%zi/%u",
+ (u64 *) entry - jset->_data,
+ le32_to_cpu(jset->u64s));
+ }
+
prt_str(out, ": ");
}
-#define journal_entry_err(c, jset, entry, msg, ...) \
+#define journal_entry_err(c, version, jset, entry, msg, ...) \
({ \
- struct printbuf buf = PRINTBUF; \
+ struct printbuf _buf = PRINTBUF; \
\
- journal_entry_err_msg(&buf, jset, entry); \
- prt_printf(&buf, msg, ##__VA_ARGS__); \
+ journal_entry_err_msg(&_buf, version, jset, entry); \
+ prt_printf(&_buf, msg, ##__VA_ARGS__); \
\
- switch (write) { \
+ switch (flags & BKEY_INVALID_WRITE) { \
case READ: \
- mustfix_fsck_err(c, "%s", buf.buf); \
+ mustfix_fsck_err(c, "%s", _buf.buf); \
break; \
case WRITE: \
- bch_err(c, "corrupt metadata before write: %s\n", buf.buf);\
+ bch_err(c, "corrupt metadata before write: %s\n", _buf.buf);\
if (bch2_fs_inconsistent(c)) { \
ret = -BCH_ERR_fsck_errors_not_fixed; \
goto fsck_err; \
break; \
} \
\
- printbuf_exit(&buf); \
+ printbuf_exit(&_buf); \
true; \
})
-#define journal_entry_err_on(cond, c, jset, entry, msg, ...) \
- ((cond) ? journal_entry_err(c, jset, entry, msg, ##__VA_ARGS__) : false)
+#define journal_entry_err_on(cond, c, version, jset, entry, msg, ...) \
+ ((cond) ? journal_entry_err(c, version, jset, entry, msg, ##__VA_ARGS__) : false)
#define FSCK_DELETED_KEY 5
struct jset_entry *entry,
unsigned level, enum btree_id btree_id,
struct bkey_i *k,
- unsigned version, int big_endian, int write)
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
+ int write = flags & BKEY_INVALID_WRITE;
void *next = vstruct_next(entry);
struct printbuf buf = PRINTBUF;
int ret = 0;
- if (journal_entry_err_on(!k->k.u64s, c, jset, entry, "k->u64s 0")) {
+ if (journal_entry_err_on(!k->k.u64s, c, version, jset, entry, "k->u64s 0")) {
entry->u64s = cpu_to_le16((u64 *) k - entry->_data);
journal_entry_null_range(vstruct_next(entry), next);
return FSCK_DELETED_KEY;
if (journal_entry_err_on((void *) bkey_next(k) >
(void *) vstruct_next(entry),
- c, jset, entry,
+ c, version, jset, entry,
"extends past end of journal entry")) {
entry->u64s = cpu_to_le16((u64 *) k - entry->_data);
journal_entry_null_range(vstruct_next(entry), next);
}
if (journal_entry_err_on(k->k.format != KEY_FORMAT_CURRENT,
- c, jset, entry,
+ c, version, jset, entry,
"bad format %u", k->k.format)) {
le16_add_cpu(&entry->u64s, -((u16) k->k.u64s));
memmove(k, bkey_next(k), next - (void *) bkey_next(k));
if (bch2_bkey_invalid(c, bkey_i_to_s_c(k),
__btree_node_type(level, btree_id), write, &buf)) {
printbuf_reset(&buf);
- prt_printf(&buf, "invalid journal entry %s at offset %zi/%u seq %llu:",
- bch2_jset_entry_types[entry->type],
- (u64 *) entry - jset->_data,
- le32_to_cpu(jset->u64s),
- le64_to_cpu(jset->seq));
+ journal_entry_err_msg(&buf, version, jset, entry);
prt_newline(&buf);
printbuf_indent_add(&buf, 2);
}
static int journal_entry_btree_keys_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct bkey_i *k = entry->start;
int ret = journal_validate_key(c, jset, entry,
entry->level,
entry->btree_id,
- k, version, big_endian, write|BKEY_INVALID_FROM_JOURNAL);
+ k, version, big_endian,
+ flags|BKEY_INVALID_JOURNAL);
if (ret == FSCK_DELETED_KEY)
continue;
prt_newline(out);
prt_printf(out, "%s: ", bch2_jset_entry_types[entry->type]);
}
- prt_printf(out, "btree=%s l=%u ", bch2_btree_ids[entry->btree_id], entry->level);
+ prt_printf(out, "btree=%s l=%u ", bch2_btree_id_str(entry->btree_id), entry->level);
bch2_bkey_val_to_text(out, c, bkey_i_to_s_c(k));
first = false;
}
}
static int journal_entry_btree_root_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct bkey_i *k = entry->start;
int ret = 0;
if (journal_entry_err_on(!entry->u64s ||
le16_to_cpu(entry->u64s) != k->k.u64s,
- c, jset, entry,
+ c, version, jset, entry,
"invalid btree root journal entry: wrong number of keys")) {
void *next = vstruct_next(entry);
/*
}
return journal_validate_key(c, jset, entry, 1, entry->btree_id, k,
- version, big_endian, write);
+ version, big_endian, flags);
fsck_err:
return ret;
}
}
static int journal_entry_prio_ptrs_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
/* obsolete, don't care: */
return 0;
}
static int journal_entry_blacklist_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
int ret = 0;
if (journal_entry_err_on(le16_to_cpu(entry->u64s) != 1,
- c, jset, entry,
+ c, version, jset, entry,
"invalid journal seq blacklist entry: bad size")) {
journal_entry_null_range(entry, vstruct_next(entry));
}
}
static int journal_entry_blacklist_v2_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct jset_entry_blacklist_v2 *bl_entry;
int ret = 0;
if (journal_entry_err_on(le16_to_cpu(entry->u64s) != 2,
- c, jset, entry,
+ c, version, jset, entry,
"invalid journal seq blacklist entry: bad size")) {
journal_entry_null_range(entry, vstruct_next(entry));
goto out;
if (journal_entry_err_on(le64_to_cpu(bl_entry->start) >
le64_to_cpu(bl_entry->end),
- c, jset, entry,
+ c, version, jset, entry,
"invalid journal seq blacklist entry: start > end")) {
journal_entry_null_range(entry, vstruct_next(entry));
}
}
static int journal_entry_usage_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct jset_entry_usage *u =
container_of(entry, struct jset_entry_usage, entry);
int ret = 0;
if (journal_entry_err_on(bytes < sizeof(*u),
- c, jset, entry,
+ c, version, jset, entry,
"invalid journal entry usage: bad size")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
static int journal_entry_data_usage_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct jset_entry_data_usage *u =
container_of(entry, struct jset_entry_data_usage, entry);
if (journal_entry_err_on(bytes < sizeof(*u) ||
bytes < sizeof(*u) + u->r.nr_devs,
- c, jset, entry,
+ c, version, jset, entry,
"invalid journal entry usage: bad size")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
static int journal_entry_clock_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct jset_entry_clock *clock =
container_of(entry, struct jset_entry_clock, entry);
int ret = 0;
if (journal_entry_err_on(bytes != sizeof(*clock),
- c, jset, entry, "bad size")) {
+ c, version, jset, entry, "bad size")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
if (journal_entry_err_on(clock->rw > 1,
- c, jset, entry, "bad rw")) {
+ c, version, jset, entry, "bad rw")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
}
static int journal_entry_dev_usage_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
struct jset_entry_dev_usage *u =
container_of(entry, struct jset_entry_dev_usage, entry);
int ret = 0;
if (journal_entry_err_on(bytes < expected,
- c, jset, entry, "bad size (%u < %u)",
+ c, version, jset, entry, "bad size (%u < %u)",
bytes, expected)) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
dev = le32_to_cpu(u->dev);
if (journal_entry_err_on(!bch2_dev_exists2(c, dev),
- c, jset, entry, "bad dev")) {
+ c, version, jset, entry, "bad dev")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
if (journal_entry_err_on(u->pad,
- c, jset, entry, "bad pad")) {
+ c, version, jset, entry, "bad pad")) {
journal_entry_null_range(entry, vstruct_next(entry));
return ret;
}
}
static int journal_entry_log_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
return 0;
}
}
static int journal_entry_overwrite_validate(struct bch_fs *c,
- struct jset *jset,
- struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ struct jset *jset,
+ struct jset_entry *entry,
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
return journal_entry_btree_keys_validate(c, jset, entry,
version, big_endian, READ);
struct jset_entry_ops {
int (*validate)(struct bch_fs *, struct jset *,
- struct jset_entry *, unsigned, int, int);
+ struct jset_entry *, unsigned, int,
+ enum bkey_invalid_flags);
void (*to_text)(struct printbuf *, struct bch_fs *, struct jset_entry *);
};
int bch2_journal_entry_validate(struct bch_fs *c,
struct jset *jset,
struct jset_entry *entry,
- unsigned version, int big_endian, int write)
+ unsigned version, int big_endian,
+ enum bkey_invalid_flags flags)
{
return entry->type < BCH_JSET_ENTRY_NR
? bch2_jset_entry_ops[entry->type].validate(c, jset, entry,
- version, big_endian, write)
+ version, big_endian, flags)
: 0;
}
}
static int jset_validate_entries(struct bch_fs *c, struct jset *jset,
- int write)
+ enum bkey_invalid_flags flags)
{
struct jset_entry *entry;
+ unsigned version = le32_to_cpu(jset->version);
int ret = 0;
vstruct_for_each(jset, entry) {
- if (journal_entry_err_on(vstruct_next(entry) >
- vstruct_last(jset), c, jset, entry,
+ if (journal_entry_err_on(vstruct_next(entry) > vstruct_last(jset),
+ c, version, jset, entry,
"journal entry extends past end of jset")) {
jset->u64s = cpu_to_le32((u64 *) entry - jset->_data);
break;
}
ret = bch2_journal_entry_validate(c, jset, entry,
- le32_to_cpu(jset->version),
- JSET_BIG_ENDIAN(jset), write);
+ version, JSET_BIG_ENDIAN(jset), flags);
if (ret)
break;
}
static int jset_validate(struct bch_fs *c,
struct bch_dev *ca,
struct jset *jset, u64 sector,
- int write)
+ enum bkey_invalid_flags flags)
{
unsigned version;
int ret = 0;
return JOURNAL_ENTRY_NONE;
version = le32_to_cpu(jset->version);
- if (journal_entry_err_on((version != BCH_JSET_VERSION_OLD &&
- version < bcachefs_metadata_version_min) ||
- version >= bcachefs_metadata_version_max,
- c, jset, NULL,
- "%s sector %llu seq %llu: unknown journal entry version %u",
+ if (journal_entry_err_on(!bch2_version_compatible(version),
+ c, version, jset, NULL,
+ "%s sector %llu seq %llu: incompatible journal entry version %u.%u",
ca ? ca->name : c->name,
sector, le64_to_cpu(jset->seq),
- version)) {
+ BCH_VERSION_MAJOR(version),
+ BCH_VERSION_MINOR(version))) {
/* don't try to continue: */
return -EINVAL;
}
if (journal_entry_err_on(!bch2_checksum_type_valid(c, JSET_CSUM_TYPE(jset)),
- c, jset, NULL,
+ c, version, jset, NULL,
"%s sector %llu seq %llu: journal entry with unknown csum type %llu",
ca ? ca->name : c->name,
sector, le64_to_cpu(jset->seq),
/* last_seq is ignored when JSET_NO_FLUSH is true */
if (journal_entry_err_on(!JSET_NO_FLUSH(jset) &&
le64_to_cpu(jset->last_seq) > le64_to_cpu(jset->seq),
- c, jset, NULL,
+ c, version, jset, NULL,
"invalid journal entry: last_seq > seq (%llu > %llu)",
le64_to_cpu(jset->last_seq),
le64_to_cpu(jset->seq))) {
return JOURNAL_ENTRY_BAD;
}
- ret = jset_validate_entries(c, jset, write);
+ ret = jset_validate_entries(c, jset, flags);
fsck_err:
return ret;
}
{
size_t bytes = vstruct_bytes(jset);
unsigned version;
- int write = READ;
+ enum bkey_invalid_flags flags = BKEY_INVALID_JOURNAL;
int ret = 0;
if (le64_to_cpu(jset->magic) != jset_magic(c))
return JOURNAL_ENTRY_NONE;
version = le32_to_cpu(jset->version);
- if (journal_entry_err_on((version != BCH_JSET_VERSION_OLD &&
- version < bcachefs_metadata_version_min) ||
- version >= bcachefs_metadata_version_max,
- c, jset, NULL,
- "%s sector %llu seq %llu: unknown journal entry version %u",
+ if (journal_entry_err_on(!bch2_version_compatible(version),
+ c, version, jset, NULL,
+ "%s sector %llu seq %llu: unknown journal entry version %u.%u",
ca ? ca->name : c->name,
sector, le64_to_cpu(jset->seq),
- version)) {
+ BCH_VERSION_MAJOR(version),
+ BCH_VERSION_MINOR(version))) {
/* don't try to continue: */
return -EINVAL;
}
return JOURNAL_ENTRY_REREAD;
if (journal_entry_err_on(bytes > bucket_sectors_left << 9,
- c, jset, NULL,
+ c, version, jset, NULL,
"%s sector %llu seq %llu: journal entry too big (%zu bytes)",
ca ? ca->name : c->name,
sector, le64_to_cpu(jset->seq), bytes))
bch_err(c, "cur_idx %u/%u", ja->cur_idx, ja->nr);
for (i = 0; i < 3; i++) {
unsigned idx = (ja->cur_idx + ja->nr - 1 + i) % ja->nr;
+
bch_err(c, "bucket_seq[%u] = %llu", idx, ja->bucket_seq[idx]);
}
ja->sectors_free = 0;
* those entries will be blacklisted:
*/
genradix_for_each_reverse(&c->journal_entries, radix_iter, _i) {
- int write = READ;
+ enum bkey_invalid_flags flags = BKEY_INVALID_JOURNAL;
i = *_i;
}
if (journal_entry_err_on(le64_to_cpu(i->j.last_seq) > le64_to_cpu(i->j.seq),
- c, &i->j, NULL,
+ c, le32_to_cpu(i->j.version), &i->j, NULL,
"invalid journal entry: last_seq > seq (%llu > %llu)",
le64_to_cpu(i->j.last_seq),
le64_to_cpu(i->j.seq)))
continue;
for (ptr = 0; ptr < i->nr_ptrs; ptr++) {
- struct bch_dev *ca = bch_dev_bkey_exists(c, i->ptrs[ptr].dev);
+ ca = bch_dev_bkey_exists(c, i->ptrs[ptr].dev);
if (!i->ptrs[ptr].csum_good)
bch_err_dev_offset(ca, i->ptrs[ptr].sector,
bch2_replicas_entry_sort(&replicas.e);
- /*
- * If we're mounting in degraded mode - if we didn't read all
- * the devices - this is wrong:
- */
-
printbuf_reset(&buf);
bch2_replicas_entry_to_text(&buf, &replicas.e);
if (!degraded &&
- fsck_err_on(!bch2_replicas_marked(c, &replicas.e), c,
- "superblock not marked as containing replicas %s",
- buf.buf)) {
+ !bch2_replicas_marked(c, &replicas.e) &&
+ (le64_to_cpu(i->j.seq) == *last_seq ||
+ fsck_err(c, "superblock not marked as containing replicas for journal entry %llu\n %s",
+ le64_to_cpu(i->j.seq), buf.buf))) {
ret = bch2_mark_replicas(c, &replicas.e);
if (ret)
goto err;
}
/**
- * journal_next_bucket - move on to the next journal bucket if possible
+ * journal_write_alloc - decide where to write next journal entry
+ *
+ * @j: journal object
+ * @w: journal buf (entry to be written)
+ *
+ * Returns: 0 on success, or -EROFS on failure
*/
-static int journal_write_alloc(struct journal *j, struct journal_buf *w,
- unsigned sectors)
+static int journal_write_alloc(struct journal *j, struct journal_buf *w)
{
struct bch_fs *c = container_of(j, struct bch_fs, journal);
struct bch_devs_mask devs;
struct journal_device *ja;
struct bch_dev *ca;
struct dev_alloc_list devs_sorted;
+ unsigned sectors = vstruct_sectors(w->data, c->block_bits);
unsigned target = c->opts.metadata_target ?:
c->opts.foreground_target;
unsigned i, replicas = 0, replicas_want =
if (buf->buf_size >= new_size)
return;
- new_buf = kvpmalloc(new_size, GFP_NOIO|__GFP_NOWARN);
+ new_buf = kvpmalloc(new_size, GFP_NOFS|__GFP_NOWARN);
if (!new_buf)
return;
struct journal *j = container_of(cl, struct journal, io);
struct bch_fs *c = container_of(j, struct bch_fs, journal);
struct journal_buf *w = journal_last_unwritten_buf(j);
+ struct bch_replicas_padded replicas;
union journal_res_state old, new;
u64 v, seq;
int err = 0;
if (!w->devs_written.nr) {
bch_err(c, "unable to write journal to sufficient devices");
err = -EIO;
+ } else {
+ bch2_devlist_to_replicas(&replicas.e, BCH_DATA_journal,
+ w->devs_written);
+ if (bch2_mark_replicas(c, &replicas.e))
+ err = -EIO;
}
+
if (err)
bch2_fatal_error(c);
* Must come before signaling write completion, for
* bch2_fs_journal_stop():
*/
- if (j->watermark)
+ if (j->watermark != BCH_WATERMARK_stripe)
journal_reclaim_kick(&c->journal);
/* also must come before signalling write completion: */
if (!journal_state_count(new, new.unwritten_idx) &&
journal_last_unwritten_seq(j) <= journal_cur_seq(j)) {
+ spin_unlock(&j->lock);
closure_call(&j->io, bch2_journal_write, c->io_complete_wq, NULL);
} else if (journal_last_unwritten_seq(j) == journal_cur_seq(j) &&
new.cur_entry_offset < JOURNAL_ENTRY_CLOSED_VAL) {
* might want to be written now:
*/
+ spin_unlock(&j->lock);
mod_delayed_work(c->io_complete_wq, &j->write_work, max(0L, delta));
+ } else {
+ spin_unlock(&j->lock);
}
-
- spin_unlock(&j->lock);
}
static void journal_write_endio(struct bio *bio)
}
continue_at(cl, journal_write_done, c->io_complete_wq);
- return;
}
static void bch2_journal_entries_postprocess(struct bch_fs *c, struct jset *jset)
BUG_ON(u64s > j->entry_u64s_reserved);
le32_add_cpu(&jset->u64s, u64s);
- BUG_ON(vstruct_sectors(jset, c->block_bits) > w->sectors);
+
+ sectors = vstruct_sectors(jset, c->block_bits);
+ bytes = vstruct_bytes(jset);
+
+ if (sectors > w->sectors) {
+ bch2_fs_fatal_error(c, "aieeee! journal write overran available space, %zu > %u (extra %u reserved %u/%u)",
+ vstruct_bytes(jset), w->sectors << 9,
+ u64s, w->u64s_reserved, j->entry_u64s_reserved);
+ goto err;
+ }
jset->magic = cpu_to_le64(jset_magic(c));
- jset->version = c->sb.version < bcachefs_metadata_version_bkey_renumber
- ? cpu_to_le32(BCH_JSET_VERSION_OLD)
- : cpu_to_le32(c->sb.version);
+ jset->version = cpu_to_le32(c->sb.version);
SET_JSET_BIG_ENDIAN(jset, CPU_BIG_ENDIAN);
SET_JSET_CSUM_TYPE(jset, bch2_meta_checksum_type(c));
jset_validate(c, NULL, jset, 0, WRITE))
goto err;
- sectors = vstruct_sectors(jset, c->block_bits);
- BUG_ON(sectors > w->sectors);
-
- bytes = vstruct_bytes(jset);
memset((void *) jset + bytes, 0, (sectors << 9) - bytes);
retry_alloc:
spin_lock(&j->lock);
- ret = journal_write_alloc(j, w, sectors);
+ ret = journal_write_alloc(j, w);
if (ret && j->can_discard) {
spin_unlock(&j->lock);