dump.f2fs: Dump symlinks as symlinks

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.

Signed-off-by: Daniel Rosenberg <drosen@google.com>
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
This commit is contained in:
Daniel Rosenberg 2024-07-23 14:11:57 -07:00 committed by Jaegeuk Kim
parent 4a5da2ef15
commit a66d49fd26
7 changed files with 125 additions and 36 deletions

View File

@ -253,20 +253,27 @@ static void dump_folder_contents(struct f2fs_sb_info *sbi, u8 *bitmap,
{ {
int i; int i;
int name_len; int name_len;
char name[F2FS_NAME_LEN + 1] = {0};
for (i = 0; i < max; i++) { for (i = 0; i < max; i++) {
if (test_bit_le(i, bitmap) == 0) if (test_bit_le(i, bitmap) == 0)
continue; continue;
name_len = le16_to_cpu(dentry[i].name_len); 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] == '.') if (name_len == 1 && filenames[i][0] == '.')
continue; continue;
if (name_len == 2 && filenames[i][0] == '.' && filenames[i][1] == '.') if (name_len == 2 && filenames[i][0] == '.' && filenames[i][1] == '.')
continue; 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]; char buf[F2FS_BLKSIZE];
@ -307,11 +314,15 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, b
ASSERT(ret >= 0); ASSERT(ret >= 0);
} }
if (is_folder) { if (S_ISDIR(type)) {
struct f2fs_dentry_block *d = (struct f2fs_dentry_block *) buf; struct f2fs_dentry_block *d = (struct f2fs_dentry_block *) buf;
dump_folder_contents(sbi, d->dentry_bitmap, F2FS_DENTRY_BLOCK_DENTRIES(d), dump_folder_contents(sbi, d->dentry_bitmap, F2FS_DENTRY_BLOCK_DENTRIES(d),
F2FS_DENTRY_BLOCK_FILENAMES(d), NR_DENTRY_IN_BLOCK); 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 { } else {
/* write blkaddr */ /* write blkaddr */
dev_write_dump(buf, offset, F2FS_BLKSIZE); dev_write_dump(buf, offset, F2FS_BLKSIZE);
@ -319,7 +330,7 @@ static void dump_data_blk(struct f2fs_sb_info *sbi, __u64 offset, u32 blkaddr, b
} }
static void dump_node_blk(struct f2fs_sb_info *sbi, int ntype, 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 node_info ni;
struct f2fs_node *node_blk; struct f2fs_node *node_blk;
@ -356,20 +367,20 @@ static void dump_node_blk(struct f2fs_sb_info *sbi, int ntype,
switch (ntype) { switch (ntype) {
case TYPE_DIRECT_NODE: case TYPE_DIRECT_NODE:
dump_data_blk(sbi, *ofs * F2FS_BLKSIZE, 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)++; (*ofs)++;
break; break;
case TYPE_INDIRECT_NODE: case TYPE_INDIRECT_NODE:
dump_node_blk(sbi, TYPE_DIRECT_NODE, dump_node_blk(sbi, TYPE_DIRECT_NODE,
le32_to_cpu(node_blk->in.nid[i]), le32_to_cpu(node_blk->in.nid[i]),
addr_per_block, addr_per_block,
ofs, is_dir); ofs, type);
break; break;
case TYPE_DOUBLE_INDIRECT_NODE: case TYPE_DOUBLE_INDIRECT_NODE:
dump_node_blk(sbi, TYPE_INDIRECT_NODE, dump_node_blk(sbi, TYPE_INDIRECT_NODE,
le32_to_cpu(node_blk->in.nid[i]), le32_to_cpu(node_blk->in.nid[i]),
addr_per_block, addr_per_block,
ofs, is_dir); ofs, type);
break; break;
} }
} }
@ -377,7 +388,7 @@ static void dump_node_blk(struct f2fs_sb_info *sbi, int ntype,
} }
#ifdef HAVE_FSETXATTR #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 *xattr;
void *last_base_addr; void *last_base_addr;
@ -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); DBG(1, "fd %d xattr_name %s\n", c.dump_fd, xattr_name);
#if defined(__linux__) #if defined(__linux__)
if (is_dir) { if (S_ISDIR(type)) {
ret = setxattr(".", xattr_name, value, ret = setxattr(".", xattr_name, value,
le16_to_cpu(ent->e_value_size), 0); 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 { } else {
ret = fsetxattr(c.dump_fd, xattr_name, value, ret = fsetxattr(c.dump_fd, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0); le16_to_cpu(ent->e_value_size), 0);
} }
#elif defined(__APPLE__) #elif defined(__APPLE__)
if (is_dir) { if (S_ISDIR(type)) {
ret = setxattr(".", xattr_name, value, ret = setxattr(".", xattr_name, value,
le16_to_cpu(ent->e_value_size), 0, le16_to_cpu(ent->e_value_size), 0,
XATTR_CREATE); 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 { } else {
ret = fsetxattr(c.dump_fd, xattr_name, value, ret = fsetxattr(c.dump_fd, xattr_name, value,
le16_to_cpu(ent->e_value_size), 0, le16_to_cpu(ent->e_value_size), 0,
@ -473,14 +491,21 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
u32 i = 0; u32 i = 0;
u64 ofs = 0; u64 ofs = 0;
u32 addr_per_block; 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; int ret = 0;
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); DBG(3, "ino[0x%x] has inline data!\n", nid);
/* recover from inline data */ /* 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)); 0, MAX_INLINE_DATA(node_blk));
}
ret = -1; ret = -1;
goto dump_xattr; goto dump_xattr;
} }
@ -504,7 +529,7 @@ static int dump_inode_blk(struct f2fs_sb_info *sbi, u32 nid,
/* check data blocks in inode */ /* check data blocks in inode */
for (i = 0; i < ADDRS_PER_INODE(&node_blk->i); i++, ofs++) for (i = 0; i < ADDRS_PER_INODE(&node_blk->i); i++, ofs++)
dump_data_blk(sbi, ofs * F2FS_BLKSIZE, le32_to_cpu( 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 */ /* check node blocks in inode */
for (i = 0; i < 5; i++) { for (i = 0; i < 5; i++) {
@ -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)), le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block, addr_per_block,
&ofs, &ofs,
is_dir); type);
else if (i == 2 || i == 3) else if (i == 2 || i == 3)
dump_node_blk(sbi, TYPE_INDIRECT_NODE, dump_node_blk(sbi, TYPE_INDIRECT_NODE,
le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)), le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block, addr_per_block,
&ofs, &ofs,
is_dir); type);
else if (i == 4) else if (i == 4)
dump_node_blk(sbi, TYPE_DOUBLE_INDIRECT_NODE, dump_node_blk(sbi, TYPE_DOUBLE_INDIRECT_NODE,
le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)), le32_to_cpu(F2FS_INODE_I_NID(&node_blk->i, i)),
addr_per_block, addr_per_block,
&ofs, &ofs,
is_dir); type);
else else
ASSERT(0); ASSERT(0);
} }
/* last block in extent cache */ /* last block in extent cache */
print_extent(true); print_extent(true);
dump_xattr: dump_xattr:
dump_xattr(sbi, node_blk, is_dir); dump_xattr(sbi, node_blk, type);
return ret; return ret;
} }
@ -555,6 +580,23 @@ static void dump_file(struct f2fs_sb_info *sbi, struct node_info *ni,
close(c.dump_fd); 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, name);
#else
struct f2fs_inode *inode = &node_blk->i;
int len = le64_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, static void dump_folder(struct f2fs_sb_info *sbi, struct node_info *ni,
struct f2fs_node *node_blk, char *path, int is_root) struct f2fs_node *node_blk, char *path, int is_root)
{ {
@ -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, static int dump_filesystem(struct f2fs_sb_info *sbi, struct node_info *ni,
struct f2fs_node *node_blk, int force, char *base_path, 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; struct f2fs_inode *inode = &node_blk->i;
u32 imode = le16_to_cpu(inode->i_mode); u32 imode = le16_to_cpu(inode->i_mode);
u32 namelen = le32_to_cpu(inode->i_namelen); u32 ilinks = le32_to_cpu(inode->i_links);
char name[F2FS_NAME_LEN + 1] = {0}; u32 i_namelen = le32_to_cpu(inode->i_namelen);
char i_name[F2FS_NAME_LEN + 1] = {0};
char *name = NULL;
char path[1024] = {0}; char path[1024] = {0};
char ans[255] = {0}; char ans[255] = {0};
int is_encrypted = file_is_encrypt(inode); int is_encrypted = file_is_encrypt(inode);
int is_root = sbi->root_ino_num == ni->nid; int is_root = sbi->root_ino_num == ni->nid;
int ret; int ret;
if (!S_ISDIR(imode) && ilinks != 1) {
MSG(force, "Warning: Hard link detected. Dumped files may be duplicated\n");
}
if (is_encrypted) { if (is_encrypted) {
MSG(force, "File is encrypted\n"); MSG(force, "File is encrypted\n");
return -1; return -1;
@ -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"); MSG(force, "Not a valid file type\n\n");
return -1; 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"); MSG(force, "Wrong name info\n\n");
return -1; return -1;
} }
@ -614,7 +662,7 @@ static int dump_filesystem(struct f2fs_sb_info *sbi, struct node_info *ni,
return dump_inode_blk(sbi, ni->ino, node_blk); return dump_inode_blk(sbi, ni->ino, node_blk);
printf("Do you want to dump this %s into %s/? [Y/N] ", printf("Do you want to dump this %s into %s/? [Y/N] ",
S_ISREG(imode) || S_ISLNK(imode) ? "file" : "folder", S_ISDIR(imode) ? "folder" : "file",
base_path); base_path);
ret = scanf("%s", ans); ret = scanf("%s", ans);
ASSERT(ret >= 0); ASSERT(ret >= 0);
@ -635,23 +683,34 @@ dump:
/* make a file */ /* make a file */
if (!is_root) { if (!is_root) {
strncpy(name, (const char *)inode->i_name, namelen); /* The i_name name may be out of date. Prefer dirent_name */
name[namelen] = 0; 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); dump_file(sbi, ni, node_blk, name);
} else { } else if (S_ISDIR(imode)) {
dump_folder(sbi, ni, node_blk, name, is_root); dump_folder(sbi, ni, node_blk, name, is_root);
} else {
dump_link(sbi, ni, node_blk, name);
} }
#if !defined(__MINGW32__) #if !defined(__MINGW32__)
/* fix up mode/owner */ /* fix up mode/owner */
if (c.preserve_perms) { if (c.preserve_perms) {
if (is_root) if (is_root) {
name = i_name;
strncpy(name, ".", 2); 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 #endif
if (is_base) if (is_base)
@ -705,7 +764,7 @@ void dump_node_scan_disk(struct f2fs_sb_info *sbi, nid_t nid)
free(node_blk); 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 node_info ni;
struct f2fs_node *node_blk; struct f2fs_node *node_blk;
@ -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); print_node_info(sbi, node_blk, force);
if (ni.ino == ni.nid) 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 { } else {
print_node_info(sbi, node_blk, force); print_node_info(sbi, node_blk, force);
MSG(force, "Invalid (i)node block\n\n"); MSG(force, "Invalid (i)node block\n\n");

View File

@ -1651,7 +1651,7 @@ static void print_dentry(struct f2fs_sb_info *sbi, __u8 *name,
d = d->next; d = d->next;
} }
printf("/%s", new); 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"); printf("\33[2K\r");
} else { } else {
for (i = 1; i < depth; i++) for (i = 1; i < depth; i++)
@ -3632,7 +3632,7 @@ int fsck_verify(struct f2fs_sb_info *sbi)
if (!strcasecmp(ans, "y")) { if (!strcasecmp(ans, "y")) {
for (i = 0; i < fsck->nr_nat_entries; i++) { for (i = 0; i < fsck->nr_nat_entries; i++) {
if (f2fs_test_bit(i, fsck->nat_area_bitmap)) 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);
} }
} }
} }

View File

@ -277,7 +277,7 @@ struct dump_option {
extern void nat_dump(struct f2fs_sb_info *, nid_t, nid_t); 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 sit_dump(struct f2fs_sb_info *, unsigned int, unsigned int);
extern void ssa_dump(struct f2fs_sb_info *, int, 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 int dump_info_from_blkaddr(struct f2fs_sb_info *, u32);
extern unsigned int start_bidx_of_node(unsigned int, struct f2fs_node *); 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); extern void dump_node_scan_disk(struct f2fs_sb_info *sbi, nid_t nid);

View File

@ -102,6 +102,7 @@ void dump_usage()
MSG(0, " -y alias for -f\n"); MSG(0, " -y alias for -f\n");
MSG(0, " -o dump inodes to the given path\n"); MSG(0, " -o dump inodes to the given path\n");
MSG(0, " -P preserve mode/owner/group for dumped inode\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"); MSG(0, " -V print the version number and exit\n");
exit(1); exit(1);
@ -389,7 +390,7 @@ void f2fs_parse_options(int argc, char *argv[])
} }
} else if (!strcmp("dump.f2fs", prog)) { } else if (!strcmp("dump.f2fs", prog)) {
#ifdef WITH_DUMP #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 = { static struct dump_option dump_opt = {
.nid = 0, /* default root ino */ .nid = 0, /* default root ino */
.start_nat = -1, .start_nat = -1,
@ -479,6 +480,14 @@ void f2fs_parse_options(int argc, char *argv[])
err = EWRONG_OPT; err = EWRONG_OPT;
#else #else
c.preserve_perms = 1; 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;
#endif #endif
break; break;
case 'V': case 'V':
@ -957,7 +966,7 @@ static void do_dump(struct f2fs_sb_info *sbi)
if (opt->blk_addr != -1) if (opt->blk_addr != -1)
dump_info_from_blkaddr(sbi, opt->blk_addr); dump_info_from_blkaddr(sbi, opt->blk_addr);
if (opt->nid) 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) if (opt->scan_nid)
dump_node_scan_disk(sbi, opt->scan_nid); dump_node_scan_disk(sbi, opt->scan_nid);

View File

@ -1478,6 +1478,8 @@ struct f2fs_configuration {
uint16_t s_encoding_flags; uint16_t s_encoding_flags;
int32_t kd; int32_t kd;
int32_t dump_fd; int32_t dump_fd;
char *dump_symlink;
int dump_sym_target_len;
struct device_info devices[MAX_DEVICES]; struct device_info devices[MAX_DEVICES];
int ndevs; int ndevs;
char *extension_list[2]; char *extension_list[2];
@ -1540,7 +1542,10 @@ struct f2fs_configuration {
struct selinux_opt seopt_file[8]; struct selinux_opt seopt_file[8];
int nr_opt; int nr_opt;
#endif #endif
/* dump parameters */
int preserve_perms; int preserve_perms;
int preserve_symlinks;
/* resize parameters */ /* resize parameters */
int safe_resize; int safe_resize;
@ -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(void *, __u64, size_t);
extern int dev_write_block(void *, __u64); extern int dev_write_block(void *, __u64);
extern int dev_write_dump(void *, __u64, size_t); 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(). */ /* All bytes in the buffer must be 0 use dev_fill(). */
extern int dev_fill(void *, __u64, size_t); extern int dev_fill(void *, __u64, size_t);
extern int dev_fill_block(void *, __u64); extern int dev_fill_block(void *, __u64);

View File

@ -598,6 +598,16 @@ int dev_write_dump(void *buf, __u64 offset, size_t len)
return 0; return 0;
} }
#if !defined(__MINGW32__)
int dev_write_symlink(char *buf, size_t len)
{
buf[len] = 0;
if (symlink(buf, c.dump_symlink))
return -1;
return 0;
}
#endif
int dev_fill(void *buf, __u64 offset, size_t len) int dev_fill(void *buf, __u64 offset, size_t len)
{ {
int fd; int fd;

View File

@ -71,6 +71,9 @@ Dump inodes to the given path
.BI \-P .BI \-P
Preserve mode/owner/group for dumped inode Preserve mode/owner/group for dumped inode
.TP .TP
.BI \-L
Preserves symlinks. Otherwise symlinks are dumped as regular files.
.TP
.BI \-I " inode number" .BI \-I " inode number"
Specify an inode number and scan full disk to dump out, include history inode block Specify an inode number and scan full disk to dump out, include history inode block
.TP .TP