Commit a3965607 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'ext4_for_linus_stable' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4

Pull ext4 bug fixes from Ted Ts'o:
 "Ext4 bug fixes, including a regression fix"

* tag 'ext4_for_linus_stable' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4:
  ext4: clarify impact of 'commit' mount option
  ext4: fix unused-but-set-variable warning in ext4_add_entry()
  jbd2: fix kernel-doc notation warning
  ext4: use RCU API in debug_print_tree
  ext4: validate the debug_want_extra_isize mount option at parse time
  ext4: reserve revoke credits in __ext4_new_inode
  ext4: unlock on error in ext4_expand_extra_isize()
  ext4: optimize __ext4_check_dir_entry()
  ext4: check for directory entries too close to block end
  ext4: fix ext4_empty_dir() for directories with holes
parents 44579f35 23f6b024
...@@ -181,14 +181,17 @@ When mounting an ext4 filesystem, the following option are accepted: ...@@ -181,14 +181,17 @@ When mounting an ext4 filesystem, the following option are accepted:
system after its metadata has been committed to the journal. system after its metadata has been committed to the journal.
commit=nrsec (*) commit=nrsec (*)
Ext4 can be told to sync all its data and metadata every 'nrsec' This setting limits the maximum age of the running transaction to
seconds. The default value is 5 seconds. This means that if you lose 'nrsec' seconds. The default value is 5 seconds. This means that if
your power, you will lose as much as the latest 5 seconds of work (your you lose your power, you will lose as much as the latest 5 seconds of
filesystem will not be damaged though, thanks to the journaling). This metadata changes (your filesystem will not be damaged though, thanks
default value (or any low value) will hurt performance, but it's good to the journaling). This default value (or any low value) will hurt
for data-safety. Setting it to 0 will have the same effect as leaving performance, but it's good for data-safety. Setting it to 0 will have
it at the default (5 seconds). Setting it to very large values will the same effect as leaving it at the default (5 seconds). Setting it
improve performance. to very large values will improve performance. Note that due to
delayed allocation even older data can be lost on power failure since
writeback of those data begins only after time set in
/proc/sys/vm/dirty_expire_centisecs.
barrier=<0|1(*)>, barrier(*), nobarrier barrier=<0|1(*)>, barrier(*), nobarrier
This enables/disables the use of write barriers in the jbd code. This enables/disables the use of write barriers in the jbd code.
......
...@@ -133,10 +133,13 @@ static void debug_print_tree(struct ext4_sb_info *sbi) ...@@ -133,10 +133,13 @@ static void debug_print_tree(struct ext4_sb_info *sbi)
{ {
struct rb_node *node; struct rb_node *node;
struct ext4_system_zone *entry; struct ext4_system_zone *entry;
struct ext4_system_blocks *system_blks;
int first = 1; int first = 1;
printk(KERN_INFO "System zones: "); printk(KERN_INFO "System zones: ");
node = rb_first(&sbi->system_blks->root); rcu_read_lock();
system_blks = rcu_dereference(sbi->system_blks);
node = rb_first(&system_blks->root);
while (node) { while (node) {
entry = rb_entry(node, struct ext4_system_zone, node); entry = rb_entry(node, struct ext4_system_zone, node);
printk(KERN_CONT "%s%llu-%llu", first ? "" : ", ", printk(KERN_CONT "%s%llu-%llu", first ? "" : ", ",
...@@ -144,6 +147,7 @@ static void debug_print_tree(struct ext4_sb_info *sbi) ...@@ -144,6 +147,7 @@ static void debug_print_tree(struct ext4_sb_info *sbi)
first = 0; first = 0;
node = rb_next(node); node = rb_next(node);
} }
rcu_read_unlock();
printk(KERN_CONT "\n"); printk(KERN_CONT "\n");
} }
......
...@@ -72,6 +72,7 @@ int __ext4_check_dir_entry(const char *function, unsigned int line, ...@@ -72,6 +72,7 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
const char *error_msg = NULL; const char *error_msg = NULL;
const int rlen = ext4_rec_len_from_disk(de->rec_len, const int rlen = ext4_rec_len_from_disk(de->rec_len,
dir->i_sb->s_blocksize); dir->i_sb->s_blocksize);
const int next_offset = ((char *) de - buf) + rlen;
if (unlikely(rlen < EXT4_DIR_REC_LEN(1))) if (unlikely(rlen < EXT4_DIR_REC_LEN(1)))
error_msg = "rec_len is smaller than minimal"; error_msg = "rec_len is smaller than minimal";
...@@ -79,8 +80,11 @@ int __ext4_check_dir_entry(const char *function, unsigned int line, ...@@ -79,8 +80,11 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
error_msg = "rec_len % 4 != 0"; error_msg = "rec_len % 4 != 0";
else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len))) else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))
error_msg = "rec_len is too small for name_len"; error_msg = "rec_len is too small for name_len";
else if (unlikely(((char *) de - buf) + rlen > size)) else if (unlikely(next_offset > size))
error_msg = "directory entry overrun"; error_msg = "directory entry overrun";
else if (unlikely(next_offset > size - EXT4_DIR_REC_LEN(1) &&
next_offset != size))
error_msg = "directory entry too close to block end";
else if (unlikely(le32_to_cpu(de->inode) > else if (unlikely(le32_to_cpu(de->inode) >
le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))) le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
error_msg = "inode out of bounds"; error_msg = "inode out of bounds";
......
...@@ -921,8 +921,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, ...@@ -921,8 +921,8 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir,
if (!handle) { if (!handle) {
BUG_ON(nblocks <= 0); BUG_ON(nblocks <= 0);
handle = __ext4_journal_start_sb(dir->i_sb, line_no, handle = __ext4_journal_start_sb(dir->i_sb, line_no,
handle_type, nblocks, handle_type, nblocks, 0,
0, 0); ext4_trans_default_revoke_credits(sb));
if (IS_ERR(handle)) { if (IS_ERR(handle)) {
err = PTR_ERR(handle); err = PTR_ERR(handle);
ext4_std_error(sb, err); ext4_std_error(sb, err);
......
...@@ -5692,7 +5692,7 @@ int ext4_expand_extra_isize(struct inode *inode, ...@@ -5692,7 +5692,7 @@ int ext4_expand_extra_isize(struct inode *inode,
error = ext4_journal_get_write_access(handle, iloc->bh); error = ext4_journal_get_write_access(handle, iloc->bh);
if (error) { if (error) {
brelse(iloc->bh); brelse(iloc->bh);
goto out_stop; goto out_unlock;
} }
error = __ext4_expand_extra_isize(inode, new_extra_isize, iloc, error = __ext4_expand_extra_isize(inode, new_extra_isize, iloc,
...@@ -5702,8 +5702,8 @@ int ext4_expand_extra_isize(struct inode *inode, ...@@ -5702,8 +5702,8 @@ int ext4_expand_extra_isize(struct inode *inode,
if (!error) if (!error)
error = rc; error = rc;
out_unlock:
ext4_write_unlock_xattr(inode, &no_expand); ext4_write_unlock_xattr(inode, &no_expand);
out_stop:
ext4_journal_stop(handle); ext4_journal_stop(handle);
return error; return error;
} }
......
...@@ -2164,7 +2164,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, ...@@ -2164,7 +2164,9 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
struct buffer_head *bh = NULL; struct buffer_head *bh = NULL;
struct ext4_dir_entry_2 *de; struct ext4_dir_entry_2 *de;
struct super_block *sb; struct super_block *sb;
#ifdef CONFIG_UNICODE
struct ext4_sb_info *sbi; struct ext4_sb_info *sbi;
#endif
struct ext4_filename fname; struct ext4_filename fname;
int retval; int retval;
int dx_fallback=0; int dx_fallback=0;
...@@ -2176,12 +2178,12 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry, ...@@ -2176,12 +2178,12 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
csum_size = sizeof(struct ext4_dir_entry_tail); csum_size = sizeof(struct ext4_dir_entry_tail);
sb = dir->i_sb; sb = dir->i_sb;
sbi = EXT4_SB(sb);
blocksize = sb->s_blocksize; blocksize = sb->s_blocksize;
if (!dentry->d_name.len) if (!dentry->d_name.len)
return -EINVAL; return -EINVAL;
#ifdef CONFIG_UNICODE #ifdef CONFIG_UNICODE
sbi = EXT4_SB(sb);
if (ext4_has_strict_mode(sbi) && IS_CASEFOLDED(dir) && if (ext4_has_strict_mode(sbi) && IS_CASEFOLDED(dir) &&
sbi->s_encoding && utf8_validate(sbi->s_encoding, &dentry->d_name)) sbi->s_encoding && utf8_validate(sbi->s_encoding, &dentry->d_name))
return -EINVAL; return -EINVAL;
...@@ -2822,7 +2824,7 @@ bool ext4_empty_dir(struct inode *inode) ...@@ -2822,7 +2824,7 @@ bool ext4_empty_dir(struct inode *inode)
{ {
unsigned int offset; unsigned int offset;
struct buffer_head *bh; struct buffer_head *bh;
struct ext4_dir_entry_2 *de, *de1; struct ext4_dir_entry_2 *de;
struct super_block *sb; struct super_block *sb;
if (ext4_has_inline_data(inode)) { if (ext4_has_inline_data(inode)) {
...@@ -2847,19 +2849,25 @@ bool ext4_empty_dir(struct inode *inode) ...@@ -2847,19 +2849,25 @@ bool ext4_empty_dir(struct inode *inode)
return true; return true;
de = (struct ext4_dir_entry_2 *) bh->b_data; de = (struct ext4_dir_entry_2 *) bh->b_data;
de1 = ext4_next_entry(de, sb->s_blocksize); if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
if (le32_to_cpu(de->inode) != inode->i_ino || 0) ||
le32_to_cpu(de1->inode) == 0 || le32_to_cpu(de->inode) != inode->i_ino || strcmp(".", de->name)) {
strcmp(".", de->name) || strcmp("..", de1->name)) { ext4_warning_inode(inode, "directory missing '.'");
ext4_warning_inode(inode, "directory missing '.' and/or '..'"); brelse(bh);
return true;
}
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
de = ext4_next_entry(de, sb->s_blocksize);
if (ext4_check_dir_entry(inode, NULL, de, bh, bh->b_data, bh->b_size,
offset) ||
le32_to_cpu(de->inode) == 0 || strcmp("..", de->name)) {
ext4_warning_inode(inode, "directory missing '..'");
brelse(bh); brelse(bh);
return true; return true;
} }
offset = ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize) + offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
ext4_rec_len_from_disk(de1->rec_len, sb->s_blocksize);
de = ext4_next_entry(de1, sb->s_blocksize);
while (offset < inode->i_size) { while (offset < inode->i_size) {
if ((void *) de >= (void *) (bh->b_data+sb->s_blocksize)) { if (!(offset & (sb->s_blocksize - 1))) {
unsigned int lblock; unsigned int lblock;
brelse(bh); brelse(bh);
lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb); lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb);
...@@ -2870,12 +2878,11 @@ bool ext4_empty_dir(struct inode *inode) ...@@ -2870,12 +2878,11 @@ bool ext4_empty_dir(struct inode *inode)
} }
if (IS_ERR(bh)) if (IS_ERR(bh))
return true; return true;
de = (struct ext4_dir_entry_2 *) bh->b_data;
} }
de = (struct ext4_dir_entry_2 *) (bh->b_data +
(offset & (sb->s_blocksize - 1)));
if (ext4_check_dir_entry(inode, NULL, de, bh, if (ext4_check_dir_entry(inode, NULL, de, bh,
bh->b_data, bh->b_size, offset)) { bh->b_data, bh->b_size, offset)) {
de = (struct ext4_dir_entry_2 *)(bh->b_data +
sb->s_blocksize);
offset = (offset | (sb->s_blocksize - 1)) + 1; offset = (offset | (sb->s_blocksize - 1)) + 1;
continue; continue;
} }
...@@ -2884,7 +2891,6 @@ bool ext4_empty_dir(struct inode *inode) ...@@ -2884,7 +2891,6 @@ bool ext4_empty_dir(struct inode *inode)
return false; return false;
} }
offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize); offset += ext4_rec_len_from_disk(de->rec_len, sb->s_blocksize);
de = ext4_next_entry(de, sb->s_blocksize);
} }
brelse(bh); brelse(bh);
return true; return true;
......
...@@ -1900,6 +1900,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token, ...@@ -1900,6 +1900,13 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
} }
sbi->s_commit_interval = HZ * arg; sbi->s_commit_interval = HZ * arg;
} else if (token == Opt_debug_want_extra_isize) { } else if (token == Opt_debug_want_extra_isize) {
if ((arg & 1) ||
(arg < 4) ||
(arg > (sbi->s_inode_size - EXT4_GOOD_OLD_INODE_SIZE))) {
ext4_msg(sb, KERN_ERR,
"Invalid want_extra_isize %d", arg);
return -1;
}
sbi->s_want_extra_isize = arg; sbi->s_want_extra_isize = arg;
} else if (token == Opt_max_batch_time) { } else if (token == Opt_max_batch_time) {
sbi->s_max_batch_time = arg; sbi->s_max_batch_time = arg;
...@@ -3554,40 +3561,6 @@ int ext4_calculate_overhead(struct super_block *sb) ...@@ -3554,40 +3561,6 @@ int ext4_calculate_overhead(struct super_block *sb)
return 0; return 0;
} }
static void ext4_clamp_want_extra_isize(struct super_block *sb)
{
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = sbi->s_es;
unsigned def_extra_isize = sizeof(struct ext4_inode) -
EXT4_GOOD_OLD_INODE_SIZE;
if (sbi->s_inode_size == EXT4_GOOD_OLD_INODE_SIZE) {
sbi->s_want_extra_isize = 0;
return;
}
if (sbi->s_want_extra_isize < 4) {
sbi->s_want_extra_isize = def_extra_isize;
if (ext4_has_feature_extra_isize(sb)) {
if (sbi->s_want_extra_isize <
le16_to_cpu(es->s_want_extra_isize))
sbi->s_want_extra_isize =
le16_to_cpu(es->s_want_extra_isize);
if (sbi->s_want_extra_isize <
le16_to_cpu(es->s_min_extra_isize))
sbi->s_want_extra_isize =
le16_to_cpu(es->s_min_extra_isize);
}
}
/* Check if enough inode space is available */
if ((sbi->s_want_extra_isize > sbi->s_inode_size) ||
(EXT4_GOOD_OLD_INODE_SIZE + sbi->s_want_extra_isize >
sbi->s_inode_size)) {
sbi->s_want_extra_isize = def_extra_isize;
ext4_msg(sb, KERN_INFO,
"required extra inode space not available");
}
}
static void ext4_set_resv_clusters(struct super_block *sb) static void ext4_set_resv_clusters(struct super_block *sb)
{ {
ext4_fsblk_t resv_clusters; ext4_fsblk_t resv_clusters;
...@@ -3795,6 +3768,68 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) ...@@ -3795,6 +3768,68 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
*/ */
sbi->s_li_wait_mult = EXT4_DEF_LI_WAIT_MULT; sbi->s_li_wait_mult = EXT4_DEF_LI_WAIT_MULT;
if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV) {
sbi->s_inode_size = EXT4_GOOD_OLD_INODE_SIZE;
sbi->s_first_ino = EXT4_GOOD_OLD_FIRST_INO;
} else {
sbi->s_inode_size = le16_to_cpu(es->s_inode_size);
sbi->s_first_ino = le32_to_cpu(es->s_first_ino);
if (sbi->s_first_ino < EXT4_GOOD_OLD_FIRST_INO) {
ext4_msg(sb, KERN_ERR, "invalid first ino: %u",
sbi->s_first_ino);
goto failed_mount;
}
if ((sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE) ||
(!is_power_of_2(sbi->s_inode_size)) ||
(sbi->s_inode_size > blocksize)) {
ext4_msg(sb, KERN_ERR,
"unsupported inode size: %d",
sbi->s_inode_size);
goto failed_mount;
}
/*
* i_atime_extra is the last extra field available for
* [acm]times in struct ext4_inode. Checking for that
* field should suffice to ensure we have extra space
* for all three.
*/
if (sbi->s_inode_size >= offsetof(struct ext4_inode, i_atime_extra) +
sizeof(((struct ext4_inode *)0)->i_atime_extra)) {
sb->s_time_gran = 1;
sb->s_time_max = EXT4_EXTRA_TIMESTAMP_MAX;
} else {
sb->s_time_gran = NSEC_PER_SEC;
sb->s_time_max = EXT4_NON_EXTRA_TIMESTAMP_MAX;
}
sb->s_time_min = EXT4_TIMESTAMP_MIN;
}
if (sbi->s_inode_size > EXT4_GOOD_OLD_INODE_SIZE) {
sbi->s_want_extra_isize = sizeof(struct ext4_inode) -
EXT4_GOOD_OLD_INODE_SIZE;
if (ext4_has_feature_extra_isize(sb)) {
unsigned v, max = (sbi->s_inode_size -
EXT4_GOOD_OLD_INODE_SIZE);
v = le16_to_cpu(es->s_want_extra_isize);
if (v > max) {
ext4_msg(sb, KERN_ERR,
"bad s_want_extra_isize: %d", v);
goto failed_mount;
}
if (sbi->s_want_extra_isize < v)
sbi->s_want_extra_isize = v;
v = le16_to_cpu(es->s_min_extra_isize);
if (v > max) {
ext4_msg(sb, KERN_ERR,
"bad s_min_extra_isize: %d", v);
goto failed_mount;
}
if (sbi->s_want_extra_isize < v)
sbi->s_want_extra_isize = v;
}
}
if (sbi->s_es->s_mount_opts[0]) { if (sbi->s_es->s_mount_opts[0]) {
char *s_mount_opts = kstrndup(sbi->s_es->s_mount_opts, char *s_mount_opts = kstrndup(sbi->s_es->s_mount_opts,
sizeof(sbi->s_es->s_mount_opts), sizeof(sbi->s_es->s_mount_opts),
...@@ -4033,42 +4068,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) ...@@ -4033,42 +4068,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
has_huge_files); has_huge_files);
sb->s_maxbytes = ext4_max_size(sb->s_blocksize_bits, has_huge_files); sb->s_maxbytes = ext4_max_size(sb->s_blocksize_bits, has_huge_files);
if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV) {
sbi->s_inode_size = EXT4_GOOD_OLD_INODE_SIZE;
sbi->s_first_ino = EXT4_GOOD_OLD_FIRST_INO;
} else {
sbi->s_inode_size = le16_to_cpu(es->s_inode_size);
sbi->s_first_ino = le32_to_cpu(es->s_first_ino);
if (sbi->s_first_ino < EXT4_GOOD_OLD_FIRST_INO) {
ext4_msg(sb, KERN_ERR, "invalid first ino: %u",
sbi->s_first_ino);
goto failed_mount;
}
if ((sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE) ||
(!is_power_of_2(sbi->s_inode_size)) ||
(sbi->s_inode_size > blocksize)) {
ext4_msg(sb, KERN_ERR,
"unsupported inode size: %d",
sbi->s_inode_size);
goto failed_mount;
}
/*
* i_atime_extra is the last extra field available for [acm]times in
* struct ext4_inode. Checking for that field should suffice to ensure
* we have extra space for all three.
*/
if (sbi->s_inode_size >= offsetof(struct ext4_inode, i_atime_extra) +
sizeof(((struct ext4_inode *)0)->i_atime_extra)) {
sb->s_time_gran = 1;
sb->s_time_max = EXT4_EXTRA_TIMESTAMP_MAX;
} else {
sb->s_time_gran = NSEC_PER_SEC;
sb->s_time_max = EXT4_NON_EXTRA_TIMESTAMP_MAX;
}
sb->s_time_min = EXT4_TIMESTAMP_MIN;
}
sbi->s_desc_size = le16_to_cpu(es->s_desc_size); sbi->s_desc_size = le16_to_cpu(es->s_desc_size);
if (ext4_has_feature_64bit(sb)) { if (ext4_has_feature_64bit(sb)) {
if (sbi->s_desc_size < EXT4_MIN_DESC_SIZE_64BIT || if (sbi->s_desc_size < EXT4_MIN_DESC_SIZE_64BIT ||
...@@ -4517,8 +4516,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) ...@@ -4517,8 +4516,6 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
} else if (ret) } else if (ret)
goto failed_mount4a; goto failed_mount4a;
ext4_clamp_want_extra_isize(sb);
ext4_set_resv_clusters(sb); ext4_set_resv_clusters(sb);
err = ext4_setup_system_zone(sb); err = ext4_setup_system_zone(sb);
...@@ -5306,8 +5303,6 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data) ...@@ -5306,8 +5303,6 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
goto restore_opts; goto restore_opts;
} }
ext4_clamp_want_extra_isize(sb);
if ((old_opts.s_mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) ^ if ((old_opts.s_mount_opt & EXT4_MOUNT_JOURNAL_CHECKSUM) ^
test_opt(sb, JOURNAL_CHECKSUM)) { test_opt(sb, JOURNAL_CHECKSUM)) {
ext4_msg(sb, KERN_ERR, "changing journal_checksum " ext4_msg(sb, KERN_ERR, "changing journal_checksum "
......
...@@ -457,7 +457,7 @@ struct jbd2_revoke_table_s; ...@@ -457,7 +457,7 @@ struct jbd2_revoke_table_s;
* @h_journal: Which journal handle belongs to - used iff h_reserved set. * @h_journal: Which journal handle belongs to - used iff h_reserved set.
* @h_rsv_handle: Handle reserved for finishing the logical operation. * @h_rsv_handle: Handle reserved for finishing the logical operation.
* @h_total_credits: Number of remaining buffers we are allowed to add to * @h_total_credits: Number of remaining buffers we are allowed to add to
journal. These are dirty buffers and revoke descriptor blocks. * journal. These are dirty buffers and revoke descriptor blocks.
* @h_revoke_credits: Number of remaining revoke records available for handle * @h_revoke_credits: Number of remaining revoke records available for handle
* @h_ref: Reference count on this handle. * @h_ref: Reference count on this handle.
* @h_err: Field for caller's use to track errors through large fs operations. * @h_err: Field for caller's use to track errors through large fs operations.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment