Skip to content

Commit

Permalink
dump.f2fs: Dump symlinks as symlinks
Browse files Browse the repository at this point in the history
Previously, dumped symlinks would always create regular files instead.
This allows symlinks to be dumped as symlinks with the -L option.

The i_name field's name may not be the same as the actual name from the
dirent, so we use the dirent name when available.

Currently hardlinks aren't detected, so print a warning if we notice a
nondirectory with a link count over 1.

Test: atest CopyEfsTest
Flag: TEST_ONLY
Change-Id: I9c25bcdd44274108b5720f317b187515f64c3622
Signed-off-by: Daniel Rosenberg <[email protected]>
  • Loading branch information
drosen-google committed Jul 17, 2024
1 parent 584ebc7 commit fdcb252
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 34 deletions.
117 changes: 88 additions & 29 deletions fsck/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,27 @@ static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
{
int i;
int name_len;
char name[F2FS_NAME_LEN + 1] = {0};

for (i = 0; i < max; i++) {
if (test_bit_le(i, bitmap) == 0)
continue;
name_len = le16_to_cpu(dentry[i].name_len);
if (name_len == 0 || name_len > F2FS_NAME_LEN) {
MSG(c.force, "Wrong name info\n\n");
ASSERT(name_len == 0 || name_len > F2FS_NAME_LEN);
}
if (name_len == 1 && filenames[i][0] == '.')
continue;
if (name_len == 2 && filenames[i][0] == '.' && filenames[i][1] == '.')
continue;
dump_node(sbi, le32_to_cpu(dentry[i].ino), 1, NULL, 0, 1);
strncpy(name, (const char *)filenames[i], name_len);
name[name_len] = 0;
dump_node(sbi, le32_to_cpu(dentry[i].ino), 1, NULL, 0, 1, name);
}
}

static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, int type)
{
char buf[F2FS_BLKSIZE];

Expand Down Expand Up @@ -307,19 +314,23 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, b
ASSERT(ret >= 0);
}

if (is_folder) {
if (S_ISDIR(type)) {
struct f2fs_dentry_block *d = (struct f2fs_dentry_block *) buf;

dump_folder_contents(sbi, d->dentry_bitmap, F2FS_DENTRY_BLOCK_DENTRIES(d),
F2FS_DENTRY_BLOCK_FILENAMES(d), NR_DENTRY_IN_BLOCK);
#if !defined(__MINGW32__)
} if (S_ISLNK(type)) {
dev_write_symlink(buf, c.dump_sym_target_len);
#endif
} else {
/* write blkaddr */
dev_write_dump(buf, offset, F2FS_BLKSIZE);
}
}

static void dump_node_blk(struct f2fs_sb_info *sbi, int ntype,
u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
u32 nid, u32 addr_per_block, u64 *ofs, int type)
{
struct node_info ni;
struct f2fs_node *node_blk;
Expand Down Expand Up @@ -356,28 +367,28 @@ static void dump_node_blk(struct f2fs_sb_info *sbi, int ntype,
switch (ntype) {
case TYPE_DIRECT_NODE:
dump_data_blk(sbi, *ofs * F2FS_BLKSIZE,
le32_to_cpu(node_blk->dn.addr[i]), is_dir);
le32_to_cpu(node_blk->dn.addr[i]), type);
(*ofs)++;
break;
case TYPE_INDIRECT_NODE:
dump_node_blk(sbi, TYPE_DIRECT_NODE,
le32_to_cpu(node_blk->in.nid[i]),
addr_per_block,
ofs, is_dir);
ofs, type);
break;
case TYPE_DOUBLE_INDIRECT_NODE:
dump_node_blk(sbi, TYPE_INDIRECT_NODE,
le32_to_cpu(node_blk->in.nid[i]),
addr_per_block,
ofs, is_dir);
ofs, type);
break;
}
}
free(node_blk);
}

#ifdef HAVE_FSETXATTR
static void dump_xattr(struct f2fs_sb_info *sbi, struct f2fs_node *node_blk, int is_dir)
static void dump_xattr(struct f2fs_sb_info *sbi, struct f2fs_node *node_blk, int type)
{
void *xattr;
void *last_base_addr;
Expand Down Expand Up @@ -431,19 +442,26 @@ static void dump_xattr(struct f2fs_sb_info *sbi, struct f2fs_node *node_blk, int

DBG(1, "fd %d xattr_name %s\n", c.dump_fd, xattr_name);
#if defined(__linux__)
if (is_dir) {
if (S_ISDIR(type)) {
ret = setxattr(".", xattr_name, value,
le16_to_cpu(ent->e_value_size), 0);
} if (S_ISLNK(type) && c.preserve_symlinks) {
ret = lsetxattr(c.dump_symlink, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0);
} else {
ret = fsetxattr(c.dump_fd, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0);
}

#elif defined(__APPLE__)
if (is_dir) {
if (S_ISDIR(type)) {
ret = setxattr(".", xattr_name, value,
le16_to_cpu(ent->e_value_size), 0,
XATTR_CREATE);
} if (S_ISLNK(type) && c.preserve_symlinks) {
ret = lsetxattr(c.dump_symlink, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0,
XATTR_CREATE);
} else {
ret = fsetxattr(c.dump_fd, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0,
Expand Down Expand Up @@ -473,14 +491,21 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
u32 i = 0;
u64 ofs = 0;
u32 addr_per_block;
bool is_dir = S_ISDIR(le16_to_cpu(node_blk->i.i_mode));
u16 type = le16_to_cpu(node_blk->i.i_mode);
int ret = 0;

if ((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
DBG(3, "ino[0x%x] has inline data!\n", nid);
/* recover from inline data */
dev_write_dump(inline_data_addr(node_blk),
#if !defined(__MINGW32__)
if (S_ISLNK(type) && c.preserve_symlinks) {
dev_write_symlink(inline_data_addr(node_blk), c.dump_sym_target_len);
} else
#endif
{
dev_write_dump(inline_data_addr(node_blk),
0, MAX_INLINE_DATA(node_blk));
}
ret = -1;
goto dump_xattr;
}
Expand All @@ -504,7 +529,7 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
/* check data blocks in inode */
for (i = 0; i < ADDRS_PER_INODE(&node_blk->i); i++, ofs++)
dump_data_blk(sbi, ofs * F2FS_BLKSIZE, le32_to_cpu(
node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
node_blk->i.i_addr[get_extra_isize(node_blk) + i]), type);

/* check node blocks in inode */
for (i = 0; i < 5; i++) {
Expand All @@ -513,26 +538,26 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block,
&ofs,
is_dir);
type);
else if (i == 2 || i == 3)
dump_node_blk(sbi, TYPE_INDIRECT_NODE,
le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block,
&ofs,
is_dir);
type);
else if (i == 4)
dump_node_blk(sbi, TYPE_DOUBLE_INDIRECT_NODE,
le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block,
&ofs,
is_dir);
type);
else
ASSERT(0);
}
/* last block in extent cache */
print_extent(true);
dump_xattr:
dump_xattr(sbi, node_blk, is_dir);
dump_xattr(sbi, node_blk, type);
return ret;
}

Expand All @@ -555,6 +580,23 @@ static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
close(c.dump_fd);
}

static void dump_link(struct f2fs_sb_info *sbi, struct node_info *ni,
struct f2fs_node *node_blk, char *name)
{
#if defined(__MINGW32__)
dump_file(sbi, ni, node_blk, path, path);
#else
struct f2fs_inode *inode = &node_blk->i;
int len = le32_to_cpu(inode->i_size);

if (!c.preserve_symlinks)
return dump_file(sbi, ni, node_blk, name);
c.dump_symlink = name;
c.dump_sym_target_len = len + 1;
dump_inode_blk(sbi, ni->ino, node_blk);
#endif
}

static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
struct f2fs_node *node_blk, char *path, int is_root)
{
Expand All @@ -580,18 +622,24 @@ static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,

static int dump_filesystem(struct f2fs_sb_info *sbi, struct node_info *ni,
struct f2fs_node *node_blk, int force, char *base_path,
bool is_base, bool allow_folder)
bool is_base, bool allow_folder, char *dirent_name)
{
struct f2fs_inode *inode = &node_blk->i;
u32 imode = le16_to_cpu(inode->i_mode);
u32 namelen = le32_to_cpu(inode->i_namelen);
char name[F2FS_NAME_LEN + 1] = {0};
u32 ilinks = le32_to_cpu(inode->i_links);
u32 i_namelen = le32_to_cpu(inode->i_namelen);
char i_name[F2FS_NAME_LEN + 1] = {0};
char *name;
char path[1024] = {0};
char ans[255] = {0};
int is_encrypted = file_is_encrypt(inode);
int is_root = sbi->root_ino_num == ni->nid;
int ret;

if (!S_ISDIR(imode) && ilinks != 1) {
MSG(force, "Warning: Hard link detected. Dumped files may be duplicated\n");
}

if (is_encrypted) {
MSG(force, "File is encrypted\n");
return -1;
Expand All @@ -601,7 +649,7 @@ static int dump_filesystem(struct f2fs_sb_info *sbi, struct node_info *ni,
MSG(force, "Not a valid file type\n\n");
return -1;
}
if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
if (!is_root && !dirent_name && (i_namelen == 0 || i_namelen > F2FS_NAME_LEN)) {
MSG(force, "Wrong name info\n\n");
return -1;
}
Expand Down Expand Up @@ -635,23 +683,34 @@ static int dump_filesystem(struct f2fs_sb_info *sbi, struct node_info *ni,

/* make a file */
if (!is_root) {
strncpy(name, (const char *)inode->i_name, namelen);
name[namelen] = 0;
/* The i_name name may be out of date. Prefer dirent_name */
if (dirent_name) {
name = dirent_name;
} else {
strncpy(i_name, (const char *)inode->i_name, i_namelen);
i_name[i_namelen] = 0;
name = i_name;
}
}

if (S_ISREG(imode) || S_ISLNK(imode)) {
if (S_ISREG(imode)) {
dump_file(sbi, ni, node_blk, name);
} else if S_ISLNK(imode) {
dump_link(sbi, ni, node_blk, name);
} else {
dump_folder(sbi, ni, node_blk, name, is_root);
}

#if !defined(__MINGW32__)
/* fix up mode/owner */
if (c.preserve_perms) {
if (is_root)
if (is_root) {
name = i_name;
strncpy(name, ".", 2);
ASSERT(chmod(name, imode) == 0);
ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
}
if (!S_ISLNK(imode))
ASSERT(chmod(name, imode) == 0);
ASSERT(lchown(name, inode->i_uid, inode->i_gid) == 0);
}
#endif
if (is_base)
Expand Down Expand Up @@ -705,7 +764,7 @@ void dump_node_scan_disk(struct f2fs_sb_info *sbi, nid_t nid)
free(node_blk);
}

int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder, char *dirent_name)
{
struct node_info ni;
struct f2fs_node *node_blk;
Expand Down Expand Up @@ -740,7 +799,7 @@ int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, i
print_node_info(sbi, node_blk, force);

if (ni.ino == ni.nid)
ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder, dirent_name);
} else {
print_node_info(sbi, node_blk, force);
MSG(force, "Invalid (i)node block\n\n");
Expand Down
4 changes: 2 additions & 2 deletions fsck/fsck.c
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@ static void print_dentry(struct f2fs_sb_info *sbi, __u8 *name,
d = d->next;
}
printf("/%s", new);
if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0, NULL))
printf("\33[2K\r");
} else {
for (i = 1; i < depth; i++)
Expand Down Expand Up @@ -3632,7 +3632,7 @@ int fsck_verify(struct f2fs_sb_info *sbi)
if (!strcasecmp(ans, "y")) {
for (i = 0; i < fsck->nr_nat_entries; i++) {
if (f2fs_test_bit(i, fsck->nat_area_bitmap))
dump_node(sbi, i, 1, NULL, 1, 0);
dump_node(sbi, i, 1, NULL, 1, 0, NULL);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion fsck/fsck.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ struct dump_option {
extern void nat_dump(struct f2fs_sb_info *, nid_t, nid_t);
extern void sit_dump(struct f2fs_sb_info *, unsigned int, unsigned int);
extern void ssa_dump(struct f2fs_sb_info *, int, int);
extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int, char *);
extern int dump_info_from_blkaddr(struct f2fs_sb_info *, u32);
extern unsigned int start_bidx_of_node(unsigned int, struct f2fs_node *);
extern void dump_node_scan_disk(struct f2fs_sb_info *sbi, nid_t nid);
Expand Down
13 changes: 11 additions & 2 deletions fsck/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void dump_usage()
MSG(0, " -y alias for -f\n");
MSG(0, " -o dump inodes to the given path\n");
MSG(0, " -P preserve mode/owner/group for dumped inode\n");
MSG(0, " -L Preserves symlinks. Otherwise symlinks are dumped as regular files.\n");
MSG(0, " -V print the version number and exit\n");

exit(1);
Expand Down Expand Up @@ -389,7 +390,7 @@ void f2fs_parse_options(int argc, char *argv[])
}
} else if (!strcmp("dump.f2fs", prog)) {
#ifdef WITH_DUMP
const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
const char *option_string = "d:fi:I:n:LMo:Prs:Sa:b:Vy";
static struct dump_option dump_opt = {
.nid = 0, /* default root ino */
.start_nat = -1,
Expand Down Expand Up @@ -481,6 +482,14 @@ void f2fs_parse_options(int argc, char *argv[])
c.preserve_perms = 1;
#endif
break;
case 'L':
#if defined(__MINGW32__)
MSG(0, "-L not supported for Windows\n");
err = EWRONG_OPT;
#else
c.preserve_symlinks = 1;
break;
#endif
case 'V':
show_version(prog);
exit(0);
Expand Down Expand Up @@ -957,7 +966,7 @@ static void do_dump(struct f2fs_sb_info *sbi)
if (opt->blk_addr != -1)
dump_info_from_blkaddr(sbi, opt->blk_addr);
if (opt->nid)
dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1, NULL);
if (opt->scan_nid)
dump_node_scan_disk(sbi, opt->scan_nid);

Expand Down
8 changes: 8 additions & 0 deletions include/f2fs_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,8 @@ struct f2fs_configuration {
uint16_t s_encoding_flags;
int32_t kd;
int32_t dump_fd;
char *dump_symlink;
int dump_sym_target_len;
struct device_info devices[MAX_DEVICES];
int ndevs;
char *extension_list[2];
Expand Down Expand Up @@ -1540,7 +1542,10 @@ struct f2fs_configuration {
struct selinux_opt seopt_file[8];
int nr_opt;
#endif

/* dump parameters */
int preserve_perms;
int preserve_symlinks;

/* resize parameters */
int safe_resize;
Expand Down Expand Up @@ -1614,6 +1619,9 @@ extern int dev_readahead(__u64, size_t UNUSED(len));
extern int dev_write(void *, __u64, size_t);
extern int dev_write_block(void *, __u64);
extern int dev_write_dump(void *, __u64, size_t);
#if !defined(__MINGW32__)
extern int dev_write_symlink(char *, size_t);
#endif
/* All bytes in the buffer must be 0 use dev_fill(). */
extern int dev_fill(void *, __u64, size_t);
extern int dev_fill_block(void *, __u64);
Expand Down
Loading

0 comments on commit fdcb252

Please sign in to comment.