Commit 122a7b3a authored by David Woodhouse's avatar David Woodhouse Committed by Luis Henriques

Fix directory hardlinks from deleted directories

commit be629c62 upstream.

When a directory is deleted, we don't take too much care about killing off
all the dirents that belong to it — on the basis that on remount, the scan
will conclude that the directory is dead anyway.

This doesn't work though, when the deleted directory contained a child
directory which was moved *out*. In the early stages of the fs build
we can then end up with an apparent hard link, with the child directory
appearing both in its true location, and as a child of the original
directory which are this stage of the mount process we don't *yet* know
is defunct.

To resolve this, take out the early special-casing of the "directories
shall not have hard links" rule in jffs2_build_inode_pass1(), and let the
normal nlink processing happen for directories as well as other inodes.

Then later in the build process we can set ic->pino_nlink to the parent
inode#, as is required for directories during normal operaton, instead
of the nlink. And complain only *then* about hard links which are still
in evidence even after killing off all the unreachable paths.
Reported-by: default avatarLiu Song <liu.song11@zte.com.cn>
Signed-off-by: default avatarDavid Woodhouse <David.Woodhouse@intel.com>
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent d6e29517
...@@ -49,7 +49,8 @@ next_inode(int *i, struct jffs2_inode_cache *ic, struct jffs2_sb_info *c) ...@@ -49,7 +49,8 @@ next_inode(int *i, struct jffs2_inode_cache *ic, struct jffs2_sb_info *c)
static void jffs2_build_inode_pass1(struct jffs2_sb_info *c, static void jffs2_build_inode_pass1(struct jffs2_sb_info *c,
struct jffs2_inode_cache *ic) struct jffs2_inode_cache *ic,
int *dir_hardlinks)
{ {
struct jffs2_full_dirent *fd; struct jffs2_full_dirent *fd;
...@@ -68,19 +69,21 @@ static void jffs2_build_inode_pass1(struct jffs2_sb_info *c, ...@@ -68,19 +69,21 @@ static void jffs2_build_inode_pass1(struct jffs2_sb_info *c,
dbg_fsbuild("child \"%s\" (ino #%u) of dir ino #%u doesn't exist!\n", dbg_fsbuild("child \"%s\" (ino #%u) of dir ino #%u doesn't exist!\n",
fd->name, fd->ino, ic->ino); fd->name, fd->ino, ic->ino);
jffs2_mark_node_obsolete(c, fd->raw); jffs2_mark_node_obsolete(c, fd->raw);
/* Clear the ic/raw union so it doesn't cause problems later. */
fd->ic = NULL;
continue; continue;
} }
/* From this point, fd->raw is no longer used so we can set fd->ic */
fd->ic = child_ic;
child_ic->pino_nlink++;
/* If we appear (at this stage) to have hard-linked directories,
* set a flag to trigger a scan later */
if (fd->type == DT_DIR) { if (fd->type == DT_DIR) {
if (child_ic->pino_nlink) { child_ic->flags |= INO_FLAGS_IS_DIR;
JFFS2_ERROR("child dir \"%s\" (ino #%u) of dir ino #%u appears to be a hard link\n", if (child_ic->pino_nlink > 1)
fd->name, fd->ino, ic->ino); *dir_hardlinks = 1;
/* TODO: What do we do about it? */
} else {
child_ic->pino_nlink = ic->ino;
} }
} else
child_ic->pino_nlink++;
dbg_fsbuild("increased nlink for child \"%s\" (ino #%u)\n", fd->name, fd->ino); dbg_fsbuild("increased nlink for child \"%s\" (ino #%u)\n", fd->name, fd->ino);
/* Can't free scan_dents so far. We might need them in pass 2 */ /* Can't free scan_dents so far. We might need them in pass 2 */
...@@ -94,8 +97,7 @@ static void jffs2_build_inode_pass1(struct jffs2_sb_info *c, ...@@ -94,8 +97,7 @@ static void jffs2_build_inode_pass1(struct jffs2_sb_info *c,
*/ */
static int jffs2_build_filesystem(struct jffs2_sb_info *c) static int jffs2_build_filesystem(struct jffs2_sb_info *c)
{ {
int ret; int ret, i, dir_hardlinks = 0;
int i;
struct jffs2_inode_cache *ic; struct jffs2_inode_cache *ic;
struct jffs2_full_dirent *fd; struct jffs2_full_dirent *fd;
struct jffs2_full_dirent *dead_fds = NULL; struct jffs2_full_dirent *dead_fds = NULL;
...@@ -119,7 +121,7 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c) ...@@ -119,7 +121,7 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c)
/* Now scan the directory tree, increasing nlink according to every dirent found. */ /* Now scan the directory tree, increasing nlink according to every dirent found. */
for_each_inode(i, c, ic) { for_each_inode(i, c, ic) {
if (ic->scan_dents) { if (ic->scan_dents) {
jffs2_build_inode_pass1(c, ic); jffs2_build_inode_pass1(c, ic, &dir_hardlinks);
cond_resched(); cond_resched();
} }
} }
...@@ -155,6 +157,20 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c) ...@@ -155,6 +157,20 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c)
} }
dbg_fsbuild("pass 2a complete\n"); dbg_fsbuild("pass 2a complete\n");
if (dir_hardlinks) {
/* If we detected directory hardlinks earlier, *hopefully*
* they are gone now because some of the links were from
* dead directories which still had some old dirents lying
* around and not yet garbage-collected, but which have
* been discarded above. So clear the pino_nlink field
* in each directory, so that the final scan below can
* print appropriate warnings. */
for_each_inode(i, c, ic) {
if (ic->flags & INO_FLAGS_IS_DIR)
ic->pino_nlink = 0;
}
}
dbg_fsbuild("freeing temporary data structures\n"); dbg_fsbuild("freeing temporary data structures\n");
/* Finally, we can scan again and free the dirent structs */ /* Finally, we can scan again and free the dirent structs */
...@@ -162,6 +178,33 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c) ...@@ -162,6 +178,33 @@ static int jffs2_build_filesystem(struct jffs2_sb_info *c)
while(ic->scan_dents) { while(ic->scan_dents) {
fd = ic->scan_dents; fd = ic->scan_dents;
ic->scan_dents = fd->next; ic->scan_dents = fd->next;
/* We do use the pino_nlink field to count nlink of
* directories during fs build, so set it to the
* parent ino# now. Now that there's hopefully only
* one. */
if (fd->type == DT_DIR) {
if (!fd->ic) {
/* We'll have complained about it and marked the coresponding
raw node obsolete already. Just skip it. */
continue;
}
/* We *have* to have set this in jffs2_build_inode_pass1() */
BUG_ON(!(fd->ic->flags & INO_FLAGS_IS_DIR));
/* We clear ic->pino_nlink ∀ directories' ic *only* if dir_hardlinks
* is set. Otherwise, we know this should never trigger anyway, so
* we don't do the check. And ic->pino_nlink still contains the nlink
* value (which is 1). */
if (dir_hardlinks && fd->ic->pino_nlink) {
JFFS2_ERROR("child dir \"%s\" (ino #%u) of dir ino #%u is also hard linked from dir ino #%u\n",
fd->name, fd->ino, ic->ino, fd->ic->pino_nlink);
/* Should we unlink it from its previous parent? */
}
/* For directories, ic->pino_nlink holds that parent inode # */
fd->ic->pino_nlink = ic->ino;
}
jffs2_free_full_dirent(fd); jffs2_free_full_dirent(fd);
} }
ic->scan_dents = NULL; ic->scan_dents = NULL;
...@@ -240,10 +283,6 @@ static void jffs2_build_remove_unlinked_inode(struct jffs2_sb_info *c, ...@@ -240,10 +283,6 @@ static void jffs2_build_remove_unlinked_inode(struct jffs2_sb_info *c,
/* Reduce nlink of the child. If it's now zero, stick it on the /* Reduce nlink of the child. If it's now zero, stick it on the
dead_fds list to be cleaned up later. Else just free the fd */ dead_fds list to be cleaned up later. Else just free the fd */
if (fd->type == DT_DIR)
child_ic->pino_nlink = 0;
else
child_ic->pino_nlink--; child_ic->pino_nlink--;
if (!child_ic->pino_nlink) { if (!child_ic->pino_nlink) {
......
...@@ -194,6 +194,7 @@ struct jffs2_inode_cache { ...@@ -194,6 +194,7 @@ struct jffs2_inode_cache {
#define INO_STATE_CLEARING 6 /* In clear_inode() */ #define INO_STATE_CLEARING 6 /* In clear_inode() */
#define INO_FLAGS_XATTR_CHECKED 0x01 /* has no duplicate xattr_ref */ #define INO_FLAGS_XATTR_CHECKED 0x01 /* has no duplicate xattr_ref */
#define INO_FLAGS_IS_DIR 0x02 /* is a directory */
#define RAWNODE_CLASS_INODE_CACHE 0 #define RAWNODE_CLASS_INODE_CACHE 0
#define RAWNODE_CLASS_XATTR_DATUM 1 #define RAWNODE_CLASS_XATTR_DATUM 1
...@@ -249,7 +250,10 @@ struct jffs2_readinode_info ...@@ -249,7 +250,10 @@ struct jffs2_readinode_info
struct jffs2_full_dirent struct jffs2_full_dirent
{ {
union {
struct jffs2_raw_node_ref *raw; struct jffs2_raw_node_ref *raw;
struct jffs2_inode_cache *ic; /* Just during part of build */
};
struct jffs2_full_dirent *next; struct jffs2_full_dirent *next;
uint32_t version; uint32_t version;
uint32_t ino; /* == zero for unlink */ uint32_t ino; /* == zero for unlink */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment