diff mbox series

[f2fs-dev,v4,1/2] dump.f2fs: Add ability to dump folders

Message ID 20240523224700.265251-1-drosen@google.com (mailing list archive)
State New
Headers show
Series [f2fs-dev,v4,1/2] dump.f2fs: Add ability to dump folders | expand

Commit Message

Daniel Rosenberg May 23, 2024, 10:46 p.m. UTC
This adds the ability to dump folders as well as files. Folders are
dumped recursively. Additionally, dumped files/folders may be directed
to a folder specified by -o [path] instead of ./lost_found. The -r flag
will dump the entire fs from the root inode. -f or -y will skip the
prompt before dumping, and -P will preserve the mode/owner info for the
created file/folder.

Signed-off-by: Daniel Rosenberg <drosen@google.com>
Reviewed-by: Daeho Jeong <daehojeong@google.com>
---
 fsck/dump.c     | 178 ++++++++++++++++++++++++++++++++++++++----------
 fsck/fsck.c     |   4 +-
 fsck/fsck.h     |   4 +-
 fsck/main.c     |  29 +++++++-
 man/dump.f2fs.8 |  17 ++++-
 5 files changed, 190 insertions(+), 42 deletions(-)


base-commit: 5da4e5241503b385e4a7e75b1b2bb3367b38be96

Comments

Daeho Jeong May 23, 2024, 11:33 p.m. UTC | #1
On Thu, May 23, 2024 at 3:48 PM Daniel Rosenberg via Linux-f2fs-devel
<linux-f2fs-devel@lists.sourceforge.net> wrote:
>
> This adds the ability to dump folders as well as files. Folders are
> dumped recursively. Additionally, dumped files/folders may be directed
> to a folder specified by -o [path] instead of ./lost_found. The -r flag
> will dump the entire fs from the root inode. -f or -y will skip the
> prompt before dumping, and -P will preserve the mode/owner info for the
> created file/folder.
>
> Signed-off-by: Daniel Rosenberg <drosen@google.com>
> Reviewed-by: Daeho Jeong <daehojeong@google.com>
> ---
>  fsck/dump.c     | 178 ++++++++++++++++++++++++++++++++++++++----------
>  fsck/fsck.c     |   4 +-
>  fsck/fsck.h     |   4 +-
>  fsck/main.c     |  29 +++++++-
>  man/dump.f2fs.8 |  17 ++++-
>  5 files changed, 190 insertions(+), 42 deletions(-)
>
> diff --git a/fsck/dump.c b/fsck/dump.c
> index b2e990b..fa68456 100644
> --- a/fsck/dump.c
> +++ b/fsck/dump.c
> @@ -247,7 +247,26 @@ out:
>                 printf("\n");
>  }
>
> -static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
> +static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
> +                               struct f2fs_dir_entry *dentry,
> +                               __u8 (*filenames)[F2FS_SLOT_LEN], int max)
> +{
> +       int i;
> +       int name_len;
> +
> +       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 == 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);
> +       }
> +}
> +
> +static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
>  {
>         char buf[F2FS_BLKSIZE];
>
> @@ -288,12 +307,19 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
>                 ASSERT(ret >= 0);
>         }
>
> -       /* write blkaddr */
> -       dev_write_dump(buf, offset, F2FS_BLKSIZE);
> +       if (is_folder) {
> +               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);
> +       } 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)
> +                               u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
>  {
>         struct node_info ni;
>         struct f2fs_node *node_blk;
> @@ -330,20 +356,20 @@ 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]));
> +                                       le32_to_cpu(node_blk->dn.addr[i]), is_dir);
>                         (*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);
> +                                       ofs, is_dir);
>                         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);
> +                                       ofs, is_dir);
>                         break;
>                 }
>         }
> @@ -435,8 +461,9 @@ 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));
>
> -       if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
> +       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(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
> @@ -444,13 +471,25 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>                 return -1;
>         }
>
> +       if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
> +               void *inline_dentry = inline_data_addr(node_blk);
> +               struct f2fs_dentry_ptr d;
> +
> +               make_dentry_ptr(&d, node_blk, inline_dentry, 2);
> +
> +               DBG(3, "ino[0x%x] has inline dentries!\n", nid);
> +               /* recover from inline dentry */
> +               dump_folder_contents(sbi, d.bitmap, d.dentry, d.filename, d.max);
> +               return -1;
> +       }
> +
>         c.show_file_map_max_offset = f2fs_max_file_offset(&node_blk->i);
>         addr_per_block = ADDRS_PER_BLOCK(&node_blk->i);
>
>         /* 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]));
> +                       node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
>
>         /* check node blocks in inode */
>         for (i = 0; i < 5; i++) {
> @@ -458,17 +497,20 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>                         dump_node_blk(sbi, TYPE_DIRECT_NODE,
>                                         le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
>                                         addr_per_block,
> -                                       &ofs);
> +                                       &ofs,
> +                                       is_dir);
>                 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);
> +                                       &ofs,
> +                                       is_dir);
>                 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);
> +                                       &ofs,
> +                                       is_dir);
>                 else
>                         ASSERT(0);
>         }
> @@ -479,8 +521,51 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>         return 0;
>  }
>
> -static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> -                               struct f2fs_node *node_blk, int force)
> +static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> +                               struct f2fs_node *node_blk, char *path)
> +{
> +       struct f2fs_inode *inode = &node_blk->i;
> +       int ret;
> +
> +       c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> +       ASSERT(c.dump_fd >= 0);
> +
> +       /* dump file's data */
> +       dump_inode_blk(sbi, ni->ino, node_blk);
> +
> +       /* adjust file size */
> +       ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> +       ASSERT(ret >= 0);
> +
> +       close(c.dump_fd);
> +}
> +
> +static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
> +                               struct f2fs_node *node_blk, char *path, int is_root)
> +{
> +       if (!is_root) {
> +#if defined(__MINGW32__)
> +               if (mkdir(path) < 0 && errno != EEXIST) {
> +                       MSG(0, "Failed to create directory %s\n", path);
> +                       return;
> +               }
> +#else
> +               if (mkdir(path, 0777) < 0 && errno != EEXIST) {
> +                       MSG(0, "Failed to create directory %s\n", path);
> +                       return;
> +               }
> +#endif
> +               ASSERT(chdir(path) == 0);
> +       }
> +       /* dump folder data */
> +       dump_inode_blk(sbi, ni->ino, node_blk);
> +       if (!is_root)
> +               ASSERT(chdir("..") == 0);
> +}
> +
> +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)
>  {
>         struct f2fs_inode *inode = &node_blk->i;
>         u32 imode = le16_to_cpu(inode->i_mode);
> @@ -489,6 +574,7 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>         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 (is_encrypted) {
> @@ -496,11 +582,15 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>                 return -1;
>         }
>
> -       if ((!S_ISREG(imode) && !S_ISLNK(imode)) ||
> -                               namelen == 0 || namelen > F2FS_NAME_LEN) {
> -               MSG(force, "Not a regular file or wrong name info\n\n");
> +       if ((!S_ISREG(imode) && !S_ISLNK(imode) && !(S_ISDIR(imode) && allow_folder))) {
> +               MSG(force, "Not a valid file type\n\n");
> +               return -1;
> +       }
> +       if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
> +               MSG(force, "Wrong name info\n\n");
>                 return -1;
>         }
> +       base_path = base_path ?: "./lost_found";
>         if (force)
>                 goto dump;
>
> @@ -508,31 +598,49 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>         if (c.show_file_map)
>                 return dump_inode_blk(sbi, ni->ino, node_blk);
>
> -       printf("Do you want to dump this file into ./lost_found/? [Y/N] ");
> +       printf("Do you want to dump this %s into %s/? [Y/N] ",
> +                       S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder",
> +                       base_path);
>         ret = scanf("%s", ans);
>         ASSERT(ret >= 0);
>
>         if (!strcasecmp(ans, "y")) {
>  dump:
> -               ret = system("mkdir -p ./lost_found");
> -               ASSERT(ret >= 0);
> -
> -               /* make a file */
> -               strncpy(name, (const char *)inode->i_name, namelen);
> -               name[namelen] = 0;
> -               sprintf(path, "./lost_found/%s", name);
> +               if (is_base) {
> +                       ASSERT(getcwd(path, sizeof(path)) != NULL);
> +#if defined(__MINGW32__)
> +                       ret = mkdir(base_path);
> +#else
> +                       ret = mkdir(base_path, 0777);
> +#endif
>
> -               c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> -               ASSERT(c.dump_fd >= 0);
> +                       ASSERT(ret == 0 || errno == EEXIST);
> +                       ASSERT(chdir(base_path) == 0);
> +               }
>
> -               /* dump file's data */
> -               dump_inode_blk(sbi, ni->ino, node_blk);
> +               /* make a file */
> +               if (!is_root) {
> +                       strncpy(name, (const char *)inode->i_name, namelen);
> +                       name[namelen] = 0;
> +               }
>
> -               /* adjust file size */
> -               ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> -               ASSERT(ret >= 0);
> +               if (S_ISREG(imode) || S_ISLNK(imode)) {
> +                       dump_file(sbi, ni, node_blk, name);
> +               } else {
> +                       dump_folder(sbi, ni, node_blk, name, is_root);
> +               }
>
> -               close(c.dump_fd);
> +#if !defined(__MINGW32__)
> +               /* fix up mode/owner */
> +               if (c.preserve_perms) {
> +                       if (is_root)
> +                               strncpy(name, ".", 2);
> +                       ASSERT(chmod(name, imode) == 0);
> +                       ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
> +               }
> +#endif
> +               if (is_base)
> +                       ASSERT(chdir(path) == 0);
>         }
>         return 0;
>  }
> @@ -582,7 +690,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)
> +int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
>  {
>         struct node_info ni;
>         struct f2fs_node *node_blk;
> @@ -617,7 +725,7 @@ int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force)
>                         print_node_info(sbi, node_blk, force);
>
>                 if (ni.ino == ni.nid)
> -                       ret = dump_file(sbi, &ni, node_blk, force);
> +                       ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
>         } else {
>                 print_node_info(sbi, node_blk, force);
>                 MSG(force, "Invalid (i)node block\n\n");
> diff --git a/fsck/fsck.c b/fsck/fsck.c
> index 5d345d0..7400dcf 100644
> --- a/fsck/fsck.c
> +++ b/fsck/fsck.c
> @@ -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))
> +               if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
>                         printf("\33[2K\r");
>         } else {
>                 for (i = 1; i < depth; i++)
> @@ -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);
> +                                       dump_node(sbi, i, 1, NULL, 1, 0);
>                         }
>                 }
>         }
> diff --git a/fsck/fsck.h b/fsck/fsck.h
> index f5282e2..6cac926 100644
> --- a/fsck/fsck.h
> +++ b/fsck/fsck.h
> @@ -270,12 +270,14 @@ struct dump_option {
>         int end_ssa;
>         int32_t blk_addr;
>         nid_t scan_nid;
> +       int use_root_nid;
> +       char *base_path;
>  };
>
>  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);
> +extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
>  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);
> diff --git a/fsck/main.c b/fsck/main.c
> index c4d0956..6edc902 100644
> --- a/fsck/main.c
> +++ b/fsck/main.c
> @@ -34,7 +34,7 @@ struct f2fs_fsck gfsck;
>
>  INIT_FEATURE_TABLE;
>
> -#ifdef WITH_SLOAD
> +#if defined(WITH_SLOAD) || defined(WITH_DUMP)
>  static char *absolute_path(const char *file)
>  {
>         char *ret;
> @@ -384,7 +384,7 @@ void f2fs_parse_options(int argc, char *argv[])
>                 }
>         } else if (!strcmp("dump.f2fs", prog)) {
>  #ifdef WITH_DUMP
> -               const char *option_string = "d:i:I:n:Ms:Sa:b:V";
> +               const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
>                 static struct dump_option dump_opt = {
>                         .nid = 0,       /* default root ino */
>                         .start_nat = -1,
> @@ -395,6 +395,8 @@ void f2fs_parse_options(int argc, char *argv[])
>                         .end_ssa = -1,
>                         .blk_addr = -1,
>                         .scan_nid = 0,
> +                       .use_root_nid = 0,
> +                       .base_path = NULL,
>                 };
>
>                 c.func = DUMP;
> @@ -456,6 +458,24 @@ void f2fs_parse_options(int argc, char *argv[])
>                                         ret = sscanf(optarg, "%x",
>                                                         &dump_opt.blk_addr);
>                                 break;
> +                       case 'y':
> +                       case 'f':
> +                               c.force = 1;
> +                               break;
> +                       case 'r':
> +                               dump_opt.use_root_nid = 1;
> +                               break;
> +                       case 'o':
> +                               dump_opt.base_path = absolute_path(optarg);
> +                               break;
> +                       case 'P':
> +#if defined(__MINGW32__)
> +                               MSG(0, "-P not supported for Windows\n");
> +                               err = EWRONG_OPT;
> +#else
> +                               c.preserve_perms = 1;
> +#endif
> +                               break;
>                         case 'V':
>                                 show_version(prog);
>                                 exit(0);
> @@ -914,6 +934,9 @@ static void do_dump(struct f2fs_sb_info *sbi)
>         struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
>         u32 flag = le32_to_cpu(ckpt->ckpt_flags);
>
> +       if (opt->use_root_nid)
> +               opt->nid = sbi->root_ino_num;
> +
>         if (opt->end_nat == -1)
>                 opt->end_nat = NM_I(sbi)->max_nid;
>         if (opt->end_sit == -1)
> @@ -929,7 +952,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, 0);
> +               dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
>         if (opt->scan_nid)
>                 dump_node_scan_disk(sbi, opt->scan_nid);
>
> diff --git a/man/dump.f2fs.8 b/man/dump.f2fs.8
> index 94bf5f3..60d6783 100644
> --- a/man/dump.f2fs.8
> +++ b/man/dump.f2fs.8
> @@ -44,7 +44,8 @@ is used to retrieve f2fs metadata (usually in a disk partition).
>  \fIdevice\fP is the special file corresponding to the device (e.g.
>  \fI/dev/sdXX\fP).
>
> -Currently, it can retrieve 1) a file given its inode number, 2) NAT
> +Currently, it can retrieve 1) a file or folder given its inode number
> +(folders are dumped recursively), 2) NAT
>  entries into a file, 3) SIT entries into a file, 4) SSA entries into
>  a file, 5) reverse information from the given block address.
>  .PP
> @@ -56,6 +57,20 @@ is 0 on success and -1 on failure.
>  .BI \-i " inode number"
>  Specify an inode number to dump out.
>  .TP
> +.BI \-r
> +Dump out from the root inode.
> +.TP
> +.BI \-f
> +Do not prompt before dumping
> +.TP
> +.BI \-y
> +Alias for \-f
> +.TP
> +.BI \-o " path"
> +Dump inodes to the given path
> +.BI \-P
> +Preserve mode/owner/group for dumped inode
> +.TP
>  .BI \-I " inode number"
>  Specify an inode number and scan full disk to dump out, include history inode block
>  .TP
>
> base-commit: 5da4e5241503b385e4a7e75b1b2bb3367b38be96
> --
> 2.45.1.288.g0e0cd299f1-goog
>
>

Reviewed-by: Daeho Jeong <daehojeong@google.com>

Thanks,

>
> _______________________________________________
> Linux-f2fs-devel mailing list
> Linux-f2fs-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
Chao Yu May 29, 2024, 6:39 a.m. UTC | #2
On 2024/5/24 6:46, Daniel Rosenberg wrote:
> This adds the ability to dump folders as well as files. Folders are
> dumped recursively. Additionally, dumped files/folders may be directed
> to a folder specified by -o [path] instead of ./lost_found. The -r flag
> will dump the entire fs from the root inode. -f or -y will skip the
> prompt before dumping, and -P will preserve the mode/owner info for the
> created file/folder.
> 
> Signed-off-by: Daniel Rosenberg <drosen@google.com>
> Reviewed-by: Daeho Jeong <daehojeong@google.com>
> ---
>   fsck/dump.c     | 178 ++++++++++++++++++++++++++++++++++++++----------
>   fsck/fsck.c     |   4 +-
>   fsck/fsck.h     |   4 +-
>   fsck/main.c     |  29 +++++++-
>   man/dump.f2fs.8 |  17 ++++-
>   5 files changed, 190 insertions(+), 42 deletions(-)
> 
> diff --git a/fsck/dump.c b/fsck/dump.c
> index b2e990b..fa68456 100644
> --- a/fsck/dump.c
> +++ b/fsck/dump.c
> @@ -247,7 +247,26 @@ out:
>   		printf("\n");
>   }
>   
> -static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
> +static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
> +				struct f2fs_dir_entry *dentry,
> +				__u8 (*filenames)[F2FS_SLOT_LEN], int max)
> +{
> +	int i;
> +	int name_len;
> +
> +	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 == 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);
> +	}
> +}
> +
> +static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
>   {
>   	char buf[F2FS_BLKSIZE];
>   
> @@ -288,12 +307,19 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
>   		ASSERT(ret >= 0);
>   	}
>   
> -	/* write blkaddr */
> -	dev_write_dump(buf, offset, F2FS_BLKSIZE);
> +	if (is_folder) {
> +		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);
> +	} 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)
> +				u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
>   {
>   	struct node_info ni;
>   	struct f2fs_node *node_blk;
> @@ -330,20 +356,20 @@ 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]));
> +					le32_to_cpu(node_blk->dn.addr[i]), is_dir);
>   			(*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);
> +					ofs, is_dir);
>   			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);
> +					ofs, is_dir);
>   			break;
>   		}
>   	}
> @@ -435,8 +461,9 @@ 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));
>   
> -	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
> +	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(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
> @@ -444,13 +471,25 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>   		return -1;
>   	}
>   
> +	if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
> +		void *inline_dentry = inline_data_addr(node_blk);
> +		struct f2fs_dentry_ptr d;
> +
> +		make_dentry_ptr(&d, node_blk, inline_dentry, 2);
> +
> +		DBG(3, "ino[0x%x] has inline dentries!\n", nid);
> +		/* recover from inline dentry */
> +		dump_folder_contents(sbi, d.bitmap, d.dentry, d.filename, d.max);
> +		return -1;
> +	}
> +
>   	c.show_file_map_max_offset = f2fs_max_file_offset(&node_blk->i);
>   	addr_per_block = ADDRS_PER_BLOCK(&node_blk->i);
>   
>   	/* 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]));
> +			node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
>   
>   	/* check node blocks in inode */
>   	for (i = 0; i < 5; i++) {
> @@ -458,17 +497,20 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>   			dump_node_blk(sbi, TYPE_DIRECT_NODE,
>   					le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
>   					addr_per_block,
> -					&ofs);
> +					&ofs,
> +					is_dir);
>   		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);
> +					&ofs,
> +					is_dir);
>   		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);
> +					&ofs,
> +					is_dir);
>   		else
>   			ASSERT(0);
>   	}
> @@ -479,8 +521,51 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>   	return 0;
>   }
>   
> -static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> -				struct f2fs_node *node_blk, int force)
> +static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> +				struct f2fs_node *node_blk, char *path)
> +{
> +	struct f2fs_inode *inode = &node_blk->i;
> +	int ret;
> +
> +	c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> +	ASSERT(c.dump_fd >= 0);
> +
> +	/* dump file's data */
> +	dump_inode_blk(sbi, ni->ino, node_blk);
> +
> +	/* adjust file size */
> +	ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> +	ASSERT(ret >= 0);
> +
> +	close(c.dump_fd);
> +}
> +
> +static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
> +				struct f2fs_node *node_blk, char *path, int is_root)
> +{
> +	if (!is_root) {
> +#if defined(__MINGW32__)
> +		if (mkdir(path) < 0 && errno != EEXIST) {
> +			MSG(0, "Failed to create directory %s\n", path);
> +			return;
> +		}
> +#else
> +		if (mkdir(path, 0777) < 0 && errno != EEXIST) {
> +			MSG(0, "Failed to create directory %s\n", path);
> +			return;
> +		}
> +#endif
> +		ASSERT(chdir(path) == 0);
> +	}
> +	/* dump folder data */
> +	dump_inode_blk(sbi, ni->ino, node_blk);
> +	if (!is_root)
> +		ASSERT(chdir("..") == 0);
> +}
> +
> +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)
>   {
>   	struct f2fs_inode *inode = &node_blk->i;
>   	u32 imode = le16_to_cpu(inode->i_mode);
> @@ -489,6 +574,7 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>   	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 (is_encrypted) {
> @@ -496,11 +582,15 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>   		return -1;
>   	}
>   
> -	if ((!S_ISREG(imode) && !S_ISLNK(imode)) ||
> -				namelen == 0 || namelen > F2FS_NAME_LEN) {
> -		MSG(force, "Not a regular file or wrong name info\n\n");
> +	if ((!S_ISREG(imode) && !S_ISLNK(imode) && !(S_ISDIR(imode) && allow_folder))) {
> +		MSG(force, "Not a valid file type\n\n");
> +		return -1;
> +	}
> +	if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
> +		MSG(force, "Wrong name info\n\n");
>   		return -1;
>   	}
> +	base_path = base_path ?: "./lost_found";
>   	if (force)
>   		goto dump;
>   
> @@ -508,31 +598,49 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>   	if (c.show_file_map)
>   		return dump_inode_blk(sbi, ni->ino, node_blk);
>   
> -	printf("Do you want to dump this file into ./lost_found/? [Y/N] ");
> +	printf("Do you want to dump this %s into %s/? [Y/N] ",
> +			S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder",
> +			base_path);
>   	ret = scanf("%s", ans);
>   	ASSERT(ret >= 0);
>   
>   	if (!strcasecmp(ans, "y")) {
>   dump:
> -		ret = system("mkdir -p ./lost_found");
> -		ASSERT(ret >= 0);
> -
> -		/* make a file */
> -		strncpy(name, (const char *)inode->i_name, namelen);
> -		name[namelen] = 0;
> -		sprintf(path, "./lost_found/%s", name);
> +		if (is_base) {
> +			ASSERT(getcwd(path, sizeof(path)) != NULL);
> +#if defined(__MINGW32__)
> +			ret = mkdir(base_path);
> +#else
> +			ret = mkdir(base_path, 0777);
> +#endif
>   
> -		c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> -		ASSERT(c.dump_fd >= 0);
> +			ASSERT(ret == 0 || errno == EEXIST);
> +			ASSERT(chdir(base_path) == 0);
> +		}
>   
> -		/* dump file's data */
> -		dump_inode_blk(sbi, ni->ino, node_blk);
> +		/* make a file */
> +		if (!is_root) {
> +			strncpy(name, (const char *)inode->i_name, namelen);
> +			name[namelen] = 0;
> +		}
>   
> -		/* adjust file size */
> -		ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> -		ASSERT(ret >= 0);
> +		if (S_ISREG(imode) || S_ISLNK(imode)) {
> +			dump_file(sbi, ni, node_blk, name);
> +		} else {
> +			dump_folder(sbi, ni, node_blk, name, is_root);
> +		}
>   
> -		close(c.dump_fd);
> +#if !defined(__MINGW32__)
> +		/* fix up mode/owner */
> +		if (c.preserve_perms) {
> +			if (is_root)
> +				strncpy(name, ".", 2);
> +			ASSERT(chmod(name, imode) == 0);
> +			ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
> +		}
> +#endif
> +		if (is_base)
> +			ASSERT(chdir(path) == 0);
>   	}
>   	return 0;
>   }
> @@ -582,7 +690,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)
> +int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
>   {
>   	struct node_info ni;
>   	struct f2fs_node *node_blk;
> @@ -617,7 +725,7 @@ int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force)
>   			print_node_info(sbi, node_blk, force);
>   
>   		if (ni.ino == ni.nid)
> -			ret = dump_file(sbi, &ni, node_blk, force);
> +			ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
>   	} else {
>   		print_node_info(sbi, node_blk, force);
>   		MSG(force, "Invalid (i)node block\n\n");
> diff --git a/fsck/fsck.c b/fsck/fsck.c
> index 5d345d0..7400dcf 100644
> --- a/fsck/fsck.c
> +++ b/fsck/fsck.c
> @@ -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))
> +		if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
>   			printf("\33[2K\r");
>   	} else {
>   		for (i = 1; i < depth; i++)
> @@ -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);
> +					dump_node(sbi, i, 1, NULL, 1, 0);
>   			}
>   		}
>   	}
> diff --git a/fsck/fsck.h b/fsck/fsck.h
> index f5282e2..6cac926 100644
> --- a/fsck/fsck.h
> +++ b/fsck/fsck.h
> @@ -270,12 +270,14 @@ struct dump_option {
>   	int end_ssa;
>   	int32_t blk_addr;
>   	nid_t scan_nid;
> +	int use_root_nid;
> +	char *base_path;
>   };
>   
>   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);
> +extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
>   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);
> diff --git a/fsck/main.c b/fsck/main.c
> index c4d0956..6edc902 100644
> --- a/fsck/main.c
> +++ b/fsck/main.c
> @@ -34,7 +34,7 @@ struct f2fs_fsck gfsck;
>   
>   INIT_FEATURE_TABLE;
>   
> -#ifdef WITH_SLOAD
> +#if defined(WITH_SLOAD) || defined(WITH_DUMP)
>   static char *absolute_path(const char *file)
>   {
>   	char *ret;
> @@ -384,7 +384,7 @@ void f2fs_parse_options(int argc, char *argv[])
>   		}
>   	} else if (!strcmp("dump.f2fs", prog)) {
>   #ifdef WITH_DUMP
> -		const char *option_string = "d:i:I:n:Ms:Sa:b:V";
> +		const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
>   		static struct dump_option dump_opt = {
>   			.nid = 0,	/* default root ino */
>   			.start_nat = -1,
> @@ -395,6 +395,8 @@ void f2fs_parse_options(int argc, char *argv[])
>   			.end_ssa = -1,
>   			.blk_addr = -1,
>   			.scan_nid = 0,
> +			.use_root_nid = 0,
> +			.base_path = NULL,
>   		};
>   
>   		c.func = DUMP;
> @@ -456,6 +458,24 @@ void f2fs_parse_options(int argc, char *argv[])
>   					ret = sscanf(optarg, "%x",
>   							&dump_opt.blk_addr);
>   				break;
> +			case 'y':
> +			case 'f':
> +				c.force = 1;
> +				break;
> +			case 'r':
> +				dump_opt.use_root_nid = 1;
> +				break;
> +			case 'o':
> +				dump_opt.base_path = absolute_path(optarg);
> +				break;
> +			case 'P':
> +#if defined(__MINGW32__)
> +				MSG(0, "-P not supported for Windows\n");
> +				err = EWRONG_OPT;
> +#else
> +				c.preserve_perms = 1;
> +#endif
> +				break;
>   			case 'V':
>   				show_version(prog);
>   				exit(0);
> @@ -914,6 +934,9 @@ static void do_dump(struct f2fs_sb_info *sbi)
>   	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
>   	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
>   
> +	if (opt->use_root_nid)
> +		opt->nid = sbi->root_ino_num;
> +
>   	if (opt->end_nat == -1)
>   		opt->end_nat = NM_I(sbi)->max_nid;
>   	if (opt->end_sit == -1)
> @@ -929,7 +952,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, 0);
> +		dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
>   	if (opt->scan_nid)
>   		dump_node_scan_disk(sbi, opt->scan_nid);
>   
> diff --git a/man/dump.f2fs.8 b/man/dump.f2fs.8
> index 94bf5f3..60d6783 100644
> --- a/man/dump.f2fs.8
> +++ b/man/dump.f2fs.8
> @@ -44,7 +44,8 @@ is used to retrieve f2fs metadata (usually in a disk partition).
>   \fIdevice\fP is the special file corresponding to the device (e.g.
>   \fI/dev/sdXX\fP).
>   
> -Currently, it can retrieve 1) a file given its inode number, 2) NAT
> +Currently, it can retrieve 1) a file or folder given its inode number
> +(folders are dumped recursively), 2) NAT
>   entries into a file, 3) SIT entries into a file, 4) SSA entries into
>   a file, 5) reverse information from the given block address.
>   .PP
> @@ -56,6 +57,20 @@ is 0 on success and -1 on failure.
>   .BI \-i " inode number"
>   Specify an inode number to dump out.
>   .TP
> +.BI \-r
> +Dump out from the root inode.
> +.TP
> +.BI \-f
> +Do not prompt before dumping
> +.TP
> +.BI \-y
> +Alias for \-f
> +.TP
> +.BI \-o " path"
> +Dump inodes to the given path
> +.BI \-P
> +Preserve mode/owner/group for dumped inode
> +.TP

It needs to update dump_usage() as well.

Seems f2fs mailing list is out-of-response previously, so I resend it.

Thanks,

>   .BI \-I " inode number"
>   Specify an inode number and scan full disk to dump out, include history inode block
>   .TP
> 
> base-commit: 5da4e5241503b385e4a7e75b1b2bb3367b38be96
Jaegeuk Kim May 30, 2024, 11:41 p.m. UTC | #3
On 05/29, Chao Yu wrote:
> On 2024/5/24 6:46, Daniel Rosenberg wrote:
> > This adds the ability to dump folders as well as files. Folders are
> > dumped recursively. Additionally, dumped files/folders may be directed
> > to a folder specified by -o [path] instead of ./lost_found. The -r flag
> > will dump the entire fs from the root inode. -f or -y will skip the
> > prompt before dumping, and -P will preserve the mode/owner info for the
> > created file/folder.
> > 
> > Signed-off-by: Daniel Rosenberg <drosen@google.com>
> > Reviewed-by: Daeho Jeong <daehojeong@google.com>
> > ---
> >   fsck/dump.c     | 178 ++++++++++++++++++++++++++++++++++++++----------
> >   fsck/fsck.c     |   4 +-
> >   fsck/fsck.h     |   4 +-
> >   fsck/main.c     |  29 +++++++-
> >   man/dump.f2fs.8 |  17 ++++-
> >   5 files changed, 190 insertions(+), 42 deletions(-)
> > 
> > diff --git a/fsck/dump.c b/fsck/dump.c
> > index b2e990b..fa68456 100644
> > --- a/fsck/dump.c
> > +++ b/fsck/dump.c
> > @@ -247,7 +247,26 @@ out:
> >   		printf("\n");
> >   }
> > -static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
> > +static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
> > +				struct f2fs_dir_entry *dentry,
> > +				__u8 (*filenames)[F2FS_SLOT_LEN], int max)
> > +{
> > +	int i;
> > +	int name_len;
> > +
> > +	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 == 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);
> > +	}
> > +}
> > +
> > +static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
> >   {
> >   	char buf[F2FS_BLKSIZE];
> > @@ -288,12 +307,19 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
> >   		ASSERT(ret >= 0);
> >   	}
> > -	/* write blkaddr */
> > -	dev_write_dump(buf, offset, F2FS_BLKSIZE);
> > +	if (is_folder) {
> > +		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);
> > +	} 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)
> > +				u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
> >   {
> >   	struct node_info ni;
> >   	struct f2fs_node *node_blk;
> > @@ -330,20 +356,20 @@ 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]));
> > +					le32_to_cpu(node_blk->dn.addr[i]), is_dir);
> >   			(*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);
> > +					ofs, is_dir);
> >   			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);
> > +					ofs, is_dir);
> >   			break;
> >   		}
> >   	}
> > @@ -435,8 +461,9 @@ 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));
> > -	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
> > +	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(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
> > @@ -444,13 +471,25 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
> >   		return -1;
> >   	}
> > +	if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
> > +		void *inline_dentry = inline_data_addr(node_blk);
> > +		struct f2fs_dentry_ptr d;
> > +
> > +		make_dentry_ptr(&d, node_blk, inline_dentry, 2);
> > +
> > +		DBG(3, "ino[0x%x] has inline dentries!\n", nid);
> > +		/* recover from inline dentry */
> > +		dump_folder_contents(sbi, d.bitmap, d.dentry, d.filename, d.max);
> > +		return -1;
> > +	}
> > +
> >   	c.show_file_map_max_offset = f2fs_max_file_offset(&node_blk->i);
> >   	addr_per_block = ADDRS_PER_BLOCK(&node_blk->i);
> >   	/* 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]));
> > +			node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
> >   	/* check node blocks in inode */
> >   	for (i = 0; i < 5; i++) {
> > @@ -458,17 +497,20 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
> >   			dump_node_blk(sbi, TYPE_DIRECT_NODE,
> >   					le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
> >   					addr_per_block,
> > -					&ofs);
> > +					&ofs,
> > +					is_dir);
> >   		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);
> > +					&ofs,
> > +					is_dir);
> >   		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);
> > +					&ofs,
> > +					is_dir);
> >   		else
> >   			ASSERT(0);
> >   	}
> > @@ -479,8 +521,51 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
> >   	return 0;
> >   }
> > -static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> > -				struct f2fs_node *node_blk, int force)
> > +static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> > +				struct f2fs_node *node_blk, char *path)
> > +{
> > +	struct f2fs_inode *inode = &node_blk->i;
> > +	int ret;
> > +
> > +	c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> > +	ASSERT(c.dump_fd >= 0);
> > +
> > +	/* dump file's data */
> > +	dump_inode_blk(sbi, ni->ino, node_blk);
> > +
> > +	/* adjust file size */
> > +	ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> > +	ASSERT(ret >= 0);
> > +
> > +	close(c.dump_fd);
> > +}
> > +
> > +static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
> > +				struct f2fs_node *node_blk, char *path, int is_root)
> > +{
> > +	if (!is_root) {
> > +#if defined(__MINGW32__)
> > +		if (mkdir(path) < 0 && errno != EEXIST) {
> > +			MSG(0, "Failed to create directory %s\n", path);
> > +			return;
> > +		}
> > +#else
> > +		if (mkdir(path, 0777) < 0 && errno != EEXIST) {
> > +			MSG(0, "Failed to create directory %s\n", path);
> > +			return;
> > +		}
> > +#endif
> > +		ASSERT(chdir(path) == 0);
> > +	}
> > +	/* dump folder data */
> > +	dump_inode_blk(sbi, ni->ino, node_blk);
> > +	if (!is_root)
> > +		ASSERT(chdir("..") == 0);
> > +}
> > +
> > +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)
> >   {
> >   	struct f2fs_inode *inode = &node_blk->i;
> >   	u32 imode = le16_to_cpu(inode->i_mode);
> > @@ -489,6 +574,7 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> >   	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 (is_encrypted) {
> > @@ -496,11 +582,15 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> >   		return -1;
> >   	}
> > -	if ((!S_ISREG(imode) && !S_ISLNK(imode)) ||
> > -				namelen == 0 || namelen > F2FS_NAME_LEN) {
> > -		MSG(force, "Not a regular file or wrong name info\n\n");
> > +	if ((!S_ISREG(imode) && !S_ISLNK(imode) && !(S_ISDIR(imode) && allow_folder))) {
> > +		MSG(force, "Not a valid file type\n\n");
> > +		return -1;
> > +	}
> > +	if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
> > +		MSG(force, "Wrong name info\n\n");
> >   		return -1;
> >   	}
> > +	base_path = base_path ?: "./lost_found";
> >   	if (force)
> >   		goto dump;
> > @@ -508,31 +598,49 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
> >   	if (c.show_file_map)
> >   		return dump_inode_blk(sbi, ni->ino, node_blk);
> > -	printf("Do you want to dump this file into ./lost_found/? [Y/N] ");
> > +	printf("Do you want to dump this %s into %s/? [Y/N] ",
> > +			S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder",
> > +			base_path);
> >   	ret = scanf("%s", ans);
> >   	ASSERT(ret >= 0);
> >   	if (!strcasecmp(ans, "y")) {
> >   dump:
> > -		ret = system("mkdir -p ./lost_found");
> > -		ASSERT(ret >= 0);
> > -
> > -		/* make a file */
> > -		strncpy(name, (const char *)inode->i_name, namelen);
> > -		name[namelen] = 0;
> > -		sprintf(path, "./lost_found/%s", name);
> > +		if (is_base) {
> > +			ASSERT(getcwd(path, sizeof(path)) != NULL);
> > +#if defined(__MINGW32__)
> > +			ret = mkdir(base_path);
> > +#else
> > +			ret = mkdir(base_path, 0777);
> > +#endif
> > -		c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
> > -		ASSERT(c.dump_fd >= 0);
> > +			ASSERT(ret == 0 || errno == EEXIST);
> > +			ASSERT(chdir(base_path) == 0);
> > +		}
> > -		/* dump file's data */
> > -		dump_inode_blk(sbi, ni->ino, node_blk);
> > +		/* make a file */
> > +		if (!is_root) {
> > +			strncpy(name, (const char *)inode->i_name, namelen);
> > +			name[namelen] = 0;
> > +		}
> > -		/* adjust file size */
> > -		ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
> > -		ASSERT(ret >= 0);
> > +		if (S_ISREG(imode) || S_ISLNK(imode)) {
> > +			dump_file(sbi, ni, node_blk, name);
> > +		} else {
> > +			dump_folder(sbi, ni, node_blk, name, is_root);
> > +		}
> > -		close(c.dump_fd);
> > +#if !defined(__MINGW32__)
> > +		/* fix up mode/owner */
> > +		if (c.preserve_perms) {
> > +			if (is_root)
> > +				strncpy(name, ".", 2);
> > +			ASSERT(chmod(name, imode) == 0);
> > +			ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
> > +		}
> > +#endif
> > +		if (is_base)
> > +			ASSERT(chdir(path) == 0);
> >   	}
> >   	return 0;
> >   }
> > @@ -582,7 +690,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)
> > +int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
> >   {
> >   	struct node_info ni;
> >   	struct f2fs_node *node_blk;
> > @@ -617,7 +725,7 @@ int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force)
> >   			print_node_info(sbi, node_blk, force);
> >   		if (ni.ino == ni.nid)
> > -			ret = dump_file(sbi, &ni, node_blk, force);
> > +			ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
> >   	} else {
> >   		print_node_info(sbi, node_blk, force);
> >   		MSG(force, "Invalid (i)node block\n\n");
> > diff --git a/fsck/fsck.c b/fsck/fsck.c
> > index 5d345d0..7400dcf 100644
> > --- a/fsck/fsck.c
> > +++ b/fsck/fsck.c
> > @@ -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))
> > +		if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
> >   			printf("\33[2K\r");
> >   	} else {
> >   		for (i = 1; i < depth; i++)
> > @@ -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);
> > +					dump_node(sbi, i, 1, NULL, 1, 0);
> >   			}
> >   		}
> >   	}
> > diff --git a/fsck/fsck.h b/fsck/fsck.h
> > index f5282e2..6cac926 100644
> > --- a/fsck/fsck.h
> > +++ b/fsck/fsck.h
> > @@ -270,12 +270,14 @@ struct dump_option {
> >   	int end_ssa;
> >   	int32_t blk_addr;
> >   	nid_t scan_nid;
> > +	int use_root_nid;
> > +	char *base_path;
> >   };
> >   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);
> > +extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
> >   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);
> > diff --git a/fsck/main.c b/fsck/main.c
> > index c4d0956..6edc902 100644
> > --- a/fsck/main.c
> > +++ b/fsck/main.c
> > @@ -34,7 +34,7 @@ struct f2fs_fsck gfsck;
> >   INIT_FEATURE_TABLE;
> > -#ifdef WITH_SLOAD
> > +#if defined(WITH_SLOAD) || defined(WITH_DUMP)
> >   static char *absolute_path(const char *file)
> >   {
> >   	char *ret;
> > @@ -384,7 +384,7 @@ void f2fs_parse_options(int argc, char *argv[])
> >   		}
> >   	} else if (!strcmp("dump.f2fs", prog)) {
> >   #ifdef WITH_DUMP
> > -		const char *option_string = "d:i:I:n:Ms:Sa:b:V";
> > +		const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
> >   		static struct dump_option dump_opt = {
> >   			.nid = 0,	/* default root ino */
> >   			.start_nat = -1,
> > @@ -395,6 +395,8 @@ void f2fs_parse_options(int argc, char *argv[])
> >   			.end_ssa = -1,
> >   			.blk_addr = -1,
> >   			.scan_nid = 0,
> > +			.use_root_nid = 0,
> > +			.base_path = NULL,
> >   		};
> >   		c.func = DUMP;
> > @@ -456,6 +458,24 @@ void f2fs_parse_options(int argc, char *argv[])
> >   					ret = sscanf(optarg, "%x",
> >   							&dump_opt.blk_addr);
> >   				break;
> > +			case 'y':
> > +			case 'f':
> > +				c.force = 1;
> > +				break;
> > +			case 'r':
> > +				dump_opt.use_root_nid = 1;
> > +				break;
> > +			case 'o':
> > +				dump_opt.base_path = absolute_path(optarg);
> > +				break;
> > +			case 'P':
> > +#if defined(__MINGW32__)
> > +				MSG(0, "-P not supported for Windows\n");
> > +				err = EWRONG_OPT;
> > +#else
> > +				c.preserve_perms = 1;
> > +#endif
> > +				break;
> >   			case 'V':
> >   				show_version(prog);
> >   				exit(0);
> > @@ -914,6 +934,9 @@ static void do_dump(struct f2fs_sb_info *sbi)
> >   	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
> >   	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
> > +	if (opt->use_root_nid)
> > +		opt->nid = sbi->root_ino_num;
> > +
> >   	if (opt->end_nat == -1)
> >   		opt->end_nat = NM_I(sbi)->max_nid;
> >   	if (opt->end_sit == -1)
> > @@ -929,7 +952,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, 0);
> > +		dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
> >   	if (opt->scan_nid)
> >   		dump_node_scan_disk(sbi, opt->scan_nid);
> > diff --git a/man/dump.f2fs.8 b/man/dump.f2fs.8
> > index 94bf5f3..60d6783 100644
> > --- a/man/dump.f2fs.8
> > +++ b/man/dump.f2fs.8
> > @@ -44,7 +44,8 @@ is used to retrieve f2fs metadata (usually in a disk partition).
> >   \fIdevice\fP is the special file corresponding to the device (e.g.
> >   \fI/dev/sdXX\fP).
> > -Currently, it can retrieve 1) a file given its inode number, 2) NAT
> > +Currently, it can retrieve 1) a file or folder given its inode number
> > +(folders are dumped recursively), 2) NAT
> >   entries into a file, 3) SIT entries into a file, 4) SSA entries into
> >   a file, 5) reverse information from the given block address.
> >   .PP
> > @@ -56,6 +57,20 @@ is 0 on success and -1 on failure.
> >   .BI \-i " inode number"
> >   Specify an inode number to dump out.
> >   .TP
> > +.BI \-r
> > +Dump out from the root inode.
> > +.TP
> > +.BI \-f
> > +Do not prompt before dumping
> > +.TP
> > +.BI \-y
> > +Alias for \-f
> > +.TP
> > +.BI \-o " path"
> > +Dump inodes to the given path
> > +.BI \-P
> > +Preserve mode/owner/group for dumped inode
> > +.TP
> 
> It needs to update dump_usage() as well.
> 
> Seems f2fs mailing list is out-of-response previously, so I resend it.

Chao, can I ask for your help to write a patch for this?

Thanks,

> 
> Thanks,
> 
> >   .BI \-I " inode number"
> >   Specify an inode number and scan full disk to dump out, include history inode block
> >   .TP
> > 
> > base-commit: 5da4e5241503b385e4a7e75b1b2bb3367b38be96
Chao Yu May 31, 2024, 1:13 a.m. UTC | #4
On 2024/5/31 7:41, Jaegeuk Kim wrote:
> On 05/29, Chao Yu wrote:
>> On 2024/5/24 6:46, Daniel Rosenberg wrote:
>>> This adds the ability to dump folders as well as files. Folders are
>>> dumped recursively. Additionally, dumped files/folders may be directed
>>> to a folder specified by -o [path] instead of ./lost_found. The -r flag
>>> will dump the entire fs from the root inode. -f or -y will skip the
>>> prompt before dumping, and -P will preserve the mode/owner info for the
>>> created file/folder.
>>>
>>> Signed-off-by: Daniel Rosenberg <drosen@google.com>
>>> Reviewed-by: Daeho Jeong <daehojeong@google.com>
>>> ---
>>>    fsck/dump.c     | 178 ++++++++++++++++++++++++++++++++++++++----------
>>>    fsck/fsck.c     |   4 +-
>>>    fsck/fsck.h     |   4 +-
>>>    fsck/main.c     |  29 +++++++-
>>>    man/dump.f2fs.8 |  17 ++++-
>>>    5 files changed, 190 insertions(+), 42 deletions(-)
>>>
>>> diff --git a/fsck/dump.c b/fsck/dump.c
>>> index b2e990b..fa68456 100644
>>> --- a/fsck/dump.c
>>> +++ b/fsck/dump.c
>>> @@ -247,7 +247,26 @@ out:
>>>    		printf("\n");
>>>    }
>>> -static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
>>> +static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
>>> +				struct f2fs_dir_entry *dentry,
>>> +				__u8 (*filenames)[F2FS_SLOT_LEN], int max)
>>> +{
>>> +	int i;
>>> +	int name_len;
>>> +
>>> +	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 == 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);
>>> +	}
>>> +}
>>> +
>>> +static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
>>>    {
>>>    	char buf[F2FS_BLKSIZE];
>>> @@ -288,12 +307,19 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
>>>    		ASSERT(ret >= 0);
>>>    	}
>>> -	/* write blkaddr */
>>> -	dev_write_dump(buf, offset, F2FS_BLKSIZE);
>>> +	if (is_folder) {
>>> +		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);
>>> +	} 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)
>>> +				u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
>>>    {
>>>    	struct node_info ni;
>>>    	struct f2fs_node *node_blk;
>>> @@ -330,20 +356,20 @@ 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]));
>>> +					le32_to_cpu(node_blk->dn.addr[i]), is_dir);
>>>    			(*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);
>>> +					ofs, is_dir);
>>>    			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);
>>> +					ofs, is_dir);
>>>    			break;
>>>    		}
>>>    	}
>>> @@ -435,8 +461,9 @@ 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));
>>> -	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
>>> +	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(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
>>> @@ -444,13 +471,25 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>>>    		return -1;
>>>    	}
>>> +	if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
>>> +		void *inline_dentry = inline_data_addr(node_blk);
>>> +		struct f2fs_dentry_ptr d;
>>> +
>>> +		make_dentry_ptr(&d, node_blk, inline_dentry, 2);
>>> +
>>> +		DBG(3, "ino[0x%x] has inline dentries!\n", nid);
>>> +		/* recover from inline dentry */
>>> +		dump_folder_contents(sbi, d.bitmap, d.dentry, d.filename, d.max);
>>> +		return -1;
>>> +	}
>>> +
>>>    	c.show_file_map_max_offset = f2fs_max_file_offset(&node_blk->i);
>>>    	addr_per_block = ADDRS_PER_BLOCK(&node_blk->i);
>>>    	/* 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]));
>>> +			node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
>>>    	/* check node blocks in inode */
>>>    	for (i = 0; i < 5; i++) {
>>> @@ -458,17 +497,20 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>>>    			dump_node_blk(sbi, TYPE_DIRECT_NODE,
>>>    					le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
>>>    					addr_per_block,
>>> -					&ofs);
>>> +					&ofs,
>>> +					is_dir);
>>>    		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);
>>> +					&ofs,
>>> +					is_dir);
>>>    		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);
>>> +					&ofs,
>>> +					is_dir);
>>>    		else
>>>    			ASSERT(0);
>>>    	}
>>> @@ -479,8 +521,51 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
>>>    	return 0;
>>>    }
>>> -static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>>> -				struct f2fs_node *node_blk, int force)
>>> +static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>>> +				struct f2fs_node *node_blk, char *path)
>>> +{
>>> +	struct f2fs_inode *inode = &node_blk->i;
>>> +	int ret;
>>> +
>>> +	c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
>>> +	ASSERT(c.dump_fd >= 0);
>>> +
>>> +	/* dump file's data */
>>> +	dump_inode_blk(sbi, ni->ino, node_blk);
>>> +
>>> +	/* adjust file size */
>>> +	ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
>>> +	ASSERT(ret >= 0);
>>> +
>>> +	close(c.dump_fd);
>>> +}
>>> +
>>> +static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
>>> +				struct f2fs_node *node_blk, char *path, int is_root)
>>> +{
>>> +	if (!is_root) {
>>> +#if defined(__MINGW32__)
>>> +		if (mkdir(path) < 0 && errno != EEXIST) {
>>> +			MSG(0, "Failed to create directory %s\n", path);
>>> +			return;
>>> +		}
>>> +#else
>>> +		if (mkdir(path, 0777) < 0 && errno != EEXIST) {
>>> +			MSG(0, "Failed to create directory %s\n", path);
>>> +			return;
>>> +		}
>>> +#endif
>>> +		ASSERT(chdir(path) == 0);
>>> +	}
>>> +	/* dump folder data */
>>> +	dump_inode_blk(sbi, ni->ino, node_blk);
>>> +	if (!is_root)
>>> +		ASSERT(chdir("..") == 0);
>>> +}
>>> +
>>> +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)
>>>    {
>>>    	struct f2fs_inode *inode = &node_blk->i;
>>>    	u32 imode = le16_to_cpu(inode->i_mode);
>>> @@ -489,6 +574,7 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>>>    	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 (is_encrypted) {
>>> @@ -496,11 +582,15 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>>>    		return -1;
>>>    	}
>>> -	if ((!S_ISREG(imode) && !S_ISLNK(imode)) ||
>>> -				namelen == 0 || namelen > F2FS_NAME_LEN) {
>>> -		MSG(force, "Not a regular file or wrong name info\n\n");
>>> +	if ((!S_ISREG(imode) && !S_ISLNK(imode) && !(S_ISDIR(imode) && allow_folder))) {
>>> +		MSG(force, "Not a valid file type\n\n");
>>> +		return -1;
>>> +	}
>>> +	if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
>>> +		MSG(force, "Wrong name info\n\n");
>>>    		return -1;
>>>    	}
>>> +	base_path = base_path ?: "./lost_found";
>>>    	if (force)
>>>    		goto dump;
>>> @@ -508,31 +598,49 @@ static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
>>>    	if (c.show_file_map)
>>>    		return dump_inode_blk(sbi, ni->ino, node_blk);
>>> -	printf("Do you want to dump this file into ./lost_found/? [Y/N] ");
>>> +	printf("Do you want to dump this %s into %s/? [Y/N] ",
>>> +			S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder",
>>> +			base_path);
>>>    	ret = scanf("%s", ans);
>>>    	ASSERT(ret >= 0);
>>>    	if (!strcasecmp(ans, "y")) {
>>>    dump:
>>> -		ret = system("mkdir -p ./lost_found");
>>> -		ASSERT(ret >= 0);
>>> -
>>> -		/* make a file */
>>> -		strncpy(name, (const char *)inode->i_name, namelen);
>>> -		name[namelen] = 0;
>>> -		sprintf(path, "./lost_found/%s", name);
>>> +		if (is_base) {
>>> +			ASSERT(getcwd(path, sizeof(path)) != NULL);
>>> +#if defined(__MINGW32__)
>>> +			ret = mkdir(base_path);
>>> +#else
>>> +			ret = mkdir(base_path, 0777);
>>> +#endif
>>> -		c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
>>> -		ASSERT(c.dump_fd >= 0);
>>> +			ASSERT(ret == 0 || errno == EEXIST);
>>> +			ASSERT(chdir(base_path) == 0);
>>> +		}
>>> -		/* dump file's data */
>>> -		dump_inode_blk(sbi, ni->ino, node_blk);
>>> +		/* make a file */
>>> +		if (!is_root) {
>>> +			strncpy(name, (const char *)inode->i_name, namelen);
>>> +			name[namelen] = 0;
>>> +		}
>>> -		/* adjust file size */
>>> -		ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
>>> -		ASSERT(ret >= 0);
>>> +		if (S_ISREG(imode) || S_ISLNK(imode)) {
>>> +			dump_file(sbi, ni, node_blk, name);
>>> +		} else {
>>> +			dump_folder(sbi, ni, node_blk, name, is_root);
>>> +		}
>>> -		close(c.dump_fd);
>>> +#if !defined(__MINGW32__)
>>> +		/* fix up mode/owner */
>>> +		if (c.preserve_perms) {
>>> +			if (is_root)
>>> +				strncpy(name, ".", 2);
>>> +			ASSERT(chmod(name, imode) == 0);
>>> +			ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
>>> +		}
>>> +#endif
>>> +		if (is_base)
>>> +			ASSERT(chdir(path) == 0);
>>>    	}
>>>    	return 0;
>>>    }
>>> @@ -582,7 +690,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)
>>> +int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
>>>    {
>>>    	struct node_info ni;
>>>    	struct f2fs_node *node_blk;
>>> @@ -617,7 +725,7 @@ int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force)
>>>    			print_node_info(sbi, node_blk, force);
>>>    		if (ni.ino == ni.nid)
>>> -			ret = dump_file(sbi, &ni, node_blk, force);
>>> +			ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
>>>    	} else {
>>>    		print_node_info(sbi, node_blk, force);
>>>    		MSG(force, "Invalid (i)node block\n\n");
>>> diff --git a/fsck/fsck.c b/fsck/fsck.c
>>> index 5d345d0..7400dcf 100644
>>> --- a/fsck/fsck.c
>>> +++ b/fsck/fsck.c
>>> @@ -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))
>>> +		if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
>>>    			printf("\33[2K\r");
>>>    	} else {
>>>    		for (i = 1; i < depth; i++)
>>> @@ -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);
>>> +					dump_node(sbi, i, 1, NULL, 1, 0);
>>>    			}
>>>    		}
>>>    	}
>>> diff --git a/fsck/fsck.h b/fsck/fsck.h
>>> index f5282e2..6cac926 100644
>>> --- a/fsck/fsck.h
>>> +++ b/fsck/fsck.h
>>> @@ -270,12 +270,14 @@ struct dump_option {
>>>    	int end_ssa;
>>>    	int32_t blk_addr;
>>>    	nid_t scan_nid;
>>> +	int use_root_nid;
>>> +	char *base_path;
>>>    };
>>>    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);
>>> +extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
>>>    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);
>>> diff --git a/fsck/main.c b/fsck/main.c
>>> index c4d0956..6edc902 100644
>>> --- a/fsck/main.c
>>> +++ b/fsck/main.c
>>> @@ -34,7 +34,7 @@ struct f2fs_fsck gfsck;
>>>    INIT_FEATURE_TABLE;
>>> -#ifdef WITH_SLOAD
>>> +#if defined(WITH_SLOAD) || defined(WITH_DUMP)
>>>    static char *absolute_path(const char *file)
>>>    {
>>>    	char *ret;
>>> @@ -384,7 +384,7 @@ void f2fs_parse_options(int argc, char *argv[])
>>>    		}
>>>    	} else if (!strcmp("dump.f2fs", prog)) {
>>>    #ifdef WITH_DUMP
>>> -		const char *option_string = "d:i:I:n:Ms:Sa:b:V";
>>> +		const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
>>>    		static struct dump_option dump_opt = {
>>>    			.nid = 0,	/* default root ino */
>>>    			.start_nat = -1,
>>> @@ -395,6 +395,8 @@ void f2fs_parse_options(int argc, char *argv[])
>>>    			.end_ssa = -1,
>>>    			.blk_addr = -1,
>>>    			.scan_nid = 0,
>>> +			.use_root_nid = 0,
>>> +			.base_path = NULL,
>>>    		};
>>>    		c.func = DUMP;
>>> @@ -456,6 +458,24 @@ void f2fs_parse_options(int argc, char *argv[])
>>>    					ret = sscanf(optarg, "%x",
>>>    							&dump_opt.blk_addr);
>>>    				break;
>>> +			case 'y':
>>> +			case 'f':
>>> +				c.force = 1;
>>> +				break;
>>> +			case 'r':
>>> +				dump_opt.use_root_nid = 1;
>>> +				break;
>>> +			case 'o':
>>> +				dump_opt.base_path = absolute_path(optarg);
>>> +				break;
>>> +			case 'P':
>>> +#if defined(__MINGW32__)
>>> +				MSG(0, "-P not supported for Windows\n");
>>> +				err = EWRONG_OPT;
>>> +#else
>>> +				c.preserve_perms = 1;
>>> +#endif
>>> +				break;
>>>    			case 'V':
>>>    				show_version(prog);
>>>    				exit(0);
>>> @@ -914,6 +934,9 @@ static void do_dump(struct f2fs_sb_info *sbi)
>>>    	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
>>>    	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
>>> +	if (opt->use_root_nid)
>>> +		opt->nid = sbi->root_ino_num;
>>> +
>>>    	if (opt->end_nat == -1)
>>>    		opt->end_nat = NM_I(sbi)->max_nid;
>>>    	if (opt->end_sit == -1)
>>> @@ -929,7 +952,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, 0);
>>> +		dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
>>>    	if (opt->scan_nid)
>>>    		dump_node_scan_disk(sbi, opt->scan_nid);
>>> diff --git a/man/dump.f2fs.8 b/man/dump.f2fs.8
>>> index 94bf5f3..60d6783 100644
>>> --- a/man/dump.f2fs.8
>>> +++ b/man/dump.f2fs.8
>>> @@ -44,7 +44,8 @@ is used to retrieve f2fs metadata (usually in a disk partition).
>>>    \fIdevice\fP is the special file corresponding to the device (e.g.
>>>    \fI/dev/sdXX\fP).
>>> -Currently, it can retrieve 1) a file given its inode number, 2) NAT
>>> +Currently, it can retrieve 1) a file or folder given its inode number
>>> +(folders are dumped recursively), 2) NAT
>>>    entries into a file, 3) SIT entries into a file, 4) SSA entries into
>>>    a file, 5) reverse information from the given block address.
>>>    .PP
>>> @@ -56,6 +57,20 @@ is 0 on success and -1 on failure.
>>>    .BI \-i " inode number"
>>>    Specify an inode number to dump out.
>>>    .TP
>>> +.BI \-r
>>> +Dump out from the root inode.
>>> +.TP
>>> +.BI \-f
>>> +Do not prompt before dumping
>>> +.TP
>>> +.BI \-y
>>> +Alias for \-f
>>> +.TP
>>> +.BI \-o " path"
>>> +Dump inodes to the given path
>>> +.BI \-P
>>> +Preserve mode/owner/group for dumped inode
>>> +.TP
>>
>> It needs to update dump_usage() as well.
>>
>> Seems f2fs mailing list is out-of-response previously, so I resend it.
> 
> Chao, can I ask for your help to write a patch for this?

Jaegeuk, no problem.

Thanks,

> 
> Thanks,
> 
>>
>> Thanks,
>>
>>>    .BI \-I " inode number"
>>>    Specify an inode number and scan full disk to dump out, include history inode block
>>>    .TP
>>>
>>> base-commit: 5da4e5241503b385e4a7e75b1b2bb3367b38be96
diff mbox series

Patch

diff --git a/fsck/dump.c b/fsck/dump.c
index b2e990b..fa68456 100644
--- a/fsck/dump.c
+++ b/fsck/dump.c
@@ -247,7 +247,26 @@  out:
 		printf("\n");
 }
 
-static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
+static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
+				struct f2fs_dir_entry *dentry,
+				__u8 (*filenames)[F2FS_SLOT_LEN], int max)
+{
+	int i;
+	int name_len;
+
+	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 == 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);
+	}
+}
+
+static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, bool is_folder)
 {
 	char buf[F2FS_BLKSIZE];
 
@@ -288,12 +307,19 @@  static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr)
 		ASSERT(ret >= 0);
 	}
 
-	/* write blkaddr */
-	dev_write_dump(buf, offset, F2FS_BLKSIZE);
+	if (is_folder) {
+		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);
+	} 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)
+				u32 nid, u32 addr_per_block, u64 *ofs, int is_dir)
 {
 	struct node_info ni;
 	struct f2fs_node *node_blk;
@@ -330,20 +356,20 @@  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]));
+					le32_to_cpu(node_blk->dn.addr[i]), is_dir);
 			(*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);
+					ofs, is_dir);
 			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);
+					ofs, is_dir);
 			break;
 		}
 	}
@@ -435,8 +461,9 @@  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));
 
-	if((node_blk->i.i_inline & F2FS_INLINE_DATA)) {
+	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(((unsigned char *)node_blk) + INLINE_DATA_OFFSET,
@@ -444,13 +471,25 @@  static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 		return -1;
 	}
 
+	if ((node_blk->i.i_inline & F2FS_INLINE_DENTRY)) {
+		void *inline_dentry = inline_data_addr(node_blk);
+		struct f2fs_dentry_ptr d;
+
+		make_dentry_ptr(&d, node_blk, inline_dentry, 2);
+
+		DBG(3, "ino[0x%x] has inline dentries!\n", nid);
+		/* recover from inline dentry */
+		dump_folder_contents(sbi, d.bitmap, d.dentry, d.filename, d.max);
+		return -1;
+	}
+
 	c.show_file_map_max_offset = f2fs_max_file_offset(&node_blk->i);
 	addr_per_block = ADDRS_PER_BLOCK(&node_blk->i);
 
 	/* 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]));
+			node_blk->i.i_addr[get_extra_isize(node_blk) + i]), is_dir);
 
 	/* check node blocks in inode */
 	for (i = 0; i < 5; i++) {
@@ -458,17 +497,20 @@  static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 			dump_node_blk(sbi, TYPE_DIRECT_NODE,
 					le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
 					addr_per_block,
-					&ofs);
+					&ofs,
+					is_dir);
 		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);
+					&ofs,
+					is_dir);
 		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);
+					&ofs,
+					is_dir);
 		else
 			ASSERT(0);
 	}
@@ -479,8 +521,51 @@  static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
 	return 0;
 }
 
-static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
-				struct f2fs_node *node_blk, int force)
+static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
+				struct f2fs_node *node_blk, char *path)
+{
+	struct f2fs_inode *inode = &node_blk->i;
+	int ret;
+
+	c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
+	ASSERT(c.dump_fd >= 0);
+
+	/* dump file's data */
+	dump_inode_blk(sbi, ni->ino, node_blk);
+
+	/* adjust file size */
+	ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
+	ASSERT(ret >= 0);
+
+	close(c.dump_fd);
+}
+
+static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
+				struct f2fs_node *node_blk, char *path, int is_root)
+{
+	if (!is_root) {
+#if defined(__MINGW32__)
+		if (mkdir(path) < 0 && errno != EEXIST) {
+			MSG(0, "Failed to create directory %s\n", path);
+			return;
+		}
+#else
+		if (mkdir(path, 0777) < 0 && errno != EEXIST) {
+			MSG(0, "Failed to create directory %s\n", path);
+			return;
+		}
+#endif
+		ASSERT(chdir(path) == 0);
+	}
+	/* dump folder data */
+	dump_inode_blk(sbi, ni->ino, node_blk);
+	if (!is_root)
+		ASSERT(chdir("..") == 0);
+}
+
+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)
 {
 	struct f2fs_inode *inode = &node_blk->i;
 	u32 imode = le16_to_cpu(inode->i_mode);
@@ -489,6 +574,7 @@  static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
 	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 (is_encrypted) {
@@ -496,11 +582,15 @@  static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
 		return -1;
 	}
 
-	if ((!S_ISREG(imode) && !S_ISLNK(imode)) ||
-				namelen == 0 || namelen > F2FS_NAME_LEN) {
-		MSG(force, "Not a regular file or wrong name info\n\n");
+	if ((!S_ISREG(imode) && !S_ISLNK(imode) && !(S_ISDIR(imode) && allow_folder))) {
+		MSG(force, "Not a valid file type\n\n");
+		return -1;
+	}
+	if (!is_root && (namelen == 0 || namelen > F2FS_NAME_LEN)) {
+		MSG(force, "Wrong name info\n\n");
 		return -1;
 	}
+	base_path = base_path ?: "./lost_found";
 	if (force)
 		goto dump;
 
@@ -508,31 +598,49 @@  static int dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
 	if (c.show_file_map)
 		return dump_inode_blk(sbi, ni->ino, node_blk);
 
-	printf("Do you want to dump this file into ./lost_found/? [Y/N] ");
+	printf("Do you want to dump this %s into %s/? [Y/N] ",
+			S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder",
+			base_path);
 	ret = scanf("%s", ans);
 	ASSERT(ret >= 0);
 
 	if (!strcasecmp(ans, "y")) {
 dump:
-		ret = system("mkdir -p ./lost_found");
-		ASSERT(ret >= 0);
-
-		/* make a file */
-		strncpy(name, (const char *)inode->i_name, namelen);
-		name[namelen] = 0;
-		sprintf(path, "./lost_found/%s", name);
+		if (is_base) {
+			ASSERT(getcwd(path, sizeof(path)) != NULL);
+#if defined(__MINGW32__)
+			ret = mkdir(base_path);
+#else
+			ret = mkdir(base_path, 0777);
+#endif
 
-		c.dump_fd = open(path, O_TRUNC|O_CREAT|O_RDWR, 0666);
-		ASSERT(c.dump_fd >= 0);
+			ASSERT(ret == 0 || errno == EEXIST);
+			ASSERT(chdir(base_path) == 0);
+		}
 
-		/* dump file's data */
-		dump_inode_blk(sbi, ni->ino, node_blk);
+		/* make a file */
+		if (!is_root) {
+			strncpy(name, (const char *)inode->i_name, namelen);
+			name[namelen] = 0;
+		}
 
-		/* adjust file size */
-		ret = ftruncate(c.dump_fd, le32_to_cpu(inode->i_size));
-		ASSERT(ret >= 0);
+		if (S_ISREG(imode) || S_ISLNK(imode)) {
+			dump_file(sbi, ni, node_blk, name);
+		} else {
+			dump_folder(sbi, ni, node_blk, name, is_root);
+		}
 
-		close(c.dump_fd);
+#if !defined(__MINGW32__)
+		/* fix up mode/owner */
+		if (c.preserve_perms) {
+			if (is_root)
+				strncpy(name, ".", 2);
+			ASSERT(chmod(name, imode) == 0);
+			ASSERT(chown(name, inode->i_uid, inode->i_gid) == 0);
+		}
+#endif
+		if (is_base)
+			ASSERT(chdir(path) == 0);
 	}
 	return 0;
 }
@@ -582,7 +690,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)
+int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force, char *base_path, int base, int allow_folder)
 {
 	struct node_info ni;
 	struct f2fs_node *node_blk;
@@ -617,7 +725,7 @@  int dump_node(struct f2fs_sb_info *sbi, nid_t nid, int force)
 			print_node_info(sbi, node_blk, force);
 
 		if (ni.ino == ni.nid)
-			ret = dump_file(sbi, &ni, node_blk, force);
+			ret = dump_filesystem(sbi, &ni, node_blk, force, base_path, base, allow_folder);
 	} else {
 		print_node_info(sbi, node_blk, force);
 		MSG(force, "Invalid (i)node block\n\n");
diff --git a/fsck/fsck.c b/fsck/fsck.c
index 5d345d0..7400dcf 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -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))
+		if (dump_node(sbi, le32_to_cpu(dentry[idx].ino), 0, NULL, 0, 0))
 			printf("\33[2K\r");
 	} else {
 		for (i = 1; i < depth; i++)
@@ -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);
+					dump_node(sbi, i, 1, NULL, 1, 0);
 			}
 		}
 	}
diff --git a/fsck/fsck.h b/fsck/fsck.h
index f5282e2..6cac926 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -270,12 +270,14 @@  struct dump_option {
 	int end_ssa;
 	int32_t blk_addr;
 	nid_t scan_nid;
+	int use_root_nid;
+	char *base_path;
 };
 
 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);
+extern int dump_node(struct f2fs_sb_info *, nid_t, int, char *, int, int);
 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);
diff --git a/fsck/main.c b/fsck/main.c
index c4d0956..6edc902 100644
--- a/fsck/main.c
+++ b/fsck/main.c
@@ -34,7 +34,7 @@  struct f2fs_fsck gfsck;
 
 INIT_FEATURE_TABLE;
 
-#ifdef WITH_SLOAD
+#if defined(WITH_SLOAD) || defined(WITH_DUMP)
 static char *absolute_path(const char *file)
 {
 	char *ret;
@@ -384,7 +384,7 @@  void f2fs_parse_options(int argc, char *argv[])
 		}
 	} else if (!strcmp("dump.f2fs", prog)) {
 #ifdef WITH_DUMP
-		const char *option_string = "d:i:I:n:Ms:Sa:b:V";
+		const char *option_string = "d:fi:I:n:Mo:Prs:Sa:b:Vy";
 		static struct dump_option dump_opt = {
 			.nid = 0,	/* default root ino */
 			.start_nat = -1,
@@ -395,6 +395,8 @@  void f2fs_parse_options(int argc, char *argv[])
 			.end_ssa = -1,
 			.blk_addr = -1,
 			.scan_nid = 0,
+			.use_root_nid = 0,
+			.base_path = NULL,
 		};
 
 		c.func = DUMP;
@@ -456,6 +458,24 @@  void f2fs_parse_options(int argc, char *argv[])
 					ret = sscanf(optarg, "%x",
 							&dump_opt.blk_addr);
 				break;
+			case 'y':
+			case 'f':
+				c.force = 1;
+				break;
+			case 'r':
+				dump_opt.use_root_nid = 1;
+				break;
+			case 'o':
+				dump_opt.base_path = absolute_path(optarg);
+				break;
+			case 'P':
+#if defined(__MINGW32__)
+				MSG(0, "-P not supported for Windows\n");
+				err = EWRONG_OPT;
+#else
+				c.preserve_perms = 1;
+#endif
+				break;
 			case 'V':
 				show_version(prog);
 				exit(0);
@@ -914,6 +934,9 @@  static void do_dump(struct f2fs_sb_info *sbi)
 	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
 	u32 flag = le32_to_cpu(ckpt->ckpt_flags);
 
+	if (opt->use_root_nid)
+		opt->nid = sbi->root_ino_num;
+
 	if (opt->end_nat == -1)
 		opt->end_nat = NM_I(sbi)->max_nid;
 	if (opt->end_sit == -1)
@@ -929,7 +952,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, 0);
+		dump_node(sbi, opt->nid, c.force, opt->base_path, 1, 1);
 	if (opt->scan_nid)
 		dump_node_scan_disk(sbi, opt->scan_nid);
 
diff --git a/man/dump.f2fs.8 b/man/dump.f2fs.8
index 94bf5f3..60d6783 100644
--- a/man/dump.f2fs.8
+++ b/man/dump.f2fs.8
@@ -44,7 +44,8 @@  is used to retrieve f2fs metadata (usually in a disk partition).
 \fIdevice\fP is the special file corresponding to the device (e.g.
 \fI/dev/sdXX\fP).
 
-Currently, it can retrieve 1) a file given its inode number, 2) NAT
+Currently, it can retrieve 1) a file or folder given its inode number
+(folders are dumped recursively), 2) NAT
 entries into a file, 3) SIT entries into a file, 4) SSA entries into
 a file, 5) reverse information from the given block address.
 .PP
@@ -56,6 +57,20 @@  is 0 on success and -1 on failure.
 .BI \-i " inode number"
 Specify an inode number to dump out.
 .TP
+.BI \-r
+Dump out from the root inode.
+.TP
+.BI \-f
+Do not prompt before dumping
+.TP
+.BI \-y
+Alias for \-f
+.TP
+.BI \-o " path"
+Dump inodes to the given path
+.BI \-P
+Preserve mode/owner/group for dumped inode
+.TP
 .BI \-I " inode number"
 Specify an inode number and scan full disk to dump out, include history inode block
 .TP