All of lore.kernel.org
 help / color / mirror / Atom feed
* [fsck.overlay RFC PATCH] overlay: add fsck utility
@ 2017-11-17  5:49 zhangyi (F)
  2017-11-17 17:13 ` Amir Goldstein
  0 siblings, 1 reply; 19+ messages in thread
From: zhangyi (F) @ 2017-11-17  5:49 UTC (permalink / raw)
  To: linux-unionfs; +Cc: miklos, amir73il, yi.zhang, miaoxie

Hi,

Here is the origin version of fsck.overlay utility I mentioned last
week. It scan each underlying directory and check orphan whiteout,
invalid opaque xattr and invalid redirect xattr. We can use it to
check filesystem inconsistency before mount overlayfs to avoid some
unexpected results.

This patch can am to an empty git repository. I have already do basic
test list in 'test' directory. Please do a review give some suggestions.
Thanks a lot.

Thanks,
Yi.

---------------------------

fsck.overlay
============

fsck.overlay is used to check and optionally repair underlying
directories of overlay-filesystem.

Check the following points:

Whiteouts
---------

A whiteout is a character device with 0/0 device number. It is used to
record the removed files or directories, When a whiteout is found in a
directory, there should be at least one directory or file with the same
name in any of the corresponding lower layers. If not exist, the whiteout
will be treated as orphan whiteout and remove.

Opaque directories
------------------

An opaque directory is a directory with "trusted.overlay.opaque" xattr
valued "y". There are two legal situations of making opaque directory: 1)
create directory over whiteout 2) creat directory in merged directory. If an
opaque directory is found, corresponding matching name in lower layers might
exist or parent directory might merged, If not, the opaque xattr will be
treated as invalid and remove.

Redirect directories
--------------------

An redirect directory is a directory with "trusted.overlay.redirect"
xattr valued to the path of the original location from the root of the
overlay. It is only used when renaming a directory and "redirect dir"
feature is enabled. If an redirect directory is found, the following
must be met:

1) The directory store in redirect xattr should exist in one of lower
layers.
2) The origin directory should be redirected only once in one layer,
which mean there is only one redirect xattr point to this origin directory in
the specified layer.
3) A whiteout or an opaque directory with the same name to origin should
exist in the same directory as the redirect directory.

If not, 1) The redirect xattr is invalid and need to remove 2) One of
the redirect xattr is redundant but not sure which one is, ask user 3)
Create a whiteout device or set opaque xattr to an existing directory if the
parent directory was meregd or remove xattr if not.

Usage
=====

1. Ensure overlay filesystem is not mounted based on directories which
need to check.

2. Run fsck.overlay program:
   Usage:
   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]

   Options:
   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
                             multiple lower use ':' as separator.
   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
   -w, --workdir=WORKDIR     specify work directory of overlayfs
   -a, --auto                repair automatically (no questions)
   -v, --verbose             print more messages of overlayfs
   -h, --help                display this usage of overlayfs
   -V, --version             display version information

3. Exit value:
   0      No errors
   1      Filesystem errors corrected
   2      System should be rebooted
   4      Filesystem errors left uncorrected
   8      Operational error
   16     Usage or syntax error
   32     Checking canceled by user request
   128    Shared-library error

Todo
====

1. Overlay filesystem mounted check. Prevent fscking when overlay is
online. Now, We cannot distinguish mounted directories if overlayfs was
mounted with relative path.
2. Symbolic link check.
3. Check origin/impure/nlink xattr.
4. ...

Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
---
 Makefile                  |  31 +++
 README.md                 |  88 +++++++++
 check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
 check.h                   |   7 +
 common.c                  |  95 +++++++++
 common.h                  |  34 ++++
 config.h                  |  22 +++
 fsck.c                    | 179 +++++++++++++++++
 lib.c                     | 197 +++++++++++++++++++
 lib.h                     |  73 +++++++
 mount.c                   | 319 ++++++++++++++++++++++++++++++
 mount.h                   |   8 +
 test/README.md            |  12 ++
 test/auto_test.sh         |  46 +++++
 test/clean.sh             |   6 +
 test/local.config         |  16 ++
 test/src/opaque_test.sh   | 144 ++++++++++++++
 test/src/prepare.sh       |  15 ++
 test/src/redirect_test.sh | 163 ++++++++++++++++
 test/src/whiteout_test.sh |  63 ++++++
 20 files changed, 2000 insertions(+)
 create mode 100644 Makefile
 create mode 100644 README.md
 create mode 100644 check.c
 create mode 100644 check.h
 create mode 100644 common.c
 create mode 100644 common.h
 create mode 100644 config.h
 create mode 100644 fsck.c
 create mode 100644 lib.c
 create mode 100644 lib.h
 create mode 100644 mount.c
 create mode 100644 mount.h
 create mode 100644 test/README.md
 create mode 100755 test/auto_test.sh
 create mode 100755 test/clean.sh
 create mode 100644 test/local.config
 create mode 100755 test/src/opaque_test.sh
 create mode 100755 test/src/prepare.sh
 create mode 100755 test/src/redirect_test.sh
 create mode 100755 test/src/whiteout_test.sh

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ced5005
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,31 @@
+CFLAGS = -Wall -g
+LFLAGS = -lm
+CC = gcc
+
+all: overlay
+
+overlay: fsck.o common.o lib.o check.o mount.o
+	$(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
+
+fsck.o:
+	$(CC) $(CFLAGS) -c fsck.c
+
+common.o:
+	$(CC) $(CFLAGS) -c common.c
+
+lib.o:
+	$(CC) $(CFLAGS) -c lib.c
+
+check.o:
+	$(CC) $(CFLAGS) -c check.c
+
+mount.o:
+	$(CC) $(CFLAGS) -c mount.c
+
+clean:
+	rm -f *.o fsck.overlay
+	rm -rf bin
+
+install: all
+	mkdir bin
+	cp fsck.overlay bin
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8de69cd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+fsck.overlay
+============
+
+fsck.overlay is used to check and optionally repair underlying directories
+of overlay-filesystem.
+
+Check the following points:
+
+Whiteouts
+---------
+
+A whiteout is a character device with 0/0 device number. It is used to record
+the removed files or directories, When a whiteout is found in a directory,
+there should be at least one directory or file with the same name in any of the
+corresponding lower layers. If not exist, the whiteout will be treated as orphan
+whiteout and remove.
+
+
+Opaque directories
+------------------
+
+An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
+"y". There are two legal situations of making opaque directory: 1) create
+directory over whiteout 2) creat directory in merged directory. If an opaque
+directory is found, corresponding matching name in lower layers might exist or
+parent directory might merged, If not, the opaque xattr will be treated as
+invalid and remove.
+
+
+Redirect directories
+--------------------
+
+An redirect directory is a directory with "trusted.overlay.redirect" xattr
+valued to the path of the original location from the root of the overlay. It
+is only used when renaming a directory and "redirect dir" feature is enabled.
+If an redirect directory is found, the following must be met:
+
+1) The directory store in redirect xattr should exist in one of lower layers.
+2) The origin directory should be redirected only once in one layer, which mean
+   there is only one redirect xattr point to this origin directory in the
+   specified layer.
+3) A whiteout or an opaque directory with the same name to origin should exist
+   in the same directory as the redirect directory.
+
+If not, 1) The redirect xattr is invalid and need to remove 2) One of the
+redirect xattr is redundant but not sure which one is, ask user 3) Create a
+whiteout device or set opaque xattr to an existing directory if the parent
+directory was meregd or remove xattr if not.
+
+Usage
+=====
+
+1. Ensure overlay filesystem is not mounted based on directories which need to
+   check.
+
+2. Run fsck.overlay program:
+   Usage:
+   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
+
+   Options:
+   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
+                             multiple lower use ':' as separator.
+   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
+   -w, --workdir=WORKDIR     specify work directory of overlayfs
+   -a, --auto                repair automatically (no questions)
+   -v, --verbose             print more messages of overlayfs
+   -h, --help                display this usage of overlayfs
+   -V, --version             display version information
+
+3. Exit value:
+   0      No errors
+   1      Filesystem errors corrected
+   2      System should be rebooted
+   4      Filesystem errors left uncorrected
+   8      Operational error
+   16     Usage or syntax error
+   32     Checking canceled by user request
+   128    Shared-library error
+
+Todo
+====
+
+1. Overlay filesystem mounted check. Prevent fscking when overlay is
+   online. Now, We cannot distinguish mounted directories if overlayfs was
+   mounted with relative path.
+2. Symbolic link check.
+3. Check origin/impure/nlink xattr.
+4. ...
diff --git a/check.c b/check.c
new file mode 100644
index 0000000..1794501
--- /dev/null
+++ b/check.c
@@ -0,0 +1,482 @@
+/*
+ *
+ *	Check and fix inconsistency for all underlying layers of overlay
+ *
+ * 	zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+#include "common.h"
+#include "lib.h"
+#include "check.h"
+
+/* Underlying information */
+struct ovl_lower_check {
+	unsigned int type;	/* check extent type */
+
+	bool exist;
+	char path[PATH_MAX];	/* exist pathname found, only valid if exist */
+	struct stat st;		/* only valid if exist */
+};
+
+/* Redirect information */
+struct ovl_redirect_entry {
+	struct ovl_redirect_entry *next;
+
+	char origin[PATH_MAX];	/* origin directory path */
+
+	char path[PATH_MAX];	/* redirect directory */
+	int dirtype;		/* OVL_UPPER or OVL_LOWER */
+	int dirnum;		/* only valid when dirtype==OVL_LOWER */
+};
+
+/* Whiteout */
+#define WHITEOUT_DEV	0
+#define WHITEOUT_MOD	0
+
+extern char **lowerdir;
+extern char upperdir[];
+extern char workdir[];
+extern unsigned int lower_num;
+extern int flags;
+extern int status;
+
+static inline mode_t file_type(const struct stat *status)
+{
+	return status->st_mode & S_IFMT;
+}
+
+static inline bool is_whiteout(const struct stat *status)
+{
+	return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
+}
+
+static inline bool is_dir(const struct stat *status)
+{
+	return file_type(status) == S_IFDIR;
+}
+
+static bool is_dir_xattr(const char *pathname, const char *xattrname)
+{
+	char val;
+	ssize_t ret;
+
+	ret = getxattr(pathname, xattrname, &val, 1);
+	if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
+		print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
+			    xattrname, strerror(errno));
+		return false;
+	}
+
+	return (ret == 1 && val == 'y') ? true : false;
+}
+
+static inline bool ovl_is_opaque(const char *pathname)
+{
+	return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
+}
+
+static inline int ovl_remove_opaque(const char *pathname)
+{
+	return remove_xattr(pathname, OVL_OPAQUE_XATTR);
+}
+
+static inline int ovl_set_opaque(const char *pathname)
+{
+	return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
+}
+
+static int ovl_get_redirect(const char *pathname, size_t dirlen,
+			    size_t filelen, char **redirect)
+{
+	char *buf = NULL;
+	ssize_t ret;
+
+	ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
+	if (ret <= 0 || !buf)
+		return ret;
+
+	if (buf[0] != '/') {
+		size_t baselen = strlen(pathname)-filelen-dirlen;
+
+		buf = srealloc(buf, ret + baselen + 1);
+		memmove(buf + baselen, buf, ret);
+		memcpy(buf, pathname+dirlen, baselen);
+		buf[ret + baselen] = '\0';
+	}
+
+	*redirect = buf;
+	return 0;
+}
+
+static inline int ovl_remove_redirect(const char *pathname)
+{
+	return remove_xattr(pathname, OVL_REDIRECT_XATTR);
+}
+
+static inline int ovl_create_whiteout(const char *pathname)
+{
+	int ret;
+
+	ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
+	if (ret)
+		print_err(_("Cannot mknod %s:%s\n"),
+			    pathname, strerror(errno));
+	return ret;
+}
+
+/*
+ * Scan each lower dir lower than 'start' and check type matching,
+ * we stop scan if we found something.
+ *
+ * skip: skip whiteout.
+ *
+ */
+static int ovl_check_lower(const char *path, unsigned int start,
+			   struct ovl_lower_check *chk, bool skip)
+{
+	char lower_path[PATH_MAX];
+	struct stat st;
+	unsigned int i;
+
+	for (i = start; i < lower_num; i++) {
+		snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
+
+		if (lstat(lower_path, &st) != 0) {
+			if (errno != ENOENT && errno != ENOTDIR) {
+				print_err(_("Cannot stat %s: %s\n"),
+					    lower_path, strerror(errno));
+				return -1;
+			}
+			continue;
+		}
+
+		if (skip && is_whiteout(&st))
+			continue;
+
+		chk->exist = true;
+		chk->st = st;
+		strncpy(chk->path, lower_path, sizeof(chk->path));
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Scan each underlying dirs under specified dir if a whiteout is
+ * found, check it's orphan or not. In auto-mode, orphan whiteouts
+ * will be removed directly.
+ */
+static int ovl_check_whiteout(struct scan_ctx *sctx)
+{
+	const char *pathname = sctx->pathname;
+	const struct stat *st = sctx->st;
+	struct ovl_lower_check chk = {0};
+	unsigned int start;
+	int ret = 0;
+
+	/* Is a whiteout ? */
+	if (!is_whiteout(st))
+		return 0;
+
+	/* Is Whiteout in the bottom lower dir ? */
+	if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
+		goto remove;
+
+	/*
+	 * Scan each corresponding lower directroy under this layer,
+	 * check is there a file or dir with the same name.
+	 */
+	start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
+	ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
+	if (ret)
+		return ret;
+	if (chk.exist && !is_whiteout(&chk.st))
+		goto out;
+
+remove:
+	/* Remove orphan whiteout directly or ask user */
+	print_info(_("Orphan whiteout: %s "), pathname);
+	if (!ask_question("Remove", 1))
+		return 0;
+
+	ret = unlink(pathname);
+	if (ret) {
+		print_err(_("Cannot unlink %s: %s\n"), pathname,
+			    strerror(errno));
+		return ret;
+	}
+out:
+	return ret;
+}
+
+/*
+ * Scan each underlying under specified dir if an opaque dir is found,
+ * check the opaque xattr is invalid or not. In auto-mode, invalid
+ * opaque xattr will be removed directly.
+ * Do the follow checking:
+ * 1) Check lower matching name exist or not.
+ * 2) Check parent dir is merged or pure.
+ * If no lower matching and parent is not merged, remove opaque xattr.
+ */
+static int ovl_check_opaque(struct scan_ctx *sctx)
+{
+	const char *pathname = sctx->pathname;
+	char parent_path[PATH_MAX];
+	struct ovl_lower_check chk = {0};
+	unsigned int start;
+	int ret = 0;
+
+	/* Is opaque ? */
+	if (!ovl_is_opaque(pathname))
+		goto out;
+
+	/* Opaque dir in last lower dir ? */
+	if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
+		goto remove;
+
+	/*
+	 * Scan each corresponding lower directroy under this layer,
+	 * check if there is a file or dir with the same name.
+	 */
+	start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
+	ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
+	if (ret)
+		return ret;
+	if (chk.exist && !is_whiteout(&chk.st))
+		goto out;
+
+	/* Check parent directory merged or pure */
+	memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
+	ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
+	if (ret)
+		return ret;
+	if (chk.exist && is_dir(&chk.st))
+		goto out;
+
+remove:
+	/* Remove opaque xattr or ask user */
+	print_info(_("Invalid opaque xattr: %s "), pathname);
+	if (!ask_question("Remove", 1))
+		return 0;
+
+	ret = ovl_remove_opaque(pathname);
+out:
+	return ret;
+}
+
+static struct ovl_redirect_entry *redirect_list = NULL;
+
+static void ovl_redirect_entry_add(const char *path, int dirtype,
+				   int dirnum, const char *origin)
+{
+	struct ovl_redirect_entry *last, *new;
+
+	new = smalloc(sizeof(*new));
+
+	print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
+		      origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
+		      (dirtype == OVL_UPPER) ? 0 : dirnum);
+
+	if (!redirect_list) {
+		redirect_list = new;
+	} else {
+		for (last = redirect_list; last->next; last = last->next);
+		last->next = new;
+	}
+	new->next = NULL;
+	new->dirtype = dirtype;
+	new->dirnum = dirnum;
+	strncpy(new->origin, origin, sizeof(new->origin));
+	strncpy(new->path, path, sizeof(new->path));
+}
+
+static bool vol_redirect_entry_find(const char *origin, int dirtype,
+				    int dirnum, char **founded)
+{
+	struct ovl_redirect_entry *entry;
+
+	if (!redirect_list)
+		return false;
+
+	for (entry = redirect_list; entry; entry = entry->next) {
+		bool same_layer;
+
+		print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
+			      entry->origin, entry->path,
+			      (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
+			      (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
+
+		same_layer = ((entry->dirtype == dirtype) &&
+			      (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
+
+		if (same_layer && !strcmp(entry->origin, origin)) {
+			*founded = entry->path;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static void vol_redirect_free(void)
+{
+	struct ovl_redirect_entry *entry;
+
+	while (redirect_list) {
+		entry = redirect_list;
+		redirect_list = redirect_list->next;
+		free(entry);
+	}
+}
+
+/*
+ * Get redirect origin directory stored in the xattr, check it's invlaid
+ * or not, In auto-mode, invalid redirect xattr will be removed directly.
+ * Do the follow checking:
+ * 1) Check the origin directory exist or not. If not, remove xattr.
+ * 2) Count how many directories the origin directory was redirected by.
+ *    If more than one in the same layer, there must be some inconsistency
+ *    but not sure, just warn.
+ * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
+ */
+static int ovl_check_redirect(struct scan_ctx *sctx)
+{
+	const char *pathname = sctx->pathname;
+	struct ovl_lower_check chk = {0};
+	char redirect_rpath[PATH_MAX];
+	struct stat rst;
+	char *redirect = NULL;
+	unsigned int start;
+	int ret = 0;
+
+	/* Get redirect */
+	ret = ovl_get_redirect(pathname, sctx->dirlen,
+			       sctx->filelen, &redirect);
+	if (ret || !redirect)
+		return ret;
+
+	print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
+
+	/* Redirect dir in last lower dir ? */
+	if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
+		goto remove;
+
+	/* Scan lower directories to check redirect dir exist or not */
+	start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
+	ret = ovl_check_lower(redirect, start, &chk, false);
+	if (ret)
+		goto out;
+	if (chk.exist && is_dir(&chk.st)) {
+		char *tmp;
+
+		/* Check duplicate in same layer */
+		if (vol_redirect_entry_find(chk.path, sctx->dirtype,
+					    sctx->num, &tmp)) {
+			print_info("Duplicate redirect directories found:\n");
+			print_info("origin:%s current:%s latest:%s\n",
+				   chk.path, pathname, tmp);
+
+			set_st_inconsistency(&status);
+		}
+
+		ovl_redirect_entry_add(pathname, sctx->dirtype,
+				       sctx->num, chk.path);
+
+		/* Check and fix whiteout or opaque dir */
+		snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
+			 sctx->dirname, redirect);
+		if (lstat(redirect_rpath, &rst) != 0) {
+			if (errno != ENOENT && errno != ENOTDIR) {
+				print_err(_("Cannot stat %s: %s\n"),
+					    redirect_rpath, strerror(errno));
+				goto out;
+			}
+
+			/* Found nothing, create a whiteout */
+			ret = ovl_create_whiteout(redirect_rpath);
+
+		} else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
+			/* Found a directory but not opaqued, fix opaque xattr */
+			ret = ovl_set_opaque(redirect_rpath);
+		}
+
+		goto out;
+	}
+
+remove:
+	/* Remove redirect xattr or ask user */
+	print_info(_("Invalid redirect xattr: %s "), pathname);
+	if (!ask_question("Remove", 1))
+		goto out;
+
+	ret = ovl_remove_redirect(pathname);
+out:
+	free(redirect);
+	return ret;
+}
+
+static struct scan_operations ovl_scan_ops = {
+	.whiteout = ovl_check_whiteout,
+	.opaque = ovl_check_opaque,
+	.redirect = ovl_check_redirect,
+};
+
+static void ovl_scan_clean(void)
+{
+	/* Clean redirect entry record */
+	vol_redirect_free();
+}
+
+/* Scan upperdir and each lowerdirs, check and fix inconsistency */
+int ovl_scan_fix(void)
+{
+	struct scan_ctx sctx;
+	unsigned int i;
+	int ret = 0;
+
+	if (flags | FL_VERBOSE)
+		print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
+
+	/* Scan upper directory */
+	if (flags | FL_VERBOSE)
+		print_info(_("Scan upper directory: %s\n"), upperdir);
+
+	sctx.dirname = upperdir;
+	sctx.dirlen = strlen(upperdir);
+	sctx.dirtype = OVL_UPPER;
+
+	ret = scan_dir(&sctx, &ovl_scan_ops);
+	if (ret)
+		goto out;
+
+	/* Scan every lower directories */
+	for (i = 0; i < lower_num; i++) {
+		if (flags | FL_VERBOSE)
+			print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
+
+		sctx.dirname = lowerdir[i];
+		sctx.dirlen = strlen(lowerdir[i]);
+		sctx.dirtype = OVL_LOWER;
+		sctx.num = i;
+
+		ret = scan_dir(&sctx, &ovl_scan_ops);
+		if (ret)
+			goto out;
+	}
+out:
+	ovl_scan_clean();
+	return ret;
+}
diff --git a/check.h b/check.h
new file mode 100644
index 0000000..373ff3a
--- /dev/null
+++ b/check.h
@@ -0,0 +1,7 @@
+#ifndef OVL_WHITECHECK_H
+#define OVL_WHITECHECK_H
+
+/* Scan upperdir and each lowerdirs, check and fix inconsistency */
+int ovl_scan_fix(void);
+
+#endif /* OVL_WHITECHECK_H */
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..4e77045
--- /dev/null
+++ b/common.c
@@ -0,0 +1,95 @@
+/*
+ *
+ *	Common things for all utilities
+ *
+ *	zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "common.h"
+#include "config.h"
+
+extern char *program_name;
+
+/* #define DEBUG 1 */
+#ifdef DEBUG
+void print_debug(char *fmtstr, ...)
+{
+	va_list args;
+
+	va_start(args, fmtstr);
+	fprintf(stdout, "%s:[Debug]: ", program_name);
+	vfprintf(stdout, fmtstr, args);
+	va_end(args);
+}
+#else
+void print_debug (char *fmtstr, ...) {}
+#endif
+
+void print_info(char *fmtstr, ...)
+{
+	va_list args;
+
+	va_start(args, fmtstr);
+	vfprintf(stdout, fmtstr, args);
+	va_end(args);
+}
+
+void print_err(char *fmtstr, ...)
+{
+	va_list args;
+
+	va_start(args, fmtstr);
+	fprintf(stderr, "%s:[Error]: ", program_name);
+	vfprintf(stderr, fmtstr, args);
+	va_end(args);
+}
+
+void *smalloc(size_t size)
+{
+	void *new = malloc(size);
+
+	if (!new) {
+		print_err(_("malloc error:%s\n"), strerror(errno));
+		exit(1);
+	}
+
+	memset(new, 0, size);
+	return new;
+}
+
+void *srealloc(void *addr, size_t size)
+{
+	void *re = realloc(addr, size);
+
+	if (!re) {
+		print_err(_("malloc error:%s\n"), strerror(errno));
+		exit(1);
+	}
+	return re;
+}
+
+char *sstrdup(const char *src)
+{
+	char *dst = strdup(src);
+
+	if (!dst) {
+		print_err(_("strdup error:%s\n"), strerror(errno));
+		exit(1);
+	}
+
+	return dst;
+}
+
+void version(void)
+{
+	printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
+}
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..c4707e7
--- /dev/null
+++ b/common.h
@@ -0,0 +1,34 @@
+#ifndef OVL_COMMON_H
+#define OVL_COMMON_H
+
+#ifndef __attribute__
+# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
+#  define __attribute__(x)
+# endif
+#endif
+
+#ifdef USE_GETTEXT
+#include <libintl.h>
+#define _(x)	gettext((x))
+#else
+#define _(x) 	(x)
+#endif
+
+/* Print an error message */
+void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+
+/* Print an info message */
+void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+
+/* Print an debug message */
+void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+
+/* Safety wrapper */
+void *smalloc(size_t size);
+void *srealloc(void *addr, size_t size);
+char *sstrdup(const char *src);
+
+/* Print program version */
+void version(void);
+
+#endif /* OVL_COMMON_H */
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..deac089
--- /dev/null
+++ b/config.h
@@ -0,0 +1,22 @@
+#ifndef OVL_CONFIG_H
+#define OVL_CONFIG_H
+
+/* program version */
+#define PACKAGE_VERSION	"v1.0.0"
+
+/* overlay max lower stacks (the same to kernel overlayfs driver) */
+#define OVL_MAX_STACK 500
+
+/* File with mounted filesystems */
+#define MOUNT_TAB "/proc/mounts"
+
+/* Name of overlay filesystem type */
+#define OVERLAY_NAME "overlay"
+#define OVERLAY_NAME_OLD "overlayfs"
+
+/* Mount options */
+#define OPT_LOWERDIR "lowerdir="
+#define OPT_UPPERDIR "upperdir="
+#define OPT_WORKDIR "workdir="
+
+#endif
diff --git a/fsck.c b/fsck.c
new file mode 100644
index 0000000..cbcb8e9
--- /dev/null
+++ b/fsck.c
@@ -0,0 +1,179 @@
+/*
+ *
+ *	Utility to fsck overlay
+ *
+ * 	zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+#include "common.h"
+#include "config.h"
+#include "lib.h"
+#include "check.h"
+#include "mount.h"
+
+char *program_name;
+
+char **lowerdir;
+char upperdir[PATH_MAX] = {0};
+char workdir[PATH_MAX] = {0};
+unsigned int lower_num;
+int flags;		/* user input option flags */
+int status;		/* fsck scan status */
+
+/* Cleanup lower directories buf */
+static void ovl_clean_lowerdirs(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < lower_num; i++) {
+		free(lowerdir[i]);
+		lowerdir[i] = NULL;
+		lower_num = 0;
+	}
+	free(lowerdir);
+	lowerdir = NULL;
+}
+
+static void usage(void)
+{
+	print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
+		    "[-avhV]\n\n"), program_name);
+	print_info(_("Options:\n"
+		    "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
+		    "                          multiple lower use ':' as separator\n"
+		    "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
+		    "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
+		    "-a, --auto                repair automatically (no questions)\n"
+		    "-v, --verbose             print more messages of overlayfs\n"
+		    "-h, --help                display this usage of overlayfs\n"
+		    "-V, --version             display version information\n"));
+	exit(1);
+}
+
+static void parse_options(int argc, char *argv[])
+{
+	char *lowertemp;
+	int c;
+	int ret = 0;
+
+	struct option long_options[] = {
+		{"lowerdir", required_argument, NULL, 'l'},
+		{"upperdir", required_argument, NULL, 'u'},
+		{"workdir", required_argument, NULL, 'w'},
+		{"auto", no_argument, NULL, 'a'},
+		{"verbose", no_argument, NULL, 'v'},
+		{"version", no_argument, NULL, 'V'},
+		{"help", no_argument, NULL, 'h'},
+		{NULL, 0, NULL, 0}
+	};
+
+	while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
+		switch (c) {
+		case 'l':
+			lowertemp = strdup(optarg);
+			ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
+			free(lowertemp);
+			break;
+		case 'u':
+			if (realpath(optarg, upperdir)) {
+				print_debug(_("Upperdir:%s\n"), upperdir);
+				flags |= FL_UPPER;
+			} else {
+				print_err(_("Failed to resolve upperdir:%s\n"), optarg);
+				ret = -1;
+			}
+			break;
+		case 'w':
+			if (realpath(optarg, workdir)) {
+				print_debug(_("Workdir:%s\n"), workdir);
+				flags |= FL_WORK;
+			} else {
+				print_err(_("Failed to resolve workdir:%s\n"), optarg);
+				ret = -1;
+			}
+			break;
+		case 'a':
+			flags |= FL_AUTO;
+			break;
+		case 'v':
+			flags |= FL_VERBOSE;
+			break;
+		case 'V':
+			version();
+			return;
+		case 'h':
+		default:
+			usage();
+			return;
+		}
+
+		if (ret)
+			exit(1);
+	}
+
+	if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
+		print_info(_("Please specify correct lowerdirs and upperdir\n"));
+		usage();
+	}
+}
+
+void fsck_status_check(int *val)
+{
+	if (status & OVL_ST_INCONSISTNECY) {
+		*val |= FSCK_UNCORRECTED;
+		print_info(_("Still have unexpected inconsistency!\n"));
+	}
+
+	if (status & OVL_ST_ABORT) {
+		*val |= FSCK_ERROR;
+		print_info(_("Cannot continue, aborting\n"));
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	bool mounted;
+	int err = 0;
+	int exit_value = 0;
+
+	program_name = basename(argv[0]);
+
+	parse_options(argc, argv);
+
+	/* Ensure overlay filesystem not mounted */
+	if ((err = ovl_check_mount(&mounted)))
+		goto out;
+	if (!mounted) {
+		set_st_abort(&status);
+		goto out;
+	}
+
+	/* Scan and fix */
+	if ((err = ovl_scan_fix()))
+		goto out;
+
+out:
+	fsck_status_check(&exit_value);
+	ovl_clean_lowerdirs();
+
+	if (err)
+		exit_value |= FSCK_ERROR;
+	if (exit_value)
+		print_info("WARNING: Filesystem check failed, may not clean\n");
+	else
+		print_info("Filesystem clean\n");
+
+	return exit_value;
+}
diff --git a/lib.c b/lib.c
new file mode 100644
index 0000000..a6832fe
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,197 @@
+/*
+ *
+ *	Common things for all utilities
+ *
+ * 	zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <fts.h>
+
+#include "common.h"
+#include "lib.h"
+
+extern int flags;
+extern int status;
+
+static int ask_yn(const char *question, int def)
+{
+	char ans[16];
+
+	print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
+	fflush(stdout);
+	while (fgets(ans, sizeof(ans)-1, stdin)) {
+		if (ans[0] == '\n')
+			return def;
+		else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
+			return 1;
+		else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
+			return 0;
+		else
+			print_info(_("Illegal answer. Please input y/n or yes/no:"));
+		fflush(stdout);
+	}
+	return def;
+}
+
+/* Ask user */
+int ask_question(const char *question, int def)
+{
+	if (flags & FL_AUTO) {
+		print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
+		return def;
+	}
+
+	return ask_yn(question, def);
+}
+
+ssize_t get_xattr(const char *pathname, const char *xattrname,
+		  char **value, bool *exist)
+{
+	char *buf = NULL;
+	ssize_t ret;
+
+	ret = getxattr(pathname, xattrname, NULL, 0);
+	if (ret < 0) {
+		if (errno == ENODATA || errno == ENOTSUP) {
+			if (exist)
+				*exist = false;
+			return 0;
+		} else {
+			goto fail;
+		}
+	}
+
+	/* Zero size value means xattr exist but value unknown */
+	if (exist)
+		*exist = true;
+	if (ret == 0 || !value)
+		return 0;
+
+	buf = smalloc(ret+1);
+	ret = getxattr(pathname, xattrname, buf, ret);
+	if (ret <= 0)
+		goto fail2;
+
+	buf[ret] = '\0';
+	*value = buf;
+	return ret;
+
+fail2:
+	free(buf);
+fail:
+	print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
+		    xattrname, strerror(errno));
+	return -1;
+}
+
+int set_xattr(const char *pathname, const char *xattrname,
+		void *value, size_t size)
+{
+	int ret;
+
+	ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
+	if (ret && ret != EEXIST)
+		goto fail;
+
+	if (ret == EEXIST) {
+		ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
+		if (ret)
+			goto fail;
+	}
+
+	return 0;
+fail:
+	print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
+		    xattrname, strerror(errno));
+	return ret;
+}
+
+int remove_xattr(const char *pathname, const char *xattrname)
+{
+	int ret;
+	if ((ret = removexattr(pathname, xattrname)))
+		print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
+			    xattrname, strerror(errno));
+	return ret;
+}
+
+static inline int __check_entry(struct scan_ctx *sctx,
+				  int (*do_check)(struct scan_ctx *))
+{
+	return do_check ? do_check(sctx) : 0;
+}
+
+/* Scan specified directories and invoke callback */
+int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
+{
+	char *paths[2] = {(char *)sctx->dirname, NULL};
+	FTS *ftsp;
+	FTSENT *ftsent;
+	int ret = 0;
+
+	ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
+	if (ftsp == NULL) {
+		print_err(_("Failed to fts open %s:%s\n"),
+			    sctx->dirname, strerror(errno));
+		return -1;
+	}
+
+	while ((ftsent = fts_read(ftsp)) != NULL) {
+		int err;
+
+		print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
+			(ftsent->fts_info == FTS_D) ? "d" :
+			(ftsent->fts_info == FTS_DNR) ? "dnr" :
+			(ftsent->fts_info == FTS_DP) ? "dp" :
+			(ftsent->fts_info == FTS_F) ? "f" :
+			(ftsent->fts_info == FTS_NS) ? "ns" :
+			(ftsent->fts_info == FTS_SL) ? "sl" :
+			(ftsent->fts_info == FTS_SLNONE) ? "sln" :
+			(ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
+			ftsent->fts_level, ftsent->fts_statp->st_size,
+			ftsent->fts_path, ftsent->fts_name);
+
+
+		/* Fillup base context */
+		sctx->pathname = ftsent->fts_path;
+		sctx->pathlen = ftsent->fts_pathlen;
+		sctx->filename = ftsent->fts_name;
+		sctx->filelen = ftsent->fts_namelen;
+		sctx->st = ftsent->fts_statp;
+
+		switch (ftsent->fts_info) {
+		case FTS_DEFAULT:
+			/* Check whiteouts */
+			err = __check_entry(sctx, sop->whiteout);
+			ret = (ret || !err) ? ret : err;
+			break;
+		case FTS_D:
+			/* Check opaque and redirect */
+			err = __check_entry(sctx, sop->opaque);
+			ret = (ret || !err) ? ret : err;
+
+			err = __check_entry(sctx, sop->redirect);
+			ret = (ret || !err) ? ret : err;
+			break;
+		case FTS_NS:
+		case FTS_DNR:
+		case FTS_ERR:
+			print_err(_("Failed to fts read %s:%s\n"),
+				    ftsent->fts_path, strerror(ftsent->fts_errno));
+			goto out;
+		}
+	}
+out:
+	fts_close(ftsp);
+	return ret;
+}
diff --git a/lib.h b/lib.h
new file mode 100644
index 0000000..463b263
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,73 @@
+#ifndef OVL_LIB_H
+#define OVL_LIB_H
+
+/* Common return value */
+#define FSCK_OK          0	/* No errors */
+#define FSCK_NONDESTRUCT 1	/* File system errors corrected */
+#define FSCK_REBOOT      2	/* System should be rebooted */
+#define FSCK_UNCORRECTED 4	/* File system errors left uncorrected */
+#define FSCK_ERROR       8	/* Operational error */
+#define FSCK_USAGE       16	/* Usage or syntax error */
+#define FSCK_CANCELED	 32	/* Aborted with a signal or ^C */
+#define FSCK_LIBRARY     128	/* Shared library error */
+
+/* Fsck status */
+#define OVL_ST_INCONSISTNECY	(1 << 0)
+#define OVL_ST_ABORT		(1 << 1)
+
+/* Option flags */
+#define FL_VERBOSE	(1 << 0)	/* verbose */
+#define FL_UPPER	(1 << 1)	/* specify upper directory */
+#define FL_WORK		(1 << 2)	/* specify work directory */
+#define FL_AUTO		(1 << 3)	/* automactically scan dirs and repair */
+
+/* Scan path type */
+#define OVL_UPPER	0
+#define OVL_LOWER	1
+#define OVL_WORKER	2
+#define OVL_PTYPE_MAX	3
+
+/* Xattr */
+#define OVL_OPAQUE_XATTR	"trusted.overlay.opaque"
+#define OVL_REDIRECT_XATTR	"trusted.overlay.redirect"
+
+/* Directories scan data struct */
+struct scan_ctx {
+	const char *dirname;	/* upper/lower root dir */
+	size_t dirlen;		/* strlen(dirlen) */
+	int dirtype;		/* OVL_UPPER or OVL_LOWER */
+	int num;		/* lower dir depth, lower type use only */
+
+	const char *pathname;	/* file path from the root */
+	size_t pathlen;		/* strlen(pathname) */
+	const char *filename;	/* filename */
+	size_t filelen;		/* strlen(filename) */
+	struct stat *st;	/* file stat */
+};
+
+/* Directories scan callback operations struct */
+struct scan_operations {
+	int (*whiteout)(struct scan_ctx *);
+	int (*opaque)(struct scan_ctx *);
+	int (*redirect)(struct scan_ctx *);
+};
+
+static inline void set_st_inconsistency(int *st)
+{
+	*st |= OVL_ST_INCONSISTNECY;
+}
+
+static inline void set_st_abort(int *st)
+{
+	*st |= OVL_ST_ABORT;
+}
+
+int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
+int ask_question(const char *question, int def);
+ssize_t get_xattr(const char *pathname, const char *xattrname,
+		  char **value, bool *exist);
+int set_xattr(const char *pathname, const char *xattrname,
+	      void *value, size_t size);
+int remove_xattr(const char *pathname, const char *xattrname);
+
+#endif /* OVL_LIB_H */
diff --git a/mount.c b/mount.c
new file mode 100644
index 0000000..28ce8e5
--- /dev/null
+++ b/mount.c
@@ -0,0 +1,319 @@
+/*
+ *
+ *	Check mounted overlay
+ *
+ * 	zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <mntent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+#include "common.h"
+#include "config.h"
+#include "lib.h"
+#include "check.h"
+#include "mount.h"
+
+struct ovl_mnt_entry {
+	char *lowers;
+	char **lowerdir;
+	unsigned int lowernum;
+	char upperdir[PATH_MAX];
+	char workdir[PATH_MAX];
+};
+
+/* Mount buf allocate a time */
+#define ALLOC_NUM	16
+
+extern char **lowerdir;
+extern char upperdir[];
+extern char workdir[];
+extern unsigned int lower_num;
+
+/*
+ * Split directories to individual one.
+ * (copied from linux kernel, see fs/overlayfs/super.c)
+ */
+static unsigned int ovl_split_lowerdirs(char *lower)
+{
+	unsigned int ctr = 1;
+	char *s, *d;
+
+	for (s = d = lower;; s++, d++) {
+		if (*s == '\\') {
+			s++;
+		} else if (*s == ':') {
+			*d = '\0';
+			ctr++;
+			continue;
+		}
+		*d = *s;
+		if (!*s)
+			break;
+	}
+	return ctr;
+}
+
+/* Resolve each lower directories and check the validity */
+int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
+			  unsigned int *lowernum)
+{
+	unsigned int num;
+	char **dirs;
+	char *p;
+	int i;
+
+	num = ovl_split_lowerdirs(loweropt);
+	if (num > OVL_MAX_STACK) {
+		print_err(_("Too many lower directories:%u, max:%u\n"),
+			    num, OVL_MAX_STACK);
+		return -1;
+	}
+
+	dirs = smalloc(sizeof(char *) * num);
+
+	p = loweropt;
+	for (i = 0; i < num; i++) {
+		dirs[i] = smalloc(PATH_MAX);
+		if (!realpath(p, dirs[i])) {
+			print_err(_("Failed to resolve lowerdir:%s:%s\n"),
+				    p, strerror(errno));
+			goto err;
+		}
+		print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
+		p = strchr(p, '\0') + 1;
+	}
+
+	*lowerdir = dirs;
+	*lowernum = num;
+
+	return 0;
+err:
+	for (i--; i >= 0; i--)
+		free(dirs[i]);
+	free(dirs);
+	*lowernum = 0;
+	return -1;
+}
+
+/*
+ * Split and return next opt.
+ * (copied from linux kernel, see fs/overlayfs/super.c)
+ */
+static char *ovl_next_opt(char **s)
+{
+	char *sbegin = *s;
+	char *p;
+
+	if (sbegin == NULL)
+		return NULL;
+
+	for (p = sbegin; *p; p++) {
+		if (*p == '\\') {
+			p++;
+			if (!*p)
+				break;
+		} else if (*p == ',') {
+			*p = '\0';
+			*s = p + 1;
+			return sbegin;
+		}
+	}
+	*s = NULL;
+	return sbegin;
+}
+
+/*
+ * Split and parse opt to each directories.
+ *
+ * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
+ * mounted use relative path, so there may have misjudgment.
+ */
+static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
+{
+	char tmp[PATH_MAX] = {0};
+	char *p;
+	int len;
+	int ret;
+	int i;
+
+	while ((p = ovl_next_opt(&opt)) != NULL) {
+		if (!*p)
+			continue;
+
+		if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
+			len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
+			strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
+
+			if (!realpath(tmp, entry->upperdir)) {
+				print_err(_("Faile to resolve path:%s:%s\n"),
+					    tmp, strerror(errno));
+				ret = -1;
+				goto errout;
+			}
+		} else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
+			len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
+			entry->lowers = smalloc(len);
+			strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
+
+			if ((ret = ovl_resolve_lowerdirs(entry->lowers,
+					&entry->lowerdir, &entry->lowernum)))
+				goto errout;
+
+		} else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
+			len = strlen(p) - strlen(OPT_WORKDIR) + 1;
+			strncpy(tmp, p+strlen(OPT_WORKDIR), len);
+
+			if (!realpath(tmp, entry->workdir)) {
+				print_err(_("Faile to resolve path:%s:%s\n"),
+					    tmp, strerror(errno));
+				ret = -1;
+				goto errout;
+			}
+		}
+	}
+
+errout:
+	if (entry->lowernum) {
+		for (i = 0; i < entry->lowernum; i++)
+			free(entry->lowerdir[i]);
+		free(entry->lowerdir);
+		entry->lowerdir = NULL;
+		entry->lowernum = 0;
+	}
+	free(entry->lowers);
+	entry->lowers = NULL;
+
+	return ret;
+}
+
+/* Scan current mounted overlayfs and get used underlying directories */
+static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
+			       int *ovl_mnt_count)
+{
+	struct ovl_mnt_entry *mnt_entries;
+	struct mntent *mnt;
+	FILE *fp;
+	char *opt;
+	int allocated, num = 0;
+
+	fp = setmntent(MOUNT_TAB, "r");
+	if (!fp) {
+		print_err(_("Fail to setmntent %s:%s\n"),
+			    MOUNT_TAB, strerror(errno));
+		return -1;
+	}
+
+	allocated = ALLOC_NUM;
+	mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
+
+	while ((mnt = getmntent(fp))) {
+		if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
+		    !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
+
+			opt = sstrdup(mnt->mnt_opts);
+			if (ovl_parse_opt(opt, &mnt_entries[num])) {
+				free(opt);
+				continue;
+			}
+
+			num++;
+			if (num % ALLOC_NUM == 0) {
+				allocated += ALLOC_NUM;
+				mnt_entries = srealloc(mnt_entries,
+				     sizeof(struct ovl_mnt_entry) * allocated);
+			}
+
+			free(opt);
+		}
+	}
+
+	*ovl_mnt_entries = mnt_entries;
+	*ovl_mnt_count = num;
+
+	endmntent(fp);
+	return 0;
+}
+
+static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
+				int ovl_mnt_count)
+{
+	int i,j;
+
+	for (i = 0; i < ovl_mnt_count; i++) {
+		for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
+			free(ovl_mnt_entries[i].lowerdir[j]);
+		free(ovl_mnt_entries[i].lowerdir);
+		free(ovl_mnt_entries[i].lowers);
+	}
+	free(ovl_mnt_entries);
+}
+
+/*
+ * Scan every mounted filesystem, check the overlay directories want
+ * to check is already mounted. Check and fix an online overlay is not
+ * allowed.
+ *
+ * Note: fsck may modify lower layers, so even match only one directory
+ *       is triggered as mounted.
+ */
+int ovl_check_mount(bool *pass)
+{
+	struct ovl_mnt_entry *ovl_mnt_entries;
+	int ovl_mnt_entry_count;
+	char *mounted_path;
+	bool mounted;
+	int i,j,k;
+	int ret;
+
+	ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
+	if (ret)
+		return ret;
+
+	/* Only check hard matching */
+	for (i = 0; i < ovl_mnt_entry_count; i++) {
+		/* Check lower */
+		for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
+			for (k = 0; k < lower_num; k++) {
+				if (!strcmp(lowerdir[k],
+					    ovl_mnt_entries[i].lowerdir[j])) {
+					mounted_path = lowerdir[k];
+					mounted = true;
+					goto out;
+				}
+			}
+		}
+
+		/* Check upper */
+		if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
+			mounted_path = upperdir;
+			mounted = true;
+			goto out;
+		}
+
+		/* Check worker */
+		if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
+			mounted_path = workdir;
+			mounted = true;
+			goto out;
+		}
+	}
+out:
+	ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
+
+	if (mounted)
+		print_info(_("Dir %s is mounted\n"), mounted_path);
+	*pass = !mounted;
+
+	return 0;
+}
diff --git a/mount.h b/mount.h
new file mode 100644
index 0000000..8a3762d
--- /dev/null
+++ b/mount.h
@@ -0,0 +1,8 @@
+#ifndef OVL_MOUNT_H
+#define OVL_MOUNT_H
+
+int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
+                          unsigned int *lowernum);
+int ovl_check_mount(bool *mounted);
+
+#endif /* OVL_MOUNT_H */
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..71dbad8
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,12 @@
+fsck.overlay test
+=================
+
+This test cases simulate different inconsistency of overlay filesystem
+underlying directories, test fsck.overlay can repair them correctly or not.
+
+USAGE
+=====
+
+1. Check "local.config", modify config value to appropriate one.
+2. ./auto_test.sh
+3. After testing, the result file and log file created in the result directory.
diff --git a/test/auto_test.sh b/test/auto_test.sh
new file mode 100755
index 0000000..8248e24
--- /dev/null
+++ b/test/auto_test.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+. ./src/prepare.sh
+
+echo "***************"
+
+# 1. Test whiteout
+echo -e "1.Test Whiteout\n"
+$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
+if [ $? -ne 0 ]; then
+	echo -e "Result:\033[31m Fail\033[0m\n"
+	RESULT="Fail"
+else
+	echo -e "Result:\033[32m Pass\033[0m\n"
+	RESULT="Pass"
+fi
+
+echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
+
+# 2. Test opaque dir
+echo -e "2.Test opaque directory\n"
+$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
+if [ $? -ne 0 ]; then
+	echo -e "Result:\033[31m Fail\033[0m\n"
+	RESULT="Fail"
+else
+	echo -e "Result:\033[32m Pass\033[0m\n"
+	RESULT="Pass"
+fi
+
+echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
+
+# 3. Test redirect dir
+echo -e "3.Test redirect direcotry\n"
+$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
+if [ $? -ne 0 ]; then
+	echo -e "Result:\033[31m Fail\033[0m\n"
+	RESULT="Fail"
+else
+	echo -e "Result:\033[32m Pass\033[0m\n"
+	RESULT="Pass"
+fi
+
+echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
+
+echo "***************"
diff --git a/test/clean.sh b/test/clean.sh
new file mode 100755
index 0000000..80f360b
--- /dev/null
+++ b/test/clean.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+. ./src/prepare.sh
+
+rm -rf $RESULT_DIR
+rm -rf $TEST_DIR
diff --git a/test/local.config b/test/local.config
new file mode 100644
index 0000000..990395f
--- /dev/null
+++ b/test/local.config
@@ -0,0 +1,16 @@
+# Config
+export OVL_FSCK=fsck.overlay
+export TEST_DIR=/mnt/test
+
+# Base environment
+export PWD=`pwd`
+export RESULT_DIR=$PWD/result
+export SOURCE_DIR=$PWD/src
+
+# Overlayfs config
+export LOWER_DIR="lower"
+export UPPER_DIR="upper"
+export WORK_DIR="worker"
+export MERGE_DIR="merge"
+export LOWER_DIR1="lower1"
+export WORK_DIR1="worker1"
diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
new file mode 100755
index 0000000..7cb5030
--- /dev/null
+++ b/test/src/opaque_test.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+. ./local.config
+
+__clean()
+{
+	rm -rf $TEST_DIR/*
+	cd $PWD
+}
+
+OVL_OPAQUE_XATTR="trusted.overlay.opaque"
+OVL_OPAQUE_XATTR_VAL="y"
+RET=0
+
+cd $TEST_DIR
+rm -rf *
+mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
+
+# 1.Test lower invalid opaque directory
+echo "***** 1.Test lower invalid opaque directory *****"
+mkdir $LOWER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
+if [[ $? -eq 0 ]];then
+	echo "ERROR: lower's invalid opaque xattr not remove"
+	RET=$(($RET+1))
+fi
+rm -rf $LOWER_DIR/*
+
+# 2.Test upper invalid opaque directory
+echo "***** 2.Test upper invalid opaque directory *****"
+mkdir -p $UPPER_DIR/testdir0/testdir1
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
+if [[ $? -eq 0 ]];then
+	echo "ERROR: upper's invalid opaque xattr not remove"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+
+# 3.Test upper opaque direcotry in merged directory
+echo "***** 3.Test upper opaque direcotry in merged directory *****"
+mkdir $UPPER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+
+# 4.Test upper opaque direcotry cover lower file
+echo "***** 4.Test upper opaque direcotry cover lower file *****"
+mkdir $UPPER_DIR/testdir
+mkdir $LOWER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 5.Test upper opaque direcotry cover lower directory
+echo "***** 5.Test upper opaque direcotry cover lower directory *****"
+mkdir $UPPER_DIR/testdir
+touch $LOWER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 6.Test lower invalid opaque directory in middle layer
+echo "***** 6.Test lower invalid opaque directory in middle layer *****"
+mkdir $LOWER_DIR/testdir0/testdir1
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
+$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
+if [[ $? -eq 0 ]];then
+	echo "ERROR: middle's opaque xattr not removed"
+	RET=$(($RET+1))
+fi
+rm -rf $LOWER_DIR/*
+
+# 7.Test lower invalid opaque directory in bottom layer
+echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
+mkdir $LOWER_DIR1/testdir0/testdir1
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
+$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
+if [[ $? -eq 0 ]];then
+	echo "ERROR: middle's opaque xattr not removed"
+	RET=$(($RET+1))
+fi
+rm -rf $LOWER_DIR1/*
+
+# 8.Test middle opaque direcotry cover bottom directory
+echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
+mkdir $LOWER_DIR1/testdir
+mkdir $LOWER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: middle's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $LOWER_DIR1/*
+rm -rf $LOWER_DIR/*
+
+# 9.Test double opaque direcotry in middle and upper layer
+echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
+mkdir $LOWER_DIR1/testdir
+mkdir $LOWER_DIR/testdir
+mkdir $UPPER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
+setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: middle's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's opaque xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $LOWER_DIR1/*
+rm -rf $LOWER_DIR/*
+rm -rf $UPPER_DIR/*
+
+__clean
+exit $RET
diff --git a/test/src/prepare.sh b/test/src/prepare.sh
new file mode 100755
index 0000000..138539a
--- /dev/null
+++ b/test/src/prepare.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+. ./local.config
+
+NOW=`date +"%Y-%m-%d-%H-%M-%S"`
+LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
+RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
+
+# creat test base dir
+if [ ! -d "$TEST_DIR" ]; then
+	mkdir -p $TEST_DIR
+fi
+
+# creat result dir
+mkdir -p $RESULT_DIR
diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
new file mode 100755
index 0000000..be2ce80
--- /dev/null
+++ b/test/src/redirect_test.sh
@@ -0,0 +1,163 @@
+#!/bin/bash
+
+. ./local.config
+
+__clean()
+{
+	rm -rf $TEST_DIR/*
+	cd $PWD
+}
+
+OVL_REDIRECT_XATTR="trusted.overlay.redirect"
+OVL_OPAQUE_XATTR="trusted.overlay.opaque"
+RET=0
+
+cd $TEST_DIR
+rm -rf *
+mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
+
+# 1.Test no exist redirect origin
+echo "***** 1.Test no exist redirect origin *****"
+mkdir $UPPER_DIR/testdir
+setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
+if [[ $? -eq 0 ]];then
+	echo "ERROR: upper's redirect xattr not remove"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+
+# 2.Test redirect origin is file
+echo "***** 2.Test redirect origin is file *****"
+mkdir $UPPER_DIR/testdir
+touch $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
+if [[ $? -eq 0 ]];then
+	echo "ERROR: upper's redirect xattr not remove"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 3.Test valid redirect xattr
+echo "***** 3.Test valid redirect xattr *****"
+mkdir $UPPER_DIR/testdir
+mknod $UPPER_DIR/origin c 0 0
+mkdir $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 4.Test valid redirect xattr start from root
+echo "***** 4.Test valid redirect xattr start from root *****"
+mkdir -p $UPPER_DIR/testdir0/testdir1
+mknod $UPPER_DIR/origin c 0 0
+mkdir $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 5.Test missing whiteout in redirect parent directory
+echo "***** 5.Test missing whiteout in redirect parent directory *****"
+mkdir $UPPER_DIR/testdir
+mkdir $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+if [ ! -e "$UPPER_DIR/origin" ];then
+	echo "ERROR: upper's missing whiteout not create"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $UPPER_DIR/*
+
+# 6.Test missing opaque in redirect parent directory
+echo "***** 6.Test missing opaque in redirect parent directory *****"
+mkdir -p $UPPER_DIR/testdir0/testdir1
+mkdir $UPPER_DIR/origin
+mkdir $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's missing opaque not set"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 7.Test duplicate redirect directory in one layer
+echo "***** 7.Test duplicate redirect directory in one layer *****"
+mkdir $UPPER_DIR/testdir0
+mkdir $UPPER_DIR/testdir1
+mknod $UPPER_DIR/origin c 0 0
+mkdir $LOWER_DIR/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+if [[ $? -eq 0 ]];then
+	echo "ERROR: fsck check incorrect pass"
+	RET=$(($RET+1))
+fi
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+
+# 8.Test duplicate redirect directory in different layer
+echo "***** 8.Test duplicate redirect directory in different layer *****"
+mkdir $UPPER_DIR/testdir0
+mkdir $LOWER_DIR/testdir1
+mkdir $LOWER_DIR1/origin
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
+setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
+$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
+getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
+if [[ $? -ne 0 ]];then
+	echo "ERROR: upper's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
+if [[ $? -ne 0 ]];then
+	echo "ERROR: lower's redirect xattr incorrect removed"
+	RET=$(($RET+1))
+fi
+
+if [ ! -e "$LOWER_DIR/origin" ];then
+	echo "ERROR: upper's missing whiteout not create"
+	RET=$(($RET+1))
+fi
+if [ ! -e "$LOWER_DIR/origin" ];then
+	echo "ERROR: lower's missing whiteout not create"
+	RET=$(($RET+1))
+fi
+
+rm -rf $UPPER_DIR/*
+rm -rf $LOWER_DIR/*
+rm -rf $LOWER_DIR1/*
+
+__clean
+
+exit $RET
diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
new file mode 100755
index 0000000..c0e1555
--- /dev/null
+++ b/test/src/whiteout_test.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+. ./local.config
+
+__clean()
+{
+	rm -rf $TEST_DIR/*
+	cd $PWD
+}
+
+RET=0
+
+cd $TEST_DIR
+rm -rf *
+mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
+
+# 1.Test lower orphan whiteout
+echo "***** 1.Test lower orphan whiteout *****"
+mknod $LOWER_DIR/foo c 0 0
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+if [ -e "$LOWER_DIR/foo" ];then
+	echo "lower's whiteout not remove"
+	RET=$(($RET+1))
+fi
+rm -f $LOWER_DIR/*
+
+# 2.Test upper orphan whiteout
+echo "***** 2.Test upper orphan whiteout *****"
+mknod $UPPER_DIR/foo c 0 0
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+if [ -e "$UPPER_DIR/foo" ];then
+	echo "upper's whiteout not remove"
+	RET=$(($RET+1))
+fi
+rm -f $UPPER_DIR/*
+
+# 3.Test upper inuse whiteout
+echo "***** 3.Test upper inuse whiteout *****"
+touch $LOWER_DIR/foo
+mknod $UPPER_DIR/foo c 0 0
+$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+if [ ! -e "$UPPER_DIR/foo" ];then
+	echo "upper's whiteout incorrect remove"
+	RET=$(($RET+1))
+fi
+rm -f $UPPER_DIR/*
+rm -f $LOWER_DIR/*
+
+# 4.Test lower inuse whiteout
+echo "***** 4.Test lower inuse whiteout *****"
+touch $LOWER_DIR/foo
+mknod $LOWER_DIR1/foo c 0 0
+$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
+if [ ! -e "$LOWER_DIR1/foo" ];then
+	echo "lower's whiteout incorrect remove"
+	RET=$(($RET+1))
+fi
+rm -f $LOWER_DIR1/*
+rm -f $LOWER_DIR/*
+
+__clean
+
+exit $RET
-- 
2.9.5

^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-17  5:49 [fsck.overlay RFC PATCH] overlay: add fsck utility zhangyi (F)
@ 2017-11-17 17:13 ` Amir Goldstein
  2017-11-17 18:39   ` Darrick J. Wong
                     ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Amir Goldstein @ 2017-11-17 17:13 UTC (permalink / raw)
  To: zhangyi (F); +Cc: overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

[adding fstests in CC with full patch inline to collect wisdom from
other fs developers]

On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
> Hi,
>
> Here is the origin version of fsck.overlay utility I mentioned last
> week. It scan each underlying directory and check orphan whiteout,
> invalid opaque xattr and invalid redirect xattr. We can use it to
> check filesystem inconsistency before mount overlayfs to avoid some
> unexpected results.

Thanks for working on this!
It looks like a great start.

>
> This patch can am to an empty git repository. I have already do basic
> test list in 'test' directory. Please do a review give some suggestions.
> Thanks a lot.

Please consider, instead of writing overlay tests in new repo,
to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
_check_scratch_fs.
See for example very basic index sanity checks I implemented here:
https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd

It may be useful, if Eryu agrees, to merge the "incubator" version of
fsck.overlayfs
into xfstests as a helper program until it starts getting packaged and
distributed.
Please consider this option.

>
> Thanks,
> Yi.
>
> ---------------------------
>
> fsck.overlay
> ============
>
> fsck.overlay is used to check and optionally repair underlying
> directories of overlay-filesystem.
>
> Check the following points:
>
> Whiteouts
> ---------
>
> A whiteout is a character device with 0/0 device number. It is used to
> record the removed files or directories, When a whiteout is found in a
> directory, there should be at least one directory or file with the same
> name in any of the corresponding lower layers. If not exist, the whiteout
> will be treated as orphan whiteout and remove.
>
> Opaque directories
> ------------------
>
> An opaque directory is a directory with "trusted.overlay.opaque" xattr
> valued "y". There are two legal situations of making opaque directory: 1)
> create directory over whiteout 2) creat directory in merged directory. If an
> opaque directory is found, corresponding matching name in lower layers might
> exist or parent directory might merged, If not, the opaque xattr will be
> treated as invalid and remove.
>
> Redirect directories
> --------------------
>
> An redirect directory is a directory with "trusted.overlay.redirect"
> xattr valued to the path of the original location from the root of the
> overlay. It is only used when renaming a directory and "redirect dir"
> feature is enabled. If an redirect directory is found, the following
> must be met:
>
> 1) The directory store in redirect xattr should exist in one of lower
> layers.
> 2) The origin directory should be redirected only once in one layer,
> which mean there is only one redirect xattr point to this origin directory in
> the specified layer.
> 3) A whiteout or an opaque directory with the same name to origin should
> exist in the same directory as the redirect directory.
>
> If not, 1) The redirect xattr is invalid and need to remove 2) One of
> the redirect xattr is redundant but not sure which one is, ask user 3)
> Create a whiteout device or set opaque xattr to an existing directory if the
> parent directory was meregd or remove xattr if not.
>
> Usage
> =====
>
> 1. Ensure overlay filesystem is not mounted based on directories which
> need to check.
>
> 2. Run fsck.overlay program:
>    Usage:
>    fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
>
>    Options:
>    -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
>                              multiple lower use ':' as separator.
>    -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
>    -w, --workdir=WORKDIR     specify work directory of overlayfs
>    -a, --auto                repair automatically (no questions)

Let stick with e2fsck conventions when possible.
-a should be -y and we should definitely have -n, because this is
how xfstest would run it.


>    -v, --verbose             print more messages of overlayfs
>    -h, --help                display this usage of overlayfs
>    -V, --version             display version information
>
> 3. Exit value:
>    0      No errors
>    1      Filesystem errors corrected
>    2      System should be rebooted
>    4      Filesystem errors left uncorrected
>    8      Operational error
>    16     Usage or syntax error
>    32     Checking canceled by user request
>    128    Shared-library error
>
> Todo
> ====
>
> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
> online. Now, We cannot distinguish mounted directories if overlayfs was
> mounted with relative path.

This should be handled by kernel.
We now already grab an advisory exclusive I_OVL_INUSE lock on both
upperdir and workdir.
fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
Read the man page section on O_EXCL and block device. This is how
e2fsck and friends get exclusive access w.r.t mount.

> 2. Symbolic link check.
> 3. Check origin/impure/nlink xattr.
> 4. ...
>
> Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
> ---
>  Makefile                  |  31 +++
>  README.md                 |  88 +++++++++
>  check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
>  check.h                   |   7 +
>  common.c                  |  95 +++++++++
>  common.h                  |  34 ++++
>  config.h                  |  22 +++
>  fsck.c                    | 179 +++++++++++++++++
>  lib.c                     | 197 +++++++++++++++++++
>  lib.h                     |  73 +++++++
>  mount.c                   | 319 ++++++++++++++++++++++++++++++
>  mount.h                   |   8 +
>  test/README.md            |  12 ++
>  test/auto_test.sh         |  46 +++++
>  test/clean.sh             |   6 +
>  test/local.config         |  16 ++
>  test/src/opaque_test.sh   | 144 ++++++++++++++
>  test/src/prepare.sh       |  15 ++
>  test/src/redirect_test.sh | 163 ++++++++++++++++
>  test/src/whiteout_test.sh |  63 ++++++
>  20 files changed, 2000 insertions(+)
>  create mode 100644 Makefile
>  create mode 100644 README.md
>  create mode 100644 check.c
>  create mode 100644 check.h
>  create mode 100644 common.c
>  create mode 100644 common.h
>  create mode 100644 config.h
>  create mode 100644 fsck.c
>  create mode 100644 lib.c
>  create mode 100644 lib.h
>  create mode 100644 mount.c
>  create mode 100644 mount.h
>  create mode 100644 test/README.md
>  create mode 100755 test/auto_test.sh
>  create mode 100755 test/clean.sh
>  create mode 100644 test/local.config
>  create mode 100755 test/src/opaque_test.sh
>  create mode 100755 test/src/prepare.sh
>  create mode 100755 test/src/redirect_test.sh
>  create mode 100755 test/src/whiteout_test.sh
>
> diff --git a/Makefile b/Makefile
> new file mode 100644
> index 0000000..ced5005
> --- /dev/null
> +++ b/Makefile
> @@ -0,0 +1,31 @@
> +CFLAGS = -Wall -g
> +LFLAGS = -lm
> +CC = gcc
> +
> +all: overlay
> +
> +overlay: fsck.o common.o lib.o check.o mount.o
> +       $(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
> +
> +fsck.o:
> +       $(CC) $(CFLAGS) -c fsck.c
> +
> +common.o:
> +       $(CC) $(CFLAGS) -c common.c
> +
> +lib.o:
> +       $(CC) $(CFLAGS) -c lib.c
> +
> +check.o:
> +       $(CC) $(CFLAGS) -c check.c
> +
> +mount.o:
> +       $(CC) $(CFLAGS) -c mount.c
> +
> +clean:
> +       rm -f *.o fsck.overlay
> +       rm -rf bin
> +
> +install: all
> +       mkdir bin
> +       cp fsck.overlay bin
> diff --git a/README.md b/README.md
> new file mode 100644
> index 0000000..8de69cd
> --- /dev/null
> +++ b/README.md
> @@ -0,0 +1,88 @@
> +fsck.overlay
> +============
> +
> +fsck.overlay is used to check and optionally repair underlying directories
> +of overlay-filesystem.
> +
> +Check the following points:
> +
> +Whiteouts
> +---------
> +
> +A whiteout is a character device with 0/0 device number. It is used to record
> +the removed files or directories, When a whiteout is found in a directory,
> +there should be at least one directory or file with the same name in any of the
> +corresponding lower layers. If not exist, the whiteout will be treated as orphan
> +whiteout and remove.
> +
> +
> +Opaque directories
> +------------------
> +
> +An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
> +"y". There are two legal situations of making opaque directory: 1) create
> +directory over whiteout 2) creat directory in merged directory. If an opaque
> +directory is found, corresponding matching name in lower layers might exist or
> +parent directory might merged, If not, the opaque xattr will be treated as
> +invalid and remove.
> +
> +
> +Redirect directories
> +--------------------
> +
> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
> +valued to the path of the original location from the root of the overlay. It
> +is only used when renaming a directory and "redirect dir" feature is enabled.
> +If an redirect directory is found, the following must be met:
> +
> +1) The directory store in redirect xattr should exist in one of lower layers.
> +2) The origin directory should be redirected only once in one layer, which mean
> +   there is only one redirect xattr point to this origin directory in the
> +   specified layer.
> +3) A whiteout or an opaque directory with the same name to origin should exist
> +   in the same directory as the redirect directory.
another redirected upper dir can also be covering the redirect lower target

> +
> +If not, 1) The redirect xattr is invalid and need to remove 2) One of the
> +redirect xattr is redundant but not sure which one is, ask user 3) Create a
> +whiteout device or set opaque xattr to an existing directory if the parent
> +directory was meregd or remove xattr if not.

You can consult origin xattr, decode it and fix redirect accordingly.
I have plans to implement this in kernel as well with 'verify_dir' mount option,
but maybe is it better to do this with fsck tool.
Restoring unset origin xattr, which I also sent a kernel patch for can also be
done by fsck as Miklos also suggested.


> +
> +Usage
> +=====
> +
> +1. Ensure overlay filesystem is not mounted based on directories which need to
> +   check.
> +
> +2. Run fsck.overlay program:
> +   Usage:
> +   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
> +
> +   Options:
> +   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
> +                             multiple lower use ':' as separator.
> +   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
> +   -w, --workdir=WORKDIR     specify work directory of overlayfs
> +   -a, --auto                repair automatically (no questions)
> +   -v, --verbose             print more messages of overlayfs
> +   -h, --help                display this usage of overlayfs
> +   -V, --version             display version information
> +
> +3. Exit value:
> +   0      No errors
> +   1      Filesystem errors corrected
> +   2      System should be rebooted
> +   4      Filesystem errors left uncorrected
> +   8      Operational error
> +   16     Usage or syntax error
> +   32     Checking canceled by user request
> +   128    Shared-library error
> +
> +Todo
> +====
> +
> +1. Overlay filesystem mounted check. Prevent fscking when overlay is
> +   online. Now, We cannot distinguish mounted directories if overlayfs was
> +   mounted with relative path.
> +2. Symbolic link check.
> +3. Check origin/impure/nlink xattr.
> +4. ...
> diff --git a/check.c b/check.c
> new file mode 100644
> index 0000000..1794501
> --- /dev/null
> +++ b/check.c
> @@ -0,0 +1,482 @@
> +/*
> + *
> + *     Check and fix inconsistency for all underlying layers of overlay
> + *
> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR

I guess you are not obliged to, but it would probably be better for
you to assign
copyright either to you or to your employer.
And choosing a license explicitly is also recommended.
IMO it would be best if you could keep at least part of the code
under LGPL (or a like) and start with a mini liboverlay that can grow over time
and be used by other projects.

> + *
> + */
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <unistd.h>
> +#include <stdbool.h>
> +#include <sys/types.h>
> +#include <sys/xattr.h>
> +#include <sys/stat.h>
> +#include <linux/limits.h>
> +
> +#include "common.h"
> +#include "lib.h"
> +#include "check.h"
> +
> +/* Underlying information */
> +struct ovl_lower_check {
> +       unsigned int type;      /* check extent type */
> +
> +       bool exist;
> +       char path[PATH_MAX];    /* exist pathname found, only valid if exist */
> +       struct stat st;         /* only valid if exist */
> +};
> +
> +/* Redirect information */
> +struct ovl_redirect_entry {
> +       struct ovl_redirect_entry *next;
> +
> +       char origin[PATH_MAX];  /* origin directory path */
> +
> +       char path[PATH_MAX];    /* redirect directory */
> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> +       int dirnum;             /* only valid when dirtype==OVL_LOWER */
> +};
> +
> +/* Whiteout */
> +#define WHITEOUT_DEV   0
> +#define WHITEOUT_MOD   0
> +
> +extern char **lowerdir;
> +extern char upperdir[];
> +extern char workdir[];
> +extern unsigned int lower_num;
> +extern int flags;
> +extern int status;
> +
> +static inline mode_t file_type(const struct stat *status)
> +{
> +       return status->st_mode & S_IFMT;
> +}
> +
> +static inline bool is_whiteout(const struct stat *status)
> +{
> +       return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
> +}
> +
> +static inline bool is_dir(const struct stat *status)
> +{
> +       return file_type(status) == S_IFDIR;
> +}
> +
> +static bool is_dir_xattr(const char *pathname, const char *xattrname)
> +{
> +       char val;
> +       ssize_t ret;
> +
> +       ret = getxattr(pathname, xattrname, &val, 1);
> +       if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
> +               print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> +                           xattrname, strerror(errno));
> +               return false;
> +       }
> +
> +       return (ret == 1 && val == 'y') ? true : false;
> +}
> +
> +static inline bool ovl_is_opaque(const char *pathname)
> +{
> +       return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
> +}
> +
> +static inline int ovl_remove_opaque(const char *pathname)
> +{
> +       return remove_xattr(pathname, OVL_OPAQUE_XATTR);
> +}
> +
> +static inline int ovl_set_opaque(const char *pathname)
> +{
> +       return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
> +}
> +
> +static int ovl_get_redirect(const char *pathname, size_t dirlen,
> +                           size_t filelen, char **redirect)
> +{
> +       char *buf = NULL;
> +       ssize_t ret;
> +
> +       ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
> +       if (ret <= 0 || !buf)
> +               return ret;
> +
> +       if (buf[0] != '/') {
> +               size_t baselen = strlen(pathname)-filelen-dirlen;
> +
> +               buf = srealloc(buf, ret + baselen + 1);
> +               memmove(buf + baselen, buf, ret);
> +               memcpy(buf, pathname+dirlen, baselen);
> +               buf[ret + baselen] = '\0';
> +       }
> +
> +       *redirect = buf;
> +       return 0;
> +}
> +
> +static inline int ovl_remove_redirect(const char *pathname)
> +{
> +       return remove_xattr(pathname, OVL_REDIRECT_XATTR);
> +}
> +
> +static inline int ovl_create_whiteout(const char *pathname)
> +{
> +       int ret;
> +
> +       ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
> +       if (ret)
> +               print_err(_("Cannot mknod %s:%s\n"),
> +                           pathname, strerror(errno));
> +       return ret;
> +}
> +
> +/*
> + * Scan each lower dir lower than 'start' and check type matching,
> + * we stop scan if we found something.
> + *
> + * skip: skip whiteout.
> + *
> + */
> +static int ovl_check_lower(const char *path, unsigned int start,
> +                          struct ovl_lower_check *chk, bool skip)
> +{
> +       char lower_path[PATH_MAX];
> +       struct stat st;
> +       unsigned int i;
> +
> +       for (i = start; i < lower_num; i++) {
> +               snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
> +
> +               if (lstat(lower_path, &st) != 0) {

IMO it would be better to keep open fd for lower layers root and use fstatat()
with layer root fd.

> +                       if (errno != ENOENT && errno != ENOTDIR) {
> +                               print_err(_("Cannot stat %s: %s\n"),
> +                                           lower_path, strerror(errno));
> +                               return -1;
> +                       }
> +                       continue;
> +               }
> +
> +               if (skip && is_whiteout(&st))
> +                       continue;
> +
> +               chk->exist = true;
> +               chk->st = st;
> +               strncpy(chk->path, lower_path, sizeof(chk->path));
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +/*
> + * Scan each underlying dirs under specified dir if a whiteout is
> + * found, check it's orphan or not. In auto-mode, orphan whiteouts
> + * will be removed directly.
> + */
> +static int ovl_check_whiteout(struct scan_ctx *sctx)
> +{
> +       const char *pathname = sctx->pathname;
> +       const struct stat *st = sctx->st;
> +       struct ovl_lower_check chk = {0};
> +       unsigned int start;
> +       int ret = 0;
> +
> +       /* Is a whiteout ? */
> +       if (!is_whiteout(st))
> +               return 0;
> +
> +       /* Is Whiteout in the bottom lower dir ? */
> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> +               goto remove;
> +
> +       /*
> +        * Scan each corresponding lower directroy under this layer,
> +        * check is there a file or dir with the same name.
> +        */
> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> +       if (ret)
> +               return ret;
> +       if (chk.exist && !is_whiteout(&chk.st))
> +               goto out;
> +
> +remove:
> +       /* Remove orphan whiteout directly or ask user */
> +       print_info(_("Orphan whiteout: %s "), pathname);
> +       if (!ask_question("Remove", 1))
> +               return 0;
> +
> +       ret = unlink(pathname);
> +       if (ret) {
> +               print_err(_("Cannot unlink %s: %s\n"), pathname,
> +                           strerror(errno));
> +               return ret;
> +       }
> +out:
> +       return ret;
> +}
> +
> +/*
> + * Scan each underlying under specified dir if an opaque dir is found,
> + * check the opaque xattr is invalid or not. In auto-mode, invalid
> + * opaque xattr will be removed directly.
> + * Do the follow checking:
> + * 1) Check lower matching name exist or not.
> + * 2) Check parent dir is merged or pure.
> + * If no lower matching and parent is not merged, remove opaque xattr.
> + */
> +static int ovl_check_opaque(struct scan_ctx *sctx)
> +{
> +       const char *pathname = sctx->pathname;
> +       char parent_path[PATH_MAX];
> +       struct ovl_lower_check chk = {0};
> +       unsigned int start;
> +       int ret = 0;
> +
> +       /* Is opaque ? */
> +       if (!ovl_is_opaque(pathname))
> +               goto out;
> +
> +       /* Opaque dir in last lower dir ? */
> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> +               goto remove;
> +
> +       /*
> +        * Scan each corresponding lower directroy under this layer,
> +        * check if there is a file or dir with the same name.
> +        */
> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> +       if (ret)
> +               return ret;
> +       if (chk.exist && !is_whiteout(&chk.st))
> +               goto out;
> +
> +       /* Check parent directory merged or pure */
> +       memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
> +       ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
> +       if (ret)
> +               return ret;
> +       if (chk.exist && is_dir(&chk.st))
> +               goto out;
> +
> +remove:
> +       /* Remove opaque xattr or ask user */
> +       print_info(_("Invalid opaque xattr: %s "), pathname);
> +       if (!ask_question("Remove", 1))
> +               return 0;
> +
> +       ret = ovl_remove_opaque(pathname);
> +out:
> +       return ret;
> +}
> +
> +static struct ovl_redirect_entry *redirect_list = NULL;
> +
> +static void ovl_redirect_entry_add(const char *path, int dirtype,
> +                                  int dirnum, const char *origin)
> +{
> +       struct ovl_redirect_entry *last, *new;
> +
> +       new = smalloc(sizeof(*new));
> +
> +       print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
> +                     origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
> +                     (dirtype == OVL_UPPER) ? 0 : dirnum);
> +
> +       if (!redirect_list) {
> +               redirect_list = new;
> +       } else {
> +               for (last = redirect_list; last->next; last = last->next);
> +               last->next = new;
> +       }
> +       new->next = NULL;
> +       new->dirtype = dirtype;
> +       new->dirnum = dirnum;
> +       strncpy(new->origin, origin, sizeof(new->origin));
> +       strncpy(new->path, path, sizeof(new->path));
> +}
> +
> +static bool vol_redirect_entry_find(const char *origin, int dirtype,
> +                                   int dirnum, char **founded)
> +{
> +       struct ovl_redirect_entry *entry;
> +
> +       if (!redirect_list)
> +               return false;
> +
> +       for (entry = redirect_list; entry; entry = entry->next) {
> +               bool same_layer;
> +
> +               print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
> +                             entry->origin, entry->path,
> +                             (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
> +                             (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
> +
> +               same_layer = ((entry->dirtype == dirtype) &&
> +                             (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
> +
> +               if (same_layer && !strcmp(entry->origin, origin)) {
> +                       *founded = entry->path;
> +                       return true;
> +               }
> +       }
> +
> +       return false;
> +}
> +
> +static void vol_redirect_free(void)
> +{
> +       struct ovl_redirect_entry *entry;
> +
> +       while (redirect_list) {
> +               entry = redirect_list;
> +               redirect_list = redirect_list->next;
> +               free(entry);
> +       }
> +}
> +
> +/*
> + * Get redirect origin directory stored in the xattr, check it's invlaid
> + * or not, In auto-mode, invalid redirect xattr will be removed directly.
> + * Do the follow checking:
> + * 1) Check the origin directory exist or not. If not, remove xattr.
> + * 2) Count how many directories the origin directory was redirected by.
> + *    If more than one in the same layer, there must be some inconsistency
> + *    but not sure, just warn.
> + * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
> + */
> +static int ovl_check_redirect(struct scan_ctx *sctx)
> +{
> +       const char *pathname = sctx->pathname;
> +       struct ovl_lower_check chk = {0};
> +       char redirect_rpath[PATH_MAX];
> +       struct stat rst;
> +       char *redirect = NULL;
> +       unsigned int start;
> +       int ret = 0;
> +
> +       /* Get redirect */
> +       ret = ovl_get_redirect(pathname, sctx->dirlen,
> +                              sctx->filelen, &redirect);
> +       if (ret || !redirect)
> +               return ret;
> +
> +       print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
> +
> +       /* Redirect dir in last lower dir ? */
> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> +               goto remove;
> +
> +       /* Scan lower directories to check redirect dir exist or not */
> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> +       ret = ovl_check_lower(redirect, start, &chk, false);
> +       if (ret)
> +               goto out;
> +       if (chk.exist && is_dir(&chk.st)) {
> +               char *tmp;
> +
> +               /* Check duplicate in same layer */
> +               if (vol_redirect_entry_find(chk.path, sctx->dirtype,
> +                                           sctx->num, &tmp)) {
> +                       print_info("Duplicate redirect directories found:\n");
> +                       print_info("origin:%s current:%s latest:%s\n",
> +                                  chk.path, pathname, tmp);
> +
> +                       set_st_inconsistency(&status);
> +               }
> +
> +               ovl_redirect_entry_add(pathname, sctx->dirtype,
> +                                      sctx->num, chk.path);
> +
> +               /* Check and fix whiteout or opaque dir */
> +               snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
> +                        sctx->dirname, redirect);
> +               if (lstat(redirect_rpath, &rst) != 0) {
> +                       if (errno != ENOENT && errno != ENOTDIR) {
> +                               print_err(_("Cannot stat %s: %s\n"),
> +                                           redirect_rpath, strerror(errno));
> +                               goto out;
> +                       }
> +
> +                       /* Found nothing, create a whiteout */
> +                       ret = ovl_create_whiteout(redirect_rpath);
> +
> +               } else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
> +                       /* Found a directory but not opaqued, fix opaque xattr */
> +                       ret = ovl_set_opaque(redirect_rpath);
> +               }
> +
> +               goto out;
> +       }
> +
> +remove:
> +       /* Remove redirect xattr or ask user */
> +       print_info(_("Invalid redirect xattr: %s "), pathname);
> +       if (!ask_question("Remove", 1))
> +               goto out;
> +
> +       ret = ovl_remove_redirect(pathname);
> +out:
> +       free(redirect);
> +       return ret;
> +}
> +
> +static struct scan_operations ovl_scan_ops = {
> +       .whiteout = ovl_check_whiteout,
> +       .opaque = ovl_check_opaque,
> +       .redirect = ovl_check_redirect,
> +};
> +
> +static void ovl_scan_clean(void)
> +{
> +       /* Clean redirect entry record */
> +       vol_redirect_free();
> +}
> +
> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> +int ovl_scan_fix(void)
> +{
> +       struct scan_ctx sctx;
> +       unsigned int i;
> +       int ret = 0;
> +
> +       if (flags | FL_VERBOSE)
> +               print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
> +
> +       /* Scan upper directory */
> +       if (flags | FL_VERBOSE)
> +               print_info(_("Scan upper directory: %s\n"), upperdir);
> +
> +       sctx.dirname = upperdir;
> +       sctx.dirlen = strlen(upperdir);
> +       sctx.dirtype = OVL_UPPER;
> +
> +       ret = scan_dir(&sctx, &ovl_scan_ops);
> +       if (ret)
> +               goto out;
> +
> +       /* Scan every lower directories */
> +       for (i = 0; i < lower_num; i++) {
> +               if (flags | FL_VERBOSE)
> +                       print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
> +
> +               sctx.dirname = lowerdir[i];
> +               sctx.dirlen = strlen(lowerdir[i]);
> +               sctx.dirtype = OVL_LOWER;
> +               sctx.num = i;
> +
> +               ret = scan_dir(&sctx, &ovl_scan_ops);
> +               if (ret)
> +                       goto out;
> +       }
> +out:
> +       ovl_scan_clean();
> +       return ret;
> +}
> diff --git a/check.h b/check.h
> new file mode 100644
> index 0000000..373ff3a
> --- /dev/null
> +++ b/check.h
> @@ -0,0 +1,7 @@
> +#ifndef OVL_WHITECHECK_H
> +#define OVL_WHITECHECK_H
> +
> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> +int ovl_scan_fix(void);
> +
> +#endif /* OVL_WHITECHECK_H */
> diff --git a/common.c b/common.c
> new file mode 100644
> index 0000000..4e77045
> --- /dev/null
> +++ b/common.c
> @@ -0,0 +1,95 @@
> +/*
> + *
> + *     Common things for all utilities
> + *
> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> + *
> + */
> +
> +#include <stdio.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <stdarg.h>
> +#include <errno.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include "common.h"
> +#include "config.h"
> +
> +extern char *program_name;
> +
> +/* #define DEBUG 1 */
> +#ifdef DEBUG
> +void print_debug(char *fmtstr, ...)
> +{
> +       va_list args;
> +
> +       va_start(args, fmtstr);
> +       fprintf(stdout, "%s:[Debug]: ", program_name);
> +       vfprintf(stdout, fmtstr, args);
> +       va_end(args);
> +}
> +#else
> +void print_debug (char *fmtstr, ...) {}
> +#endif
> +
> +void print_info(char *fmtstr, ...)
> +{
> +       va_list args;
> +
> +       va_start(args, fmtstr);
> +       vfprintf(stdout, fmtstr, args);
> +       va_end(args);
> +}
> +
> +void print_err(char *fmtstr, ...)
> +{
> +       va_list args;
> +
> +       va_start(args, fmtstr);
> +       fprintf(stderr, "%s:[Error]: ", program_name);
> +       vfprintf(stderr, fmtstr, args);
> +       va_end(args);
> +}
> +
> +void *smalloc(size_t size)
> +{
> +       void *new = malloc(size);
> +
> +       if (!new) {
> +               print_err(_("malloc error:%s\n"), strerror(errno));
> +               exit(1);
> +       }
> +
> +       memset(new, 0, size);
> +       return new;
> +}
> +
> +void *srealloc(void *addr, size_t size)
> +{
> +       void *re = realloc(addr, size);
> +
> +       if (!re) {
> +               print_err(_("malloc error:%s\n"), strerror(errno));
> +               exit(1);
> +       }
> +       return re;
> +}
> +
> +char *sstrdup(const char *src)
> +{
> +       char *dst = strdup(src);
> +
> +       if (!dst) {
> +               print_err(_("strdup error:%s\n"), strerror(errno));
> +               exit(1);
> +       }
> +
> +       return dst;
> +}
> +
> +void version(void)
> +{
> +       printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
> +}
> diff --git a/common.h b/common.h
> new file mode 100644
> index 0000000..c4707e7
> --- /dev/null
> +++ b/common.h
> @@ -0,0 +1,34 @@
> +#ifndef OVL_COMMON_H
> +#define OVL_COMMON_H
> +
> +#ifndef __attribute__
> +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
> +#  define __attribute__(x)
> +# endif
> +#endif
> +
> +#ifdef USE_GETTEXT
> +#include <libintl.h>
> +#define _(x)   gettext((x))
> +#else
> +#define _(x)   (x)
> +#endif
> +
> +/* Print an error message */
> +void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> +
> +/* Print an info message */
> +void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> +
> +/* Print an debug message */
> +void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> +
> +/* Safety wrapper */
> +void *smalloc(size_t size);
> +void *srealloc(void *addr, size_t size);
> +char *sstrdup(const char *src);
> +
> +/* Print program version */
> +void version(void);
> +
> +#endif /* OVL_COMMON_H */
> diff --git a/config.h b/config.h
> new file mode 100644
> index 0000000..deac089
> --- /dev/null
> +++ b/config.h
> @@ -0,0 +1,22 @@
> +#ifndef OVL_CONFIG_H
> +#define OVL_CONFIG_H
> +
> +/* program version */
> +#define PACKAGE_VERSION        "v1.0.0"
> +
> +/* overlay max lower stacks (the same to kernel overlayfs driver) */
> +#define OVL_MAX_STACK 500
> +
> +/* File with mounted filesystems */
> +#define MOUNT_TAB "/proc/mounts"
> +
> +/* Name of overlay filesystem type */
> +#define OVERLAY_NAME "overlay"
> +#define OVERLAY_NAME_OLD "overlayfs"
> +
> +/* Mount options */
> +#define OPT_LOWERDIR "lowerdir="
> +#define OPT_UPPERDIR "upperdir="
> +#define OPT_WORKDIR "workdir="
> +
> +#endif
> diff --git a/fsck.c b/fsck.c
> new file mode 100644
> index 0000000..cbcb8e9
> --- /dev/null
> +++ b/fsck.c
> @@ -0,0 +1,179 @@
> +/*
> + *
> + *     Utility to fsck overlay
> + *
> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> + *
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <getopt.h>
> +#include <libgen.h>
> +#include <stdbool.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <linux/limits.h>
> +
> +#include "common.h"
> +#include "config.h"
> +#include "lib.h"
> +#include "check.h"
> +#include "mount.h"
> +
> +char *program_name;
> +
> +char **lowerdir;
> +char upperdir[PATH_MAX] = {0};
> +char workdir[PATH_MAX] = {0};
> +unsigned int lower_num;
> +int flags;             /* user input option flags */
> +int status;            /* fsck scan status */
> +
> +/* Cleanup lower directories buf */
> +static void ovl_clean_lowerdirs(void)
> +{
> +       unsigned int i;
> +
> +       for (i = 0; i < lower_num; i++) {
> +               free(lowerdir[i]);
> +               lowerdir[i] = NULL;
> +               lower_num = 0;
> +       }
> +       free(lowerdir);
> +       lowerdir = NULL;
> +}
> +
> +static void usage(void)
> +{
> +       print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
> +                   "[-avhV]\n\n"), program_name);
> +       print_info(_("Options:\n"
> +                   "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
> +                   "                          multiple lower use ':' as separator\n"
> +                   "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
> +                   "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
> +                   "-a, --auto                repair automatically (no questions)\n"
> +                   "-v, --verbose             print more messages of overlayfs\n"
> +                   "-h, --help                display this usage of overlayfs\n"
> +                   "-V, --version             display version information\n"));
> +       exit(1);
> +}
> +
> +static void parse_options(int argc, char *argv[])
> +{
> +       char *lowertemp;
> +       int c;
> +       int ret = 0;
> +
> +       struct option long_options[] = {
> +               {"lowerdir", required_argument, NULL, 'l'},
> +               {"upperdir", required_argument, NULL, 'u'},
> +               {"workdir", required_argument, NULL, 'w'},
> +               {"auto", no_argument, NULL, 'a'},
> +               {"verbose", no_argument, NULL, 'v'},
> +               {"version", no_argument, NULL, 'V'},
> +               {"help", no_argument, NULL, 'h'},
> +               {NULL, 0, NULL, 0}
> +       };
> +
> +       while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
> +               switch (c) {
> +               case 'l':
> +                       lowertemp = strdup(optarg);
> +                       ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
> +                       free(lowertemp);
> +                       break;
> +               case 'u':
> +                       if (realpath(optarg, upperdir)) {
> +                               print_debug(_("Upperdir:%s\n"), upperdir);
> +                               flags |= FL_UPPER;
> +                       } else {
> +                               print_err(_("Failed to resolve upperdir:%s\n"), optarg);
> +                               ret = -1;
> +                       }
> +                       break;
> +               case 'w':
> +                       if (realpath(optarg, workdir)) {
> +                               print_debug(_("Workdir:%s\n"), workdir);
> +                               flags |= FL_WORK;
> +                       } else {
> +                               print_err(_("Failed to resolve workdir:%s\n"), optarg);
> +                               ret = -1;
> +                       }
> +                       break;
> +               case 'a':
> +                       flags |= FL_AUTO;
> +                       break;
> +               case 'v':
> +                       flags |= FL_VERBOSE;
> +                       break;
> +               case 'V':
> +                       version();
> +                       return;
> +               case 'h':
> +               default:
> +                       usage();
> +                       return;
> +               }
> +
> +               if (ret)
> +                       exit(1);
> +       }
> +
> +       if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
> +               print_info(_("Please specify correct lowerdirs and upperdir\n"));
> +               usage();
> +       }
> +}
> +
> +void fsck_status_check(int *val)
> +{
> +       if (status & OVL_ST_INCONSISTNECY) {
> +               *val |= FSCK_UNCORRECTED;
> +               print_info(_("Still have unexpected inconsistency!\n"));
> +       }
> +
> +       if (status & OVL_ST_ABORT) {
> +               *val |= FSCK_ERROR;
> +               print_info(_("Cannot continue, aborting\n"));
> +       }
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +       bool mounted;
> +       int err = 0;
> +       int exit_value = 0;
> +
> +       program_name = basename(argv[0]);
> +
> +       parse_options(argc, argv);
> +
> +       /* Ensure overlay filesystem not mounted */
> +       if ((err = ovl_check_mount(&mounted)))
> +               goto out;
> +       if (!mounted) {
> +               set_st_abort(&status);
> +               goto out;
> +       }
> +
> +       /* Scan and fix */
> +       if ((err = ovl_scan_fix()))
> +               goto out;
> +
> +out:
> +       fsck_status_check(&exit_value);
> +       ovl_clean_lowerdirs();
> +
> +       if (err)
> +               exit_value |= FSCK_ERROR;
> +       if (exit_value)
> +               print_info("WARNING: Filesystem check failed, may not clean\n");
> +       else
> +               print_info("Filesystem clean\n");
> +
> +       return exit_value;
> +}
> diff --git a/lib.c b/lib.c
> new file mode 100644
> index 0000000..a6832fe
> --- /dev/null
> +++ b/lib.c
> @@ -0,0 +1,197 @@
> +/*
> + *
> + *     Common things for all utilities
> + *
> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> + *
> + */
> +
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <string.h>
> +#include <stdbool.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/xattr.h>
> +#include <fts.h>
> +
> +#include "common.h"
> +#include "lib.h"
> +
> +extern int flags;
> +extern int status;
> +
> +static int ask_yn(const char *question, int def)
> +{
> +       char ans[16];
> +
> +       print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
> +       fflush(stdout);
> +       while (fgets(ans, sizeof(ans)-1, stdin)) {
> +               if (ans[0] == '\n')
> +                       return def;
> +               else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
> +                       return 1;
> +               else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
> +                       return 0;
> +               else
> +                       print_info(_("Illegal answer. Please input y/n or yes/no:"));
> +               fflush(stdout);
> +       }
> +       return def;
> +}
> +
> +/* Ask user */
> +int ask_question(const char *question, int def)
> +{
> +       if (flags & FL_AUTO) {
> +               print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
> +               return def;
> +       }
> +
> +       return ask_yn(question, def);
> +}
> +
> +ssize_t get_xattr(const char *pathname, const char *xattrname,
> +                 char **value, bool *exist)
> +{
> +       char *buf = NULL;
> +       ssize_t ret;
> +
> +       ret = getxattr(pathname, xattrname, NULL, 0);
> +       if (ret < 0) {
> +               if (errno == ENODATA || errno == ENOTSUP) {
> +                       if (exist)
> +                               *exist = false;
> +                       return 0;
> +               } else {
> +                       goto fail;
> +               }
> +       }
> +
> +       /* Zero size value means xattr exist but value unknown */
> +       if (exist)
> +               *exist = true;
> +       if (ret == 0 || !value)
> +               return 0;
> +
> +       buf = smalloc(ret+1);
> +       ret = getxattr(pathname, xattrname, buf, ret);
> +       if (ret <= 0)
> +               goto fail2;
> +
> +       buf[ret] = '\0';
> +       *value = buf;
> +       return ret;
> +
> +fail2:
> +       free(buf);
> +fail:
> +       print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> +                   xattrname, strerror(errno));
> +       return -1;
> +}
> +
> +int set_xattr(const char *pathname, const char *xattrname,
> +               void *value, size_t size)
> +{
> +       int ret;
> +
> +       ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
> +       if (ret && ret != EEXIST)
> +               goto fail;
> +
> +       if (ret == EEXIST) {
> +               ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
> +               if (ret)
> +                       goto fail;
> +       }
> +
> +       return 0;
> +fail:
> +       print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
> +                   xattrname, strerror(errno));
> +       return ret;
> +}
> +
> +int remove_xattr(const char *pathname, const char *xattrname)
> +{
> +       int ret;
> +       if ((ret = removexattr(pathname, xattrname)))
> +               print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
> +                           xattrname, strerror(errno));
> +       return ret;
> +}
> +
> +static inline int __check_entry(struct scan_ctx *sctx,
> +                                 int (*do_check)(struct scan_ctx *))
> +{
> +       return do_check ? do_check(sctx) : 0;
> +}
> +
> +/* Scan specified directories and invoke callback */
> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
> +{
> +       char *paths[2] = {(char *)sctx->dirname, NULL};
> +       FTS *ftsp;
> +       FTSENT *ftsent;
> +       int ret = 0;
> +
> +       ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
> +       if (ftsp == NULL) {
> +               print_err(_("Failed to fts open %s:%s\n"),
> +                           sctx->dirname, strerror(errno));
> +               return -1;
> +       }
> +
> +       while ((ftsent = fts_read(ftsp)) != NULL) {
> +               int err;
> +
> +               print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
> +                       (ftsent->fts_info == FTS_D) ? "d" :
> +                       (ftsent->fts_info == FTS_DNR) ? "dnr" :
> +                       (ftsent->fts_info == FTS_DP) ? "dp" :
> +                       (ftsent->fts_info == FTS_F) ? "f" :
> +                       (ftsent->fts_info == FTS_NS) ? "ns" :
> +                       (ftsent->fts_info == FTS_SL) ? "sl" :
> +                       (ftsent->fts_info == FTS_SLNONE) ? "sln" :
> +                       (ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
> +                       ftsent->fts_level, ftsent->fts_statp->st_size,
> +                       ftsent->fts_path, ftsent->fts_name);
> +
> +
> +               /* Fillup base context */
> +               sctx->pathname = ftsent->fts_path;
> +               sctx->pathlen = ftsent->fts_pathlen;
> +               sctx->filename = ftsent->fts_name;
> +               sctx->filelen = ftsent->fts_namelen;
> +               sctx->st = ftsent->fts_statp;
> +
> +               switch (ftsent->fts_info) {
> +               case FTS_DEFAULT:
> +                       /* Check whiteouts */
> +                       err = __check_entry(sctx, sop->whiteout);
> +                       ret = (ret || !err) ? ret : err;
> +                       break;
> +               case FTS_D:
> +                       /* Check opaque and redirect */
> +                       err = __check_entry(sctx, sop->opaque);
> +                       ret = (ret || !err) ? ret : err;
> +
> +                       err = __check_entry(sctx, sop->redirect);
> +                       ret = (ret || !err) ? ret : err;
> +                       break;
> +               case FTS_NS:
> +               case FTS_DNR:
> +               case FTS_ERR:
> +                       print_err(_("Failed to fts read %s:%s\n"),
> +                                   ftsent->fts_path, strerror(ftsent->fts_errno));
> +                       goto out;
> +               }
> +       }
> +out:
> +       fts_close(ftsp);
> +       return ret;
> +}
> diff --git a/lib.h b/lib.h
> new file mode 100644
> index 0000000..463b263
> --- /dev/null
> +++ b/lib.h
> @@ -0,0 +1,73 @@
> +#ifndef OVL_LIB_H
> +#define OVL_LIB_H
> +
> +/* Common return value */
> +#define FSCK_OK          0     /* No errors */
> +#define FSCK_NONDESTRUCT 1     /* File system errors corrected */
> +#define FSCK_REBOOT      2     /* System should be rebooted */
> +#define FSCK_UNCORRECTED 4     /* File system errors left uncorrected */
> +#define FSCK_ERROR       8     /* Operational error */
> +#define FSCK_USAGE       16    /* Usage or syntax error */
> +#define FSCK_CANCELED   32     /* Aborted with a signal or ^C */
> +#define FSCK_LIBRARY     128   /* Shared library error */
> +
> +/* Fsck status */
> +#define OVL_ST_INCONSISTNECY   (1 << 0)
> +#define OVL_ST_ABORT           (1 << 1)
> +
> +/* Option flags */
> +#define FL_VERBOSE     (1 << 0)        /* verbose */
> +#define FL_UPPER       (1 << 1)        /* specify upper directory */
> +#define FL_WORK                (1 << 2)        /* specify work directory */
> +#define FL_AUTO                (1 << 3)        /* automactically scan dirs and repair */
> +
> +/* Scan path type */
> +#define OVL_UPPER      0
> +#define OVL_LOWER      1
> +#define OVL_WORKER     2
> +#define OVL_PTYPE_MAX  3
> +
> +/* Xattr */
> +#define OVL_OPAQUE_XATTR       "trusted.overlay.opaque"
> +#define OVL_REDIRECT_XATTR     "trusted.overlay.redirect"
> +
> +/* Directories scan data struct */
> +struct scan_ctx {
> +       const char *dirname;    /* upper/lower root dir */
> +       size_t dirlen;          /* strlen(dirlen) */
> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> +       int num;                /* lower dir depth, lower type use only */
> +
> +       const char *pathname;   /* file path from the root */
> +       size_t pathlen;         /* strlen(pathname) */
> +       const char *filename;   /* filename */
> +       size_t filelen;         /* strlen(filename) */
> +       struct stat *st;        /* file stat */
> +};
> +
> +/* Directories scan callback operations struct */
> +struct scan_operations {
> +       int (*whiteout)(struct scan_ctx *);
> +       int (*opaque)(struct scan_ctx *);
> +       int (*redirect)(struct scan_ctx *);
> +};
> +
> +static inline void set_st_inconsistency(int *st)
> +{
> +       *st |= OVL_ST_INCONSISTNECY;
> +}
> +
> +static inline void set_st_abort(int *st)
> +{
> +       *st |= OVL_ST_ABORT;
> +}
> +
> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
> +int ask_question(const char *question, int def);
> +ssize_t get_xattr(const char *pathname, const char *xattrname,
> +                 char **value, bool *exist);
> +int set_xattr(const char *pathname, const char *xattrname,
> +             void *value, size_t size);
> +int remove_xattr(const char *pathname, const char *xattrname);
> +
> +#endif /* OVL_LIB_H */
> diff --git a/mount.c b/mount.c
> new file mode 100644
> index 0000000..28ce8e5
> --- /dev/null
> +++ b/mount.c
> @@ -0,0 +1,319 @@
> +/*
> + *
> + *     Check mounted overlay
> + *
> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> + *
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <getopt.h>
> +#include <libgen.h>
> +#include <stdbool.h>
> +#include <mntent.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <linux/limits.h>
> +
> +#include "common.h"
> +#include "config.h"
> +#include "lib.h"
> +#include "check.h"
> +#include "mount.h"
> +
> +struct ovl_mnt_entry {
> +       char *lowers;
> +       char **lowerdir;
> +       unsigned int lowernum;
> +       char upperdir[PATH_MAX];
> +       char workdir[PATH_MAX];
> +};
> +
> +/* Mount buf allocate a time */
> +#define ALLOC_NUM      16
> +
> +extern char **lowerdir;
> +extern char upperdir[];
> +extern char workdir[];
> +extern unsigned int lower_num;
> +
> +/*
> + * Split directories to individual one.
> + * (copied from linux kernel, see fs/overlayfs/super.c)
> + */

It would be nice if such functions could be maintained in a file/dir
that is synced with the kernel, like the xfs/lib files.
For the first release we don't really have to sync this file with the
kernel, but at least keep them at a specific file/dir and maybe the
kernel driver will follow up later.

> +static unsigned int ovl_split_lowerdirs(char *lower)
> +{
> +       unsigned int ctr = 1;
> +       char *s, *d;
> +
> +       for (s = d = lower;; s++, d++) {
> +               if (*s == '\\') {
> +                       s++;
> +               } else if (*s == ':') {
> +                       *d = '\0';
> +                       ctr++;
> +                       continue;
> +               }
> +               *d = *s;
> +               if (!*s)
> +                       break;
> +       }
> +       return ctr;
> +}
> +
> +/* Resolve each lower directories and check the validity */
> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> +                         unsigned int *lowernum)
> +{
> +       unsigned int num;
> +       char **dirs;
> +       char *p;
> +       int i;
> +
> +       num = ovl_split_lowerdirs(loweropt);
> +       if (num > OVL_MAX_STACK) {
> +               print_err(_("Too many lower directories:%u, max:%u\n"),
> +                           num, OVL_MAX_STACK);
> +               return -1;
> +       }
> +
> +       dirs = smalloc(sizeof(char *) * num);
> +
> +       p = loweropt;
> +       for (i = 0; i < num; i++) {
> +               dirs[i] = smalloc(PATH_MAX);
> +               if (!realpath(p, dirs[i])) {
> +                       print_err(_("Failed to resolve lowerdir:%s:%s\n"),
> +                                   p, strerror(errno));
> +                       goto err;
> +               }
> +               print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
> +               p = strchr(p, '\0') + 1;
> +       }
> +
> +       *lowerdir = dirs;
> +       *lowernum = num;
> +
> +       return 0;
> +err:
> +       for (i--; i >= 0; i--)
> +               free(dirs[i]);
> +       free(dirs);
> +       *lowernum = 0;
> +       return -1;
> +}
> +
> +/*
> + * Split and return next opt.
> + * (copied from linux kernel, see fs/overlayfs/super.c)
> + */
> +static char *ovl_next_opt(char **s)
> +{
> +       char *sbegin = *s;
> +       char *p;
> +
> +       if (sbegin == NULL)
> +               return NULL;
> +
> +       for (p = sbegin; *p; p++) {
> +               if (*p == '\\') {
> +                       p++;
> +                       if (!*p)
> +                               break;
> +               } else if (*p == ',') {
> +                       *p = '\0';
> +                       *s = p + 1;
> +                       return sbegin;
> +               }
> +       }
> +       *s = NULL;
> +       return sbegin;
> +}
> +
> +/*
> + * Split and parse opt to each directories.
> + *
> + * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
> + * mounted use relative path, so there may have misjudgment.
> + */
> +static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
> +{
> +       char tmp[PATH_MAX] = {0};
> +       char *p;
> +       int len;
> +       int ret;
> +       int i;
> +
> +       while ((p = ovl_next_opt(&opt)) != NULL) {
> +               if (!*p)
> +                       continue;
> +
> +               if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
> +                       len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
> +                       strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
> +
> +                       if (!realpath(tmp, entry->upperdir)) {
> +                               print_err(_("Faile to resolve path:%s:%s\n"),
> +                                           tmp, strerror(errno));
> +                               ret = -1;
> +                               goto errout;
> +                       }
> +               } else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
> +                       len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
> +                       entry->lowers = smalloc(len);
> +                       strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
> +
> +                       if ((ret = ovl_resolve_lowerdirs(entry->lowers,
> +                                       &entry->lowerdir, &entry->lowernum)))
> +                               goto errout;
> +
> +               } else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
> +                       len = strlen(p) - strlen(OPT_WORKDIR) + 1;
> +                       strncpy(tmp, p+strlen(OPT_WORKDIR), len);
> +
> +                       if (!realpath(tmp, entry->workdir)) {
> +                               print_err(_("Faile to resolve path:%s:%s\n"),
> +                                           tmp, strerror(errno));
> +                               ret = -1;
> +                               goto errout;
> +                       }
> +               }
> +       }
> +
> +errout:
> +       if (entry->lowernum) {
> +               for (i = 0; i < entry->lowernum; i++)
> +                       free(entry->lowerdir[i]);
> +               free(entry->lowerdir);
> +               entry->lowerdir = NULL;
> +               entry->lowernum = 0;
> +       }
> +       free(entry->lowers);
> +       entry->lowers = NULL;
> +
> +       return ret;
> +}
> +
> +/* Scan current mounted overlayfs and get used underlying directories */
> +static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
> +                              int *ovl_mnt_count)
> +{
> +       struct ovl_mnt_entry *mnt_entries;
> +       struct mntent *mnt;
> +       FILE *fp;
> +       char *opt;
> +       int allocated, num = 0;
> +
> +       fp = setmntent(MOUNT_TAB, "r");
> +       if (!fp) {
> +               print_err(_("Fail to setmntent %s:%s\n"),
> +                           MOUNT_TAB, strerror(errno));
> +               return -1;
> +       }
> +
> +       allocated = ALLOC_NUM;
> +       mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
> +
> +       while ((mnt = getmntent(fp))) {
> +               if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
> +                   !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
> +
> +                       opt = sstrdup(mnt->mnt_opts);
> +                       if (ovl_parse_opt(opt, &mnt_entries[num])) {
> +                               free(opt);
> +                               continue;
> +                       }
> +
> +                       num++;
> +                       if (num % ALLOC_NUM == 0) {
> +                               allocated += ALLOC_NUM;
> +                               mnt_entries = srealloc(mnt_entries,
> +                                    sizeof(struct ovl_mnt_entry) * allocated);
> +                       }
> +
> +                       free(opt);
> +               }
> +       }
> +
> +       *ovl_mnt_entries = mnt_entries;
> +       *ovl_mnt_count = num;
> +
> +       endmntent(fp);
> +       return 0;
> +}
> +
> +static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
> +                               int ovl_mnt_count)
> +{
> +       int i,j;
> +
> +       for (i = 0; i < ovl_mnt_count; i++) {
> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
> +                       free(ovl_mnt_entries[i].lowerdir[j]);
> +               free(ovl_mnt_entries[i].lowerdir);
> +               free(ovl_mnt_entries[i].lowers);
> +       }
> +       free(ovl_mnt_entries);
> +}
> +
> +/*
> + * Scan every mounted filesystem, check the overlay directories want
> + * to check is already mounted. Check and fix an online overlay is not
> + * allowed.
> + *
> + * Note: fsck may modify lower layers, so even match only one directory
> + *       is triggered as mounted.
> + */
> +int ovl_check_mount(bool *pass)
> +{
> +       struct ovl_mnt_entry *ovl_mnt_entries;
> +       int ovl_mnt_entry_count;
> +       char *mounted_path;
> +       bool mounted;
> +       int i,j,k;
> +       int ret;
> +
> +       ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
> +       if (ret)
> +               return ret;
> +
> +       /* Only check hard matching */
> +       for (i = 0; i < ovl_mnt_entry_count; i++) {
> +               /* Check lower */
> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
> +                       for (k = 0; k < lower_num; k++) {
> +                               if (!strcmp(lowerdir[k],
> +                                           ovl_mnt_entries[i].lowerdir[j])) {
> +                                       mounted_path = lowerdir[k];
> +                                       mounted = true;
> +                                       goto out;
> +                               }
> +                       }
> +               }
> +
> +               /* Check upper */
> +               if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
> +                       mounted_path = upperdir;
> +                       mounted = true;
> +                       goto out;
> +               }
> +
> +               /* Check worker */
> +               if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
> +                       mounted_path = workdir;
> +                       mounted = true;
> +                       goto out;
> +               }
> +       }
> +out:
> +       ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
> +
> +       if (mounted)
> +               print_info(_("Dir %s is mounted\n"), mounted_path);
> +       *pass = !mounted;
> +
> +       return 0;
> +}
> diff --git a/mount.h b/mount.h
> new file mode 100644
> index 0000000..8a3762d
> --- /dev/null
> +++ b/mount.h
> @@ -0,0 +1,8 @@
> +#ifndef OVL_MOUNT_H
> +#define OVL_MOUNT_H
> +
> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> +                          unsigned int *lowernum);
> +int ovl_check_mount(bool *mounted);
> +
> +#endif /* OVL_MOUNT_H */
> diff --git a/test/README.md b/test/README.md
> new file mode 100644
> index 0000000..71dbad8
> --- /dev/null
> +++ b/test/README.md
> @@ -0,0 +1,12 @@
> +fsck.overlay test
> +=================
> +
> +This test cases simulate different inconsistency of overlay filesystem
> +underlying directories, test fsck.overlay can repair them correctly or not.
> +
> +USAGE
> +=====
> +
> +1. Check "local.config", modify config value to appropriate one.
> +2. ./auto_test.sh
> +3. After testing, the result file and log file created in the result directory.

This really sounds like a mini fork of xfstests.
I'd prefer if those tests went into xfstests and used fsck.overlay either as an
external program (like the rest of fsck.*) or as in-house test helper.

> diff --git a/test/auto_test.sh b/test/auto_test.sh
> new file mode 100755
> index 0000000..8248e24
> --- /dev/null
> +++ b/test/auto_test.sh
> @@ -0,0 +1,46 @@
> +#!/bin/bash
> +
> +. ./src/prepare.sh
> +
> +echo "***************"
> +
> +# 1. Test whiteout
> +echo -e "1.Test Whiteout\n"
> +$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
> +if [ $? -ne 0 ]; then
> +       echo -e "Result:\033[31m Fail\033[0m\n"
> +       RESULT="Fail"
> +else
> +       echo -e "Result:\033[32m Pass\033[0m\n"
> +       RESULT="Pass"
> +fi
> +
> +echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
> +
> +# 2. Test opaque dir
> +echo -e "2.Test opaque directory\n"
> +$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
> +if [ $? -ne 0 ]; then
> +       echo -e "Result:\033[31m Fail\033[0m\n"
> +       RESULT="Fail"
> +else
> +       echo -e "Result:\033[32m Pass\033[0m\n"
> +       RESULT="Pass"
> +fi
> +
> +echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
> +
> +# 3. Test redirect dir
> +echo -e "3.Test redirect direcotry\n"
> +$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
> +if [ $? -ne 0 ]; then
> +       echo -e "Result:\033[31m Fail\033[0m\n"
> +       RESULT="Fail"
> +else
> +       echo -e "Result:\033[32m Pass\033[0m\n"
> +       RESULT="Pass"
> +fi
> +
> +echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
> +
> +echo "***************"
> diff --git a/test/clean.sh b/test/clean.sh
> new file mode 100755
> index 0000000..80f360b
> --- /dev/null
> +++ b/test/clean.sh
> @@ -0,0 +1,6 @@
> +#!/bin/bash
> +
> +. ./src/prepare.sh
> +
> +rm -rf $RESULT_DIR
> +rm -rf $TEST_DIR
> diff --git a/test/local.config b/test/local.config
> new file mode 100644
> index 0000000..990395f
> --- /dev/null
> +++ b/test/local.config
> @@ -0,0 +1,16 @@
> +# Config
> +export OVL_FSCK=fsck.overlay
> +export TEST_DIR=/mnt/test
> +
> +# Base environment
> +export PWD=`pwd`
> +export RESULT_DIR=$PWD/result
> +export SOURCE_DIR=$PWD/src
> +
> +# Overlayfs config
> +export LOWER_DIR="lower"
> +export UPPER_DIR="upper"
> +export WORK_DIR="worker"

worker? this is a strange name for work dir location..
it does not have the same meaning in English as upp-er and low-er.

> +export MERGE_DIR="merge"
> +export LOWER_DIR1="lower1"
> +export WORK_DIR1="worker1"
> diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
> new file mode 100755
> index 0000000..7cb5030
> --- /dev/null
> +++ b/test/src/opaque_test.sh
> @@ -0,0 +1,144 @@
> +#!/bin/bash
> +
> +. ./local.config
> +
> +__clean()
> +{
> +       rm -rf $TEST_DIR/*
> +       cd $PWD
> +}
> +
> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> +OVL_OPAQUE_XATTR_VAL="y"
> +RET=0
> +
> +cd $TEST_DIR
> +rm -rf *
> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> +
> +# 1.Test lower invalid opaque directory
> +echo "***** 1.Test lower invalid opaque directory *****"
> +mkdir $LOWER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: lower's invalid opaque xattr not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $LOWER_DIR/*
> +
> +# 2.Test upper invalid opaque directory
> +echo "***** 2.Test upper invalid opaque directory *****"
> +mkdir -p $UPPER_DIR/testdir0/testdir1
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: upper's invalid opaque xattr not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +
> +# 3.Test upper opaque direcotry in merged directory
> +echo "***** 3.Test upper opaque direcotry in merged directory *****"
> +mkdir $UPPER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +
> +# 4.Test upper opaque direcotry cover lower file
> +echo "***** 4.Test upper opaque direcotry cover lower file *****"
> +mkdir $UPPER_DIR/testdir
> +mkdir $LOWER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 5.Test upper opaque direcotry cover lower directory
> +echo "***** 5.Test upper opaque direcotry cover lower directory *****"
> +mkdir $UPPER_DIR/testdir
> +touch $LOWER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 6.Test lower invalid opaque directory in middle layer
> +echo "***** 6.Test lower invalid opaque directory in middle layer *****"
> +mkdir $LOWER_DIR/testdir0/testdir1
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: middle's opaque xattr not removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $LOWER_DIR/*
> +
> +# 7.Test lower invalid opaque directory in bottom layer
> +echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
> +mkdir $LOWER_DIR1/testdir0/testdir1
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: middle's opaque xattr not removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $LOWER_DIR1/*
> +
> +# 8.Test middle opaque direcotry cover bottom directory
> +echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
> +mkdir $LOWER_DIR1/testdir
> +mkdir $LOWER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: middle's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $LOWER_DIR1/*
> +rm -rf $LOWER_DIR/*
> +
> +# 9.Test double opaque direcotry in middle and upper layer
> +echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
> +mkdir $LOWER_DIR1/testdir
> +mkdir $LOWER_DIR/testdir
> +mkdir $UPPER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: middle's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's opaque xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $LOWER_DIR1/*
> +rm -rf $LOWER_DIR/*
> +rm -rf $UPPER_DIR/*
> +
> +__clean
> +exit $RET
> diff --git a/test/src/prepare.sh b/test/src/prepare.sh
> new file mode 100755
> index 0000000..138539a
> --- /dev/null
> +++ b/test/src/prepare.sh
> @@ -0,0 +1,15 @@
> +#!/bin/bash
> +
> +. ./local.config
> +
> +NOW=`date +"%Y-%m-%d-%H-%M-%S"`
> +LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
> +RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
> +
> +# creat test base dir
> +if [ ! -d "$TEST_DIR" ]; then
> +       mkdir -p $TEST_DIR
> +fi
> +
> +# creat result dir
> +mkdir -p $RESULT_DIR
> diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
> new file mode 100755
> index 0000000..be2ce80
> --- /dev/null
> +++ b/test/src/redirect_test.sh
> @@ -0,0 +1,163 @@
> +#!/bin/bash
> +
> +. ./local.config
> +
> +__clean()
> +{
> +       rm -rf $TEST_DIR/*
> +       cd $PWD
> +}
> +
> +OVL_REDIRECT_XATTR="trusted.overlay.redirect"
> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> +RET=0
> +
> +cd $TEST_DIR
> +rm -rf *
> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> +
> +# 1.Test no exist redirect origin
> +echo "***** 1.Test no exist redirect origin *****"
> +mkdir $UPPER_DIR/testdir
> +setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: upper's redirect xattr not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +
> +# 2.Test redirect origin is file
> +echo "***** 2.Test redirect origin is file *****"
> +mkdir $UPPER_DIR/testdir
> +touch $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: upper's redirect xattr not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 3.Test valid redirect xattr
> +echo "***** 3.Test valid redirect xattr *****"
> +mkdir $UPPER_DIR/testdir
> +mknod $UPPER_DIR/origin c 0 0
> +mkdir $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 4.Test valid redirect xattr start from root
> +echo "***** 4.Test valid redirect xattr start from root *****"
> +mkdir -p $UPPER_DIR/testdir0/testdir1
> +mknod $UPPER_DIR/origin c 0 0
> +mkdir $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 5.Test missing whiteout in redirect parent directory
> +echo "***** 5.Test missing whiteout in redirect parent directory *****"
> +mkdir $UPPER_DIR/testdir
> +mkdir $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +if [ ! -e "$UPPER_DIR/origin" ];then
> +       echo "ERROR: upper's missing whiteout not create"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $UPPER_DIR/*
> +
> +# 6.Test missing opaque in redirect parent directory
> +echo "***** 6.Test missing opaque in redirect parent directory *****"
> +mkdir -p $UPPER_DIR/testdir0/testdir1
> +mkdir $UPPER_DIR/origin
> +mkdir $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's missing opaque not set"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 7.Test duplicate redirect directory in one layer
> +echo "***** 7.Test duplicate redirect directory in one layer *****"
> +mkdir $UPPER_DIR/testdir0
> +mkdir $UPPER_DIR/testdir1
> +mknod $UPPER_DIR/origin c 0 0
> +mkdir $LOWER_DIR/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +if [[ $? -eq 0 ]];then
> +       echo "ERROR: fsck check incorrect pass"
> +       RET=$(($RET+1))
> +fi
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +
> +# 8.Test duplicate redirect directory in different layer
> +echo "***** 8.Test duplicate redirect directory in different layer *****"
> +mkdir $UPPER_DIR/testdir0
> +mkdir $LOWER_DIR/testdir1
> +mkdir $LOWER_DIR1/origin
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: upper's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
> +if [[ $? -ne 0 ]];then
> +       echo "ERROR: lower's redirect xattr incorrect removed"
> +       RET=$(($RET+1))
> +fi
> +
> +if [ ! -e "$LOWER_DIR/origin" ];then
> +       echo "ERROR: upper's missing whiteout not create"
> +       RET=$(($RET+1))
> +fi
> +if [ ! -e "$LOWER_DIR/origin" ];then
> +       echo "ERROR: lower's missing whiteout not create"
> +       RET=$(($RET+1))
> +fi
> +
> +rm -rf $UPPER_DIR/*
> +rm -rf $LOWER_DIR/*
> +rm -rf $LOWER_DIR1/*
> +
> +__clean
> +
> +exit $RET
> diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
> new file mode 100755
> index 0000000..c0e1555
> --- /dev/null
> +++ b/test/src/whiteout_test.sh
> @@ -0,0 +1,63 @@
> +#!/bin/bash
> +
> +. ./local.config
> +
> +__clean()
> +{
> +       rm -rf $TEST_DIR/*
> +       cd $PWD
> +}
> +
> +RET=0
> +
> +cd $TEST_DIR
> +rm -rf *
> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> +
> +# 1.Test lower orphan whiteout
> +echo "***** 1.Test lower orphan whiteout *****"
> +mknod $LOWER_DIR/foo c 0 0
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +if [ -e "$LOWER_DIR/foo" ];then
> +       echo "lower's whiteout not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -f $LOWER_DIR/*
> +
> +# 2.Test upper orphan whiteout
> +echo "***** 2.Test upper orphan whiteout *****"
> +mknod $UPPER_DIR/foo c 0 0
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +if [ -e "$UPPER_DIR/foo" ];then
> +       echo "upper's whiteout not remove"
> +       RET=$(($RET+1))
> +fi
> +rm -f $UPPER_DIR/*
> +
> +# 3.Test upper inuse whiteout
> +echo "***** 3.Test upper inuse whiteout *****"
> +touch $LOWER_DIR/foo
> +mknod $UPPER_DIR/foo c 0 0
> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +if [ ! -e "$UPPER_DIR/foo" ];then
> +       echo "upper's whiteout incorrect remove"
> +       RET=$(($RET+1))
> +fi
> +rm -f $UPPER_DIR/*
> +rm -f $LOWER_DIR/*
> +
> +# 4.Test lower inuse whiteout
> +echo "***** 4.Test lower inuse whiteout *****"
> +touch $LOWER_DIR/foo
> +mknod $LOWER_DIR1/foo c 0 0
> +$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> +if [ ! -e "$LOWER_DIR1/foo" ];then
> +       echo "lower's whiteout incorrect remove"
> +       RET=$(($RET+1))
> +fi
> +rm -f $LOWER_DIR1/*
> +rm -f $LOWER_DIR/*
> +
> +__clean
> +
> +exit $RET
> --
> 2.9.5
>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-17 17:13 ` Amir Goldstein
@ 2017-11-17 18:39   ` Darrick J. Wong
  2017-11-20  7:12     ` zhangyi (F)
  2017-11-20  6:56   ` zhangyi (F)
  2018-03-07  9:25   ` yangerkun
  2 siblings, 1 reply; 19+ messages in thread
From: Darrick J. Wong @ 2017-11-17 18:39 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

On Fri, Nov 17, 2017 at 07:13:23PM +0200, Amir Goldstein wrote:
> [adding fstests in CC with full patch inline to collect wisdom from
> other fs developers]
> 
> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
> > Hi,
> >
> > Here is the origin version of fsck.overlay utility I mentioned last
> > week. It scan each underlying directory and check orphan whiteout,
> > invalid opaque xattr and invalid redirect xattr. We can use it to
> > check filesystem inconsistency before mount overlayfs to avoid some
> > unexpected results.
> 
> Thanks for working on this!
> It looks like a great start.
> 
> >
> > This patch can am to an empty git repository. I have already do basic
> > test list in 'test' directory. Please do a review give some suggestions.
> > Thanks a lot.
> 
> Please consider, instead of writing overlay tests in new repo,
> to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
> _check_scratch_fs.
> See for example very basic index sanity checks I implemented here:
> https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
> 
> It may be useful, if Eryu agrees, to merge the "incubator" version of
> fsck.overlayfs
> into xfstests as a helper program until it starts getting packaged and
> distributed.
> Please consider this option.
> 
> >
> > Thanks,
> > Yi.
> >
> > ---------------------------
> >
> > fsck.overlay
> > ============
> >
> > fsck.overlay is used to check and optionally repair underlying
> > directories of overlay-filesystem.
> >
> > Check the following points:
> >
> > Whiteouts
> > ---------
> >
> > A whiteout is a character device with 0/0 device number. It is used to
> > record the removed files or directories, When a whiteout is found in a
> > directory, there should be at least one directory or file with the same
> > name in any of the corresponding lower layers. If not exist, the whiteout
> > will be treated as orphan whiteout and remove.
> >
> > Opaque directories
> > ------------------
> >
> > An opaque directory is a directory with "trusted.overlay.opaque" xattr
> > valued "y". There are two legal situations of making opaque directory: 1)
> > create directory over whiteout 2) creat directory in merged directory. If an
> > opaque directory is found, corresponding matching name in lower layers might
> > exist or parent directory might merged, If not, the opaque xattr will be
> > treated as invalid and remove.
> >
> > Redirect directories
> > --------------------
> >
> > An redirect directory is a directory with "trusted.overlay.redirect"
> > xattr valued to the path of the original location from the root of the
> > overlay. It is only used when renaming a directory and "redirect dir"
> > feature is enabled. If an redirect directory is found, the following
> > must be met:
> >
> > 1) The directory store in redirect xattr should exist in one of lower
> > layers.
> > 2) The origin directory should be redirected only once in one layer,
> > which mean there is only one redirect xattr point to this origin directory in
> > the specified layer.
> > 3) A whiteout or an opaque directory with the same name to origin should
> > exist in the same directory as the redirect directory.
> >
> > If not, 1) The redirect xattr is invalid and need to remove 2) One of
> > the redirect xattr is redundant but not sure which one is, ask user 3)
> > Create a whiteout device or set opaque xattr to an existing directory if the
> > parent directory was meregd or remove xattr if not.
> >
> > Usage
> > =====
> >
> > 1. Ensure overlay filesystem is not mounted based on directories which
> > need to check.
> >
> > 2. Run fsck.overlay program:
> >    Usage:
> >    fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
> >
> >    Options:
> >    -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
> >                              multiple lower use ':' as separator.
> >    -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
> >    -w, --workdir=WORKDIR     specify work directory of overlayfs
> >    -a, --auto                repair automatically (no questions)
> 
> Let stick with e2fsck conventions when possible.
> -a should be -y and we should definitely have -n, because this is
> how xfstest would run it.

If it's e2fsck conventions you want, then consider that e2fsck has three
action options -- -n (fix nothing), -y (fix everything), and -p (fix the
easy stuff that doesn't require interaction); and -a maps to -p.

In other words, think about whether or not repairs to overlayfs could
someday be classified into easy fixes vs. difficult fixes.  Even if that
never happens, if you're going to mirror e2fsck then you might as well
mirror the exact options behavior.

--D

> 
> >    -v, --verbose             print more messages of overlayfs
> >    -h, --help                display this usage of overlayfs
> >    -V, --version             display version information
> >
> > 3. Exit value:
> >    0      No errors
> >    1      Filesystem errors corrected
> >    2      System should be rebooted
> >    4      Filesystem errors left uncorrected
> >    8      Operational error
> >    16     Usage or syntax error
> >    32     Checking canceled by user request
> >    128    Shared-library error
> >
> > Todo
> > ====
> >
> > 1. Overlay filesystem mounted check. Prevent fscking when overlay is
> > online. Now, We cannot distinguish mounted directories if overlayfs was
> > mounted with relative path.
> 
> This should be handled by kernel.
> We now already grab an advisory exclusive I_OVL_INUSE lock on both
> upperdir and workdir.
> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
> Read the man page section on O_EXCL and block device. This is how
> e2fsck and friends get exclusive access w.r.t mount.
> 
> > 2. Symbolic link check.
> > 3. Check origin/impure/nlink xattr.
> > 4. ...
> >
> > Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
> > ---
> >  Makefile                  |  31 +++
> >  README.md                 |  88 +++++++++
> >  check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
> >  check.h                   |   7 +
> >  common.c                  |  95 +++++++++
> >  common.h                  |  34 ++++
> >  config.h                  |  22 +++
> >  fsck.c                    | 179 +++++++++++++++++
> >  lib.c                     | 197 +++++++++++++++++++
> >  lib.h                     |  73 +++++++
> >  mount.c                   | 319 ++++++++++++++++++++++++++++++
> >  mount.h                   |   8 +
> >  test/README.md            |  12 ++
> >  test/auto_test.sh         |  46 +++++
> >  test/clean.sh             |   6 +
> >  test/local.config         |  16 ++
> >  test/src/opaque_test.sh   | 144 ++++++++++++++
> >  test/src/prepare.sh       |  15 ++
> >  test/src/redirect_test.sh | 163 ++++++++++++++++
> >  test/src/whiteout_test.sh |  63 ++++++
> >  20 files changed, 2000 insertions(+)
> >  create mode 100644 Makefile
> >  create mode 100644 README.md
> >  create mode 100644 check.c
> >  create mode 100644 check.h
> >  create mode 100644 common.c
> >  create mode 100644 common.h
> >  create mode 100644 config.h
> >  create mode 100644 fsck.c
> >  create mode 100644 lib.c
> >  create mode 100644 lib.h
> >  create mode 100644 mount.c
> >  create mode 100644 mount.h
> >  create mode 100644 test/README.md
> >  create mode 100755 test/auto_test.sh
> >  create mode 100755 test/clean.sh
> >  create mode 100644 test/local.config
> >  create mode 100755 test/src/opaque_test.sh
> >  create mode 100755 test/src/prepare.sh
> >  create mode 100755 test/src/redirect_test.sh
> >  create mode 100755 test/src/whiteout_test.sh
> >
> > diff --git a/Makefile b/Makefile
> > new file mode 100644
> > index 0000000..ced5005
> > --- /dev/null
> > +++ b/Makefile
> > @@ -0,0 +1,31 @@
> > +CFLAGS = -Wall -g
> > +LFLAGS = -lm
> > +CC = gcc
> > +
> > +all: overlay
> > +
> > +overlay: fsck.o common.o lib.o check.o mount.o
> > +       $(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
> > +
> > +fsck.o:
> > +       $(CC) $(CFLAGS) -c fsck.c
> > +
> > +common.o:
> > +       $(CC) $(CFLAGS) -c common.c
> > +
> > +lib.o:
> > +       $(CC) $(CFLAGS) -c lib.c
> > +
> > +check.o:
> > +       $(CC) $(CFLAGS) -c check.c
> > +
> > +mount.o:
> > +       $(CC) $(CFLAGS) -c mount.c
> > +
> > +clean:
> > +       rm -f *.o fsck.overlay
> > +       rm -rf bin
> > +
> > +install: all
> > +       mkdir bin
> > +       cp fsck.overlay bin
> > diff --git a/README.md b/README.md
> > new file mode 100644
> > index 0000000..8de69cd
> > --- /dev/null
> > +++ b/README.md
> > @@ -0,0 +1,88 @@
> > +fsck.overlay
> > +============
> > +
> > +fsck.overlay is used to check and optionally repair underlying directories
> > +of overlay-filesystem.
> > +
> > +Check the following points:
> > +
> > +Whiteouts
> > +---------
> > +
> > +A whiteout is a character device with 0/0 device number. It is used to record
> > +the removed files or directories, When a whiteout is found in a directory,
> > +there should be at least one directory or file with the same name in any of the
> > +corresponding lower layers. If not exist, the whiteout will be treated as orphan
> > +whiteout and remove.
> > +
> > +
> > +Opaque directories
> > +------------------
> > +
> > +An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
> > +"y". There are two legal situations of making opaque directory: 1) create
> > +directory over whiteout 2) creat directory in merged directory. If an opaque
> > +directory is found, corresponding matching name in lower layers might exist or
> > +parent directory might merged, If not, the opaque xattr will be treated as
> > +invalid and remove.
> > +
> > +
> > +Redirect directories
> > +--------------------
> > +
> > +An redirect directory is a directory with "trusted.overlay.redirect" xattr
> > +valued to the path of the original location from the root of the overlay. It
> > +is only used when renaming a directory and "redirect dir" feature is enabled.
> > +If an redirect directory is found, the following must be met:
> > +
> > +1) The directory store in redirect xattr should exist in one of lower layers.
> > +2) The origin directory should be redirected only once in one layer, which mean
> > +   there is only one redirect xattr point to this origin directory in the
> > +   specified layer.
> > +3) A whiteout or an opaque directory with the same name to origin should exist
> > +   in the same directory as the redirect directory.
> another redirected upper dir can also be covering the redirect lower target
> 
> > +
> > +If not, 1) The redirect xattr is invalid and need to remove 2) One of the
> > +redirect xattr is redundant but not sure which one is, ask user 3) Create a
> > +whiteout device or set opaque xattr to an existing directory if the parent
> > +directory was meregd or remove xattr if not.
> 
> You can consult origin xattr, decode it and fix redirect accordingly.
> I have plans to implement this in kernel as well with 'verify_dir' mount option,
> but maybe is it better to do this with fsck tool.
> Restoring unset origin xattr, which I also sent a kernel patch for can also be
> done by fsck as Miklos also suggested.
> 
> 
> > +
> > +Usage
> > +=====
> > +
> > +1. Ensure overlay filesystem is not mounted based on directories which need to
> > +   check.
> > +
> > +2. Run fsck.overlay program:
> > +   Usage:
> > +   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
> > +
> > +   Options:
> > +   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
> > +                             multiple lower use ':' as separator.
> > +   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
> > +   -w, --workdir=WORKDIR     specify work directory of overlayfs
> > +   -a, --auto                repair automatically (no questions)
> > +   -v, --verbose             print more messages of overlayfs
> > +   -h, --help                display this usage of overlayfs
> > +   -V, --version             display version information
> > +
> > +3. Exit value:
> > +   0      No errors
> > +   1      Filesystem errors corrected
> > +   2      System should be rebooted
> > +   4      Filesystem errors left uncorrected
> > +   8      Operational error
> > +   16     Usage or syntax error
> > +   32     Checking canceled by user request
> > +   128    Shared-library error
> > +
> > +Todo
> > +====
> > +
> > +1. Overlay filesystem mounted check. Prevent fscking when overlay is
> > +   online. Now, We cannot distinguish mounted directories if overlayfs was
> > +   mounted with relative path.
> > +2. Symbolic link check.
> > +3. Check origin/impure/nlink xattr.
> > +4. ...
> > diff --git a/check.c b/check.c
> > new file mode 100644
> > index 0000000..1794501
> > --- /dev/null
> > +++ b/check.c
> > @@ -0,0 +1,482 @@
> > +/*
> > + *
> > + *     Check and fix inconsistency for all underlying layers of overlay
> > + *
> > + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> 
> I guess you are not obliged to, but it would probably be better for
> you to assign
> copyright either to you or to your employer.
> And choosing a license explicitly is also recommended.
> IMO it would be best if you could keep at least part of the code
> under LGPL (or a like) and start with a mini liboverlay that can grow over time
> and be used by other projects.
> 
> > + *
> > + */
> > +
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +#include <string.h>
> > +#include <errno.h>
> > +#include <unistd.h>
> > +#include <stdbool.h>
> > +#include <sys/types.h>
> > +#include <sys/xattr.h>
> > +#include <sys/stat.h>
> > +#include <linux/limits.h>
> > +
> > +#include "common.h"
> > +#include "lib.h"
> > +#include "check.h"
> > +
> > +/* Underlying information */
> > +struct ovl_lower_check {
> > +       unsigned int type;      /* check extent type */
> > +
> > +       bool exist;
> > +       char path[PATH_MAX];    /* exist pathname found, only valid if exist */
> > +       struct stat st;         /* only valid if exist */
> > +};
> > +
> > +/* Redirect information */
> > +struct ovl_redirect_entry {
> > +       struct ovl_redirect_entry *next;
> > +
> > +       char origin[PATH_MAX];  /* origin directory path */
> > +
> > +       char path[PATH_MAX];    /* redirect directory */
> > +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> > +       int dirnum;             /* only valid when dirtype==OVL_LOWER */
> > +};
> > +
> > +/* Whiteout */
> > +#define WHITEOUT_DEV   0
> > +#define WHITEOUT_MOD   0
> > +
> > +extern char **lowerdir;
> > +extern char upperdir[];
> > +extern char workdir[];
> > +extern unsigned int lower_num;
> > +extern int flags;
> > +extern int status;
> > +
> > +static inline mode_t file_type(const struct stat *status)
> > +{
> > +       return status->st_mode & S_IFMT;
> > +}
> > +
> > +static inline bool is_whiteout(const struct stat *status)
> > +{
> > +       return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
> > +}
> > +
> > +static inline bool is_dir(const struct stat *status)
> > +{
> > +       return file_type(status) == S_IFDIR;
> > +}
> > +
> > +static bool is_dir_xattr(const char *pathname, const char *xattrname)
> > +{
> > +       char val;
> > +       ssize_t ret;
> > +
> > +       ret = getxattr(pathname, xattrname, &val, 1);
> > +       if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
> > +               print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> > +                           xattrname, strerror(errno));
> > +               return false;
> > +       }
> > +
> > +       return (ret == 1 && val == 'y') ? true : false;
> > +}
> > +
> > +static inline bool ovl_is_opaque(const char *pathname)
> > +{
> > +       return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
> > +}
> > +
> > +static inline int ovl_remove_opaque(const char *pathname)
> > +{
> > +       return remove_xattr(pathname, OVL_OPAQUE_XATTR);
> > +}
> > +
> > +static inline int ovl_set_opaque(const char *pathname)
> > +{
> > +       return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
> > +}
> > +
> > +static int ovl_get_redirect(const char *pathname, size_t dirlen,
> > +                           size_t filelen, char **redirect)
> > +{
> > +       char *buf = NULL;
> > +       ssize_t ret;
> > +
> > +       ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
> > +       if (ret <= 0 || !buf)
> > +               return ret;
> > +
> > +       if (buf[0] != '/') {
> > +               size_t baselen = strlen(pathname)-filelen-dirlen;
> > +
> > +               buf = srealloc(buf, ret + baselen + 1);
> > +               memmove(buf + baselen, buf, ret);
> > +               memcpy(buf, pathname+dirlen, baselen);
> > +               buf[ret + baselen] = '\0';
> > +       }
> > +
> > +       *redirect = buf;
> > +       return 0;
> > +}
> > +
> > +static inline int ovl_remove_redirect(const char *pathname)
> > +{
> > +       return remove_xattr(pathname, OVL_REDIRECT_XATTR);
> > +}
> > +
> > +static inline int ovl_create_whiteout(const char *pathname)
> > +{
> > +       int ret;
> > +
> > +       ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
> > +       if (ret)
> > +               print_err(_("Cannot mknod %s:%s\n"),
> > +                           pathname, strerror(errno));
> > +       return ret;
> > +}
> > +
> > +/*
> > + * Scan each lower dir lower than 'start' and check type matching,
> > + * we stop scan if we found something.
> > + *
> > + * skip: skip whiteout.
> > + *
> > + */
> > +static int ovl_check_lower(const char *path, unsigned int start,
> > +                          struct ovl_lower_check *chk, bool skip)
> > +{
> > +       char lower_path[PATH_MAX];
> > +       struct stat st;
> > +       unsigned int i;
> > +
> > +       for (i = start; i < lower_num; i++) {
> > +               snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
> > +
> > +               if (lstat(lower_path, &st) != 0) {
> 
> IMO it would be better to keep open fd for lower layers root and use fstatat()
> with layer root fd.
> 
> > +                       if (errno != ENOENT && errno != ENOTDIR) {
> > +                               print_err(_("Cannot stat %s: %s\n"),
> > +                                           lower_path, strerror(errno));
> > +                               return -1;
> > +                       }
> > +                       continue;
> > +               }
> > +
> > +               if (skip && is_whiteout(&st))
> > +                       continue;
> > +
> > +               chk->exist = true;
> > +               chk->st = st;
> > +               strncpy(chk->path, lower_path, sizeof(chk->path));
> > +               break;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +/*
> > + * Scan each underlying dirs under specified dir if a whiteout is
> > + * found, check it's orphan or not. In auto-mode, orphan whiteouts
> > + * will be removed directly.
> > + */
> > +static int ovl_check_whiteout(struct scan_ctx *sctx)
> > +{
> > +       const char *pathname = sctx->pathname;
> > +       const struct stat *st = sctx->st;
> > +       struct ovl_lower_check chk = {0};
> > +       unsigned int start;
> > +       int ret = 0;
> > +
> > +       /* Is a whiteout ? */
> > +       if (!is_whiteout(st))
> > +               return 0;
> > +
> > +       /* Is Whiteout in the bottom lower dir ? */
> > +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> > +               goto remove;
> > +
> > +       /*
> > +        * Scan each corresponding lower directroy under this layer,
> > +        * check is there a file or dir with the same name.
> > +        */
> > +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> > +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> > +       if (ret)
> > +               return ret;
> > +       if (chk.exist && !is_whiteout(&chk.st))
> > +               goto out;
> > +
> > +remove:
> > +       /* Remove orphan whiteout directly or ask user */
> > +       print_info(_("Orphan whiteout: %s "), pathname);
> > +       if (!ask_question("Remove", 1))
> > +               return 0;
> > +
> > +       ret = unlink(pathname);
> > +       if (ret) {
> > +               print_err(_("Cannot unlink %s: %s\n"), pathname,
> > +                           strerror(errno));
> > +               return ret;
> > +       }
> > +out:
> > +       return ret;
> > +}
> > +
> > +/*
> > + * Scan each underlying under specified dir if an opaque dir is found,
> > + * check the opaque xattr is invalid or not. In auto-mode, invalid
> > + * opaque xattr will be removed directly.
> > + * Do the follow checking:
> > + * 1) Check lower matching name exist or not.
> > + * 2) Check parent dir is merged or pure.
> > + * If no lower matching and parent is not merged, remove opaque xattr.
> > + */
> > +static int ovl_check_opaque(struct scan_ctx *sctx)
> > +{
> > +       const char *pathname = sctx->pathname;
> > +       char parent_path[PATH_MAX];
> > +       struct ovl_lower_check chk = {0};
> > +       unsigned int start;
> > +       int ret = 0;
> > +
> > +       /* Is opaque ? */
> > +       if (!ovl_is_opaque(pathname))
> > +               goto out;
> > +
> > +       /* Opaque dir in last lower dir ? */
> > +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> > +               goto remove;
> > +
> > +       /*
> > +        * Scan each corresponding lower directroy under this layer,
> > +        * check if there is a file or dir with the same name.
> > +        */
> > +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> > +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> > +       if (ret)
> > +               return ret;
> > +       if (chk.exist && !is_whiteout(&chk.st))
> > +               goto out;
> > +
> > +       /* Check parent directory merged or pure */
> > +       memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
> > +       ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
> > +       if (ret)
> > +               return ret;
> > +       if (chk.exist && is_dir(&chk.st))
> > +               goto out;
> > +
> > +remove:
> > +       /* Remove opaque xattr or ask user */
> > +       print_info(_("Invalid opaque xattr: %s "), pathname);
> > +       if (!ask_question("Remove", 1))
> > +               return 0;
> > +
> > +       ret = ovl_remove_opaque(pathname);
> > +out:
> > +       return ret;
> > +}
> > +
> > +static struct ovl_redirect_entry *redirect_list = NULL;
> > +
> > +static void ovl_redirect_entry_add(const char *path, int dirtype,
> > +                                  int dirnum, const char *origin)
> > +{
> > +       struct ovl_redirect_entry *last, *new;
> > +
> > +       new = smalloc(sizeof(*new));
> > +
> > +       print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
> > +                     origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
> > +                     (dirtype == OVL_UPPER) ? 0 : dirnum);
> > +
> > +       if (!redirect_list) {
> > +               redirect_list = new;
> > +       } else {
> > +               for (last = redirect_list; last->next; last = last->next);
> > +               last->next = new;
> > +       }
> > +       new->next = NULL;
> > +       new->dirtype = dirtype;
> > +       new->dirnum = dirnum;
> > +       strncpy(new->origin, origin, sizeof(new->origin));
> > +       strncpy(new->path, path, sizeof(new->path));
> > +}
> > +
> > +static bool vol_redirect_entry_find(const char *origin, int dirtype,
> > +                                   int dirnum, char **founded)
> > +{
> > +       struct ovl_redirect_entry *entry;
> > +
> > +       if (!redirect_list)
> > +               return false;
> > +
> > +       for (entry = redirect_list; entry; entry = entry->next) {
> > +               bool same_layer;
> > +
> > +               print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
> > +                             entry->origin, entry->path,
> > +                             (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
> > +                             (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
> > +
> > +               same_layer = ((entry->dirtype == dirtype) &&
> > +                             (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
> > +
> > +               if (same_layer && !strcmp(entry->origin, origin)) {
> > +                       *founded = entry->path;
> > +                       return true;
> > +               }
> > +       }
> > +
> > +       return false;
> > +}
> > +
> > +static void vol_redirect_free(void)
> > +{
> > +       struct ovl_redirect_entry *entry;
> > +
> > +       while (redirect_list) {
> > +               entry = redirect_list;
> > +               redirect_list = redirect_list->next;
> > +               free(entry);
> > +       }
> > +}
> > +
> > +/*
> > + * Get redirect origin directory stored in the xattr, check it's invlaid
> > + * or not, In auto-mode, invalid redirect xattr will be removed directly.
> > + * Do the follow checking:
> > + * 1) Check the origin directory exist or not. If not, remove xattr.
> > + * 2) Count how many directories the origin directory was redirected by.
> > + *    If more than one in the same layer, there must be some inconsistency
> > + *    but not sure, just warn.
> > + * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
> > + */
> > +static int ovl_check_redirect(struct scan_ctx *sctx)
> > +{
> > +       const char *pathname = sctx->pathname;
> > +       struct ovl_lower_check chk = {0};
> > +       char redirect_rpath[PATH_MAX];
> > +       struct stat rst;
> > +       char *redirect = NULL;
> > +       unsigned int start;
> > +       int ret = 0;
> > +
> > +       /* Get redirect */
> > +       ret = ovl_get_redirect(pathname, sctx->dirlen,
> > +                              sctx->filelen, &redirect);
> > +       if (ret || !redirect)
> > +               return ret;
> > +
> > +       print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
> > +
> > +       /* Redirect dir in last lower dir ? */
> > +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> > +               goto remove;
> > +
> > +       /* Scan lower directories to check redirect dir exist or not */
> > +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> > +       ret = ovl_check_lower(redirect, start, &chk, false);
> > +       if (ret)
> > +               goto out;
> > +       if (chk.exist && is_dir(&chk.st)) {
> > +               char *tmp;
> > +
> > +               /* Check duplicate in same layer */
> > +               if (vol_redirect_entry_find(chk.path, sctx->dirtype,
> > +                                           sctx->num, &tmp)) {
> > +                       print_info("Duplicate redirect directories found:\n");
> > +                       print_info("origin:%s current:%s latest:%s\n",
> > +                                  chk.path, pathname, tmp);
> > +
> > +                       set_st_inconsistency(&status);
> > +               }
> > +
> > +               ovl_redirect_entry_add(pathname, sctx->dirtype,
> > +                                      sctx->num, chk.path);
> > +
> > +               /* Check and fix whiteout or opaque dir */
> > +               snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
> > +                        sctx->dirname, redirect);
> > +               if (lstat(redirect_rpath, &rst) != 0) {
> > +                       if (errno != ENOENT && errno != ENOTDIR) {
> > +                               print_err(_("Cannot stat %s: %s\n"),
> > +                                           redirect_rpath, strerror(errno));
> > +                               goto out;
> > +                       }
> > +
> > +                       /* Found nothing, create a whiteout */
> > +                       ret = ovl_create_whiteout(redirect_rpath);
> > +
> > +               } else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
> > +                       /* Found a directory but not opaqued, fix opaque xattr */
> > +                       ret = ovl_set_opaque(redirect_rpath);
> > +               }
> > +
> > +               goto out;
> > +       }
> > +
> > +remove:
> > +       /* Remove redirect xattr or ask user */
> > +       print_info(_("Invalid redirect xattr: %s "), pathname);
> > +       if (!ask_question("Remove", 1))
> > +               goto out;
> > +
> > +       ret = ovl_remove_redirect(pathname);
> > +out:
> > +       free(redirect);
> > +       return ret;
> > +}
> > +
> > +static struct scan_operations ovl_scan_ops = {
> > +       .whiteout = ovl_check_whiteout,
> > +       .opaque = ovl_check_opaque,
> > +       .redirect = ovl_check_redirect,
> > +};
> > +
> > +static void ovl_scan_clean(void)
> > +{
> > +       /* Clean redirect entry record */
> > +       vol_redirect_free();
> > +}
> > +
> > +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> > +int ovl_scan_fix(void)
> > +{
> > +       struct scan_ctx sctx;
> > +       unsigned int i;
> > +       int ret = 0;
> > +
> > +       if (flags | FL_VERBOSE)
> > +               print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
> > +
> > +       /* Scan upper directory */
> > +       if (flags | FL_VERBOSE)
> > +               print_info(_("Scan upper directory: %s\n"), upperdir);
> > +
> > +       sctx.dirname = upperdir;
> > +       sctx.dirlen = strlen(upperdir);
> > +       sctx.dirtype = OVL_UPPER;
> > +
> > +       ret = scan_dir(&sctx, &ovl_scan_ops);
> > +       if (ret)
> > +               goto out;
> > +
> > +       /* Scan every lower directories */
> > +       for (i = 0; i < lower_num; i++) {
> > +               if (flags | FL_VERBOSE)
> > +                       print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
> > +
> > +               sctx.dirname = lowerdir[i];
> > +               sctx.dirlen = strlen(lowerdir[i]);
> > +               sctx.dirtype = OVL_LOWER;
> > +               sctx.num = i;
> > +
> > +               ret = scan_dir(&sctx, &ovl_scan_ops);
> > +               if (ret)
> > +                       goto out;
> > +       }
> > +out:
> > +       ovl_scan_clean();
> > +       return ret;
> > +}
> > diff --git a/check.h b/check.h
> > new file mode 100644
> > index 0000000..373ff3a
> > --- /dev/null
> > +++ b/check.h
> > @@ -0,0 +1,7 @@
> > +#ifndef OVL_WHITECHECK_H
> > +#define OVL_WHITECHECK_H
> > +
> > +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> > +int ovl_scan_fix(void);
> > +
> > +#endif /* OVL_WHITECHECK_H */
> > diff --git a/common.c b/common.c
> > new file mode 100644
> > index 0000000..4e77045
> > --- /dev/null
> > +++ b/common.c
> > @@ -0,0 +1,95 @@
> > +/*
> > + *
> > + *     Common things for all utilities
> > + *
> > + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> > + *
> > + */
> > +
> > +#include <stdio.h>
> > +#include <string.h>
> > +#include <stdlib.h>
> > +#include <unistd.h>
> > +#include <stdarg.h>
> > +#include <errno.h>
> > +#include <sys/types.h>
> > +#include <sys/stat.h>
> > +#include "common.h"
> > +#include "config.h"
> > +
> > +extern char *program_name;
> > +
> > +/* #define DEBUG 1 */
> > +#ifdef DEBUG
> > +void print_debug(char *fmtstr, ...)
> > +{
> > +       va_list args;
> > +
> > +       va_start(args, fmtstr);
> > +       fprintf(stdout, "%s:[Debug]: ", program_name);
> > +       vfprintf(stdout, fmtstr, args);
> > +       va_end(args);
> > +}
> > +#else
> > +void print_debug (char *fmtstr, ...) {}
> > +#endif
> > +
> > +void print_info(char *fmtstr, ...)
> > +{
> > +       va_list args;
> > +
> > +       va_start(args, fmtstr);
> > +       vfprintf(stdout, fmtstr, args);
> > +       va_end(args);
> > +}
> > +
> > +void print_err(char *fmtstr, ...)
> > +{
> > +       va_list args;
> > +
> > +       va_start(args, fmtstr);
> > +       fprintf(stderr, "%s:[Error]: ", program_name);
> > +       vfprintf(stderr, fmtstr, args);
> > +       va_end(args);
> > +}
> > +
> > +void *smalloc(size_t size)
> > +{
> > +       void *new = malloc(size);
> > +
> > +       if (!new) {
> > +               print_err(_("malloc error:%s\n"), strerror(errno));
> > +               exit(1);
> > +       }
> > +
> > +       memset(new, 0, size);
> > +       return new;
> > +}
> > +
> > +void *srealloc(void *addr, size_t size)
> > +{
> > +       void *re = realloc(addr, size);
> > +
> > +       if (!re) {
> > +               print_err(_("malloc error:%s\n"), strerror(errno));
> > +               exit(1);
> > +       }
> > +       return re;
> > +}
> > +
> > +char *sstrdup(const char *src)
> > +{
> > +       char *dst = strdup(src);
> > +
> > +       if (!dst) {
> > +               print_err(_("strdup error:%s\n"), strerror(errno));
> > +               exit(1);
> > +       }
> > +
> > +       return dst;
> > +}
> > +
> > +void version(void)
> > +{
> > +       printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
> > +}
> > diff --git a/common.h b/common.h
> > new file mode 100644
> > index 0000000..c4707e7
> > --- /dev/null
> > +++ b/common.h
> > @@ -0,0 +1,34 @@
> > +#ifndef OVL_COMMON_H
> > +#define OVL_COMMON_H
> > +
> > +#ifndef __attribute__
> > +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
> > +#  define __attribute__(x)
> > +# endif
> > +#endif
> > +
> > +#ifdef USE_GETTEXT
> > +#include <libintl.h>
> > +#define _(x)   gettext((x))
> > +#else
> > +#define _(x)   (x)
> > +#endif
> > +
> > +/* Print an error message */
> > +void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> > +
> > +/* Print an info message */
> > +void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> > +
> > +/* Print an debug message */
> > +void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> > +
> > +/* Safety wrapper */
> > +void *smalloc(size_t size);
> > +void *srealloc(void *addr, size_t size);
> > +char *sstrdup(const char *src);
> > +
> > +/* Print program version */
> > +void version(void);
> > +
> > +#endif /* OVL_COMMON_H */
> > diff --git a/config.h b/config.h
> > new file mode 100644
> > index 0000000..deac089
> > --- /dev/null
> > +++ b/config.h
> > @@ -0,0 +1,22 @@
> > +#ifndef OVL_CONFIG_H
> > +#define OVL_CONFIG_H
> > +
> > +/* program version */
> > +#define PACKAGE_VERSION        "v1.0.0"
> > +
> > +/* overlay max lower stacks (the same to kernel overlayfs driver) */
> > +#define OVL_MAX_STACK 500
> > +
> > +/* File with mounted filesystems */
> > +#define MOUNT_TAB "/proc/mounts"
> > +
> > +/* Name of overlay filesystem type */
> > +#define OVERLAY_NAME "overlay"
> > +#define OVERLAY_NAME_OLD "overlayfs"
> > +
> > +/* Mount options */
> > +#define OPT_LOWERDIR "lowerdir="
> > +#define OPT_UPPERDIR "upperdir="
> > +#define OPT_WORKDIR "workdir="
> > +
> > +#endif
> > diff --git a/fsck.c b/fsck.c
> > new file mode 100644
> > index 0000000..cbcb8e9
> > --- /dev/null
> > +++ b/fsck.c
> > @@ -0,0 +1,179 @@
> > +/*
> > + *
> > + *     Utility to fsck overlay
> > + *
> > + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> > + *
> > + */
> > +
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <errno.h>
> > +#include <getopt.h>
> > +#include <libgen.h>
> > +#include <stdbool.h>
> > +#include <sys/types.h>
> > +#include <sys/stat.h>
> > +#include <linux/limits.h>
> > +
> > +#include "common.h"
> > +#include "config.h"
> > +#include "lib.h"
> > +#include "check.h"
> > +#include "mount.h"
> > +
> > +char *program_name;
> > +
> > +char **lowerdir;
> > +char upperdir[PATH_MAX] = {0};
> > +char workdir[PATH_MAX] = {0};
> > +unsigned int lower_num;
> > +int flags;             /* user input option flags */
> > +int status;            /* fsck scan status */
> > +
> > +/* Cleanup lower directories buf */
> > +static void ovl_clean_lowerdirs(void)
> > +{
> > +       unsigned int i;
> > +
> > +       for (i = 0; i < lower_num; i++) {
> > +               free(lowerdir[i]);
> > +               lowerdir[i] = NULL;
> > +               lower_num = 0;
> > +       }
> > +       free(lowerdir);
> > +       lowerdir = NULL;
> > +}
> > +
> > +static void usage(void)
> > +{
> > +       print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
> > +                   "[-avhV]\n\n"), program_name);
> > +       print_info(_("Options:\n"
> > +                   "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
> > +                   "                          multiple lower use ':' as separator\n"
> > +                   "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
> > +                   "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
> > +                   "-a, --auto                repair automatically (no questions)\n"
> > +                   "-v, --verbose             print more messages of overlayfs\n"
> > +                   "-h, --help                display this usage of overlayfs\n"
> > +                   "-V, --version             display version information\n"));
> > +       exit(1);
> > +}
> > +
> > +static void parse_options(int argc, char *argv[])
> > +{
> > +       char *lowertemp;
> > +       int c;
> > +       int ret = 0;
> > +
> > +       struct option long_options[] = {
> > +               {"lowerdir", required_argument, NULL, 'l'},
> > +               {"upperdir", required_argument, NULL, 'u'},
> > +               {"workdir", required_argument, NULL, 'w'},
> > +               {"auto", no_argument, NULL, 'a'},
> > +               {"verbose", no_argument, NULL, 'v'},
> > +               {"version", no_argument, NULL, 'V'},
> > +               {"help", no_argument, NULL, 'h'},
> > +               {NULL, 0, NULL, 0}
> > +       };
> > +
> > +       while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
> > +               switch (c) {
> > +               case 'l':
> > +                       lowertemp = strdup(optarg);
> > +                       ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
> > +                       free(lowertemp);
> > +                       break;
> > +               case 'u':
> > +                       if (realpath(optarg, upperdir)) {
> > +                               print_debug(_("Upperdir:%s\n"), upperdir);
> > +                               flags |= FL_UPPER;
> > +                       } else {
> > +                               print_err(_("Failed to resolve upperdir:%s\n"), optarg);
> > +                               ret = -1;
> > +                       }
> > +                       break;
> > +               case 'w':
> > +                       if (realpath(optarg, workdir)) {
> > +                               print_debug(_("Workdir:%s\n"), workdir);
> > +                               flags |= FL_WORK;
> > +                       } else {
> > +                               print_err(_("Failed to resolve workdir:%s\n"), optarg);
> > +                               ret = -1;
> > +                       }
> > +                       break;
> > +               case 'a':
> > +                       flags |= FL_AUTO;
> > +                       break;
> > +               case 'v':
> > +                       flags |= FL_VERBOSE;
> > +                       break;
> > +               case 'V':
> > +                       version();
> > +                       return;
> > +               case 'h':
> > +               default:
> > +                       usage();
> > +                       return;
> > +               }
> > +
> > +               if (ret)
> > +                       exit(1);
> > +       }
> > +
> > +       if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
> > +               print_info(_("Please specify correct lowerdirs and upperdir\n"));
> > +               usage();
> > +       }
> > +}
> > +
> > +void fsck_status_check(int *val)
> > +{
> > +       if (status & OVL_ST_INCONSISTNECY) {
> > +               *val |= FSCK_UNCORRECTED;
> > +               print_info(_("Still have unexpected inconsistency!\n"));
> > +       }
> > +
> > +       if (status & OVL_ST_ABORT) {
> > +               *val |= FSCK_ERROR;
> > +               print_info(_("Cannot continue, aborting\n"));
> > +       }
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +       bool mounted;
> > +       int err = 0;
> > +       int exit_value = 0;
> > +
> > +       program_name = basename(argv[0]);
> > +
> > +       parse_options(argc, argv);
> > +
> > +       /* Ensure overlay filesystem not mounted */
> > +       if ((err = ovl_check_mount(&mounted)))
> > +               goto out;
> > +       if (!mounted) {
> > +               set_st_abort(&status);
> > +               goto out;
> > +       }
> > +
> > +       /* Scan and fix */
> > +       if ((err = ovl_scan_fix()))
> > +               goto out;
> > +
> > +out:
> > +       fsck_status_check(&exit_value);
> > +       ovl_clean_lowerdirs();
> > +
> > +       if (err)
> > +               exit_value |= FSCK_ERROR;
> > +       if (exit_value)
> > +               print_info("WARNING: Filesystem check failed, may not clean\n");
> > +       else
> > +               print_info("Filesystem clean\n");
> > +
> > +       return exit_value;
> > +}
> > diff --git a/lib.c b/lib.c
> > new file mode 100644
> > index 0000000..a6832fe
> > --- /dev/null
> > +++ b/lib.c
> > @@ -0,0 +1,197 @@
> > +/*
> > + *
> > + *     Common things for all utilities
> > + *
> > + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> > + *
> > + */
> > +
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +#include <unistd.h>
> > +#include <errno.h>
> > +#include <string.h>
> > +#include <stdbool.h>
> > +#include <sys/types.h>
> > +#include <sys/stat.h>
> > +#include <sys/xattr.h>
> > +#include <fts.h>
> > +
> > +#include "common.h"
> > +#include "lib.h"
> > +
> > +extern int flags;
> > +extern int status;
> > +
> > +static int ask_yn(const char *question, int def)
> > +{
> > +       char ans[16];
> > +
> > +       print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
> > +       fflush(stdout);
> > +       while (fgets(ans, sizeof(ans)-1, stdin)) {
> > +               if (ans[0] == '\n')
> > +                       return def;
> > +               else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
> > +                       return 1;
> > +               else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
> > +                       return 0;
> > +               else
> > +                       print_info(_("Illegal answer. Please input y/n or yes/no:"));
> > +               fflush(stdout);
> > +       }
> > +       return def;
> > +}
> > +
> > +/* Ask user */
> > +int ask_question(const char *question, int def)
> > +{
> > +       if (flags & FL_AUTO) {
> > +               print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
> > +               return def;
> > +       }
> > +
> > +       return ask_yn(question, def);
> > +}
> > +
> > +ssize_t get_xattr(const char *pathname, const char *xattrname,
> > +                 char **value, bool *exist)
> > +{
> > +       char *buf = NULL;
> > +       ssize_t ret;
> > +
> > +       ret = getxattr(pathname, xattrname, NULL, 0);
> > +       if (ret < 0) {
> > +               if (errno == ENODATA || errno == ENOTSUP) {
> > +                       if (exist)
> > +                               *exist = false;
> > +                       return 0;
> > +               } else {
> > +                       goto fail;
> > +               }
> > +       }
> > +
> > +       /* Zero size value means xattr exist but value unknown */
> > +       if (exist)
> > +               *exist = true;
> > +       if (ret == 0 || !value)
> > +               return 0;
> > +
> > +       buf = smalloc(ret+1);
> > +       ret = getxattr(pathname, xattrname, buf, ret);
> > +       if (ret <= 0)
> > +               goto fail2;
> > +
> > +       buf[ret] = '\0';
> > +       *value = buf;
> > +       return ret;
> > +
> > +fail2:
> > +       free(buf);
> > +fail:
> > +       print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> > +                   xattrname, strerror(errno));
> > +       return -1;
> > +}
> > +
> > +int set_xattr(const char *pathname, const char *xattrname,
> > +               void *value, size_t size)
> > +{
> > +       int ret;
> > +
> > +       ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
> > +       if (ret && ret != EEXIST)
> > +               goto fail;
> > +
> > +       if (ret == EEXIST) {
> > +               ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
> > +               if (ret)
> > +                       goto fail;
> > +       }
> > +
> > +       return 0;
> > +fail:
> > +       print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
> > +                   xattrname, strerror(errno));
> > +       return ret;
> > +}
> > +
> > +int remove_xattr(const char *pathname, const char *xattrname)
> > +{
> > +       int ret;
> > +       if ((ret = removexattr(pathname, xattrname)))
> > +               print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
> > +                           xattrname, strerror(errno));
> > +       return ret;
> > +}
> > +
> > +static inline int __check_entry(struct scan_ctx *sctx,
> > +                                 int (*do_check)(struct scan_ctx *))
> > +{
> > +       return do_check ? do_check(sctx) : 0;
> > +}
> > +
> > +/* Scan specified directories and invoke callback */
> > +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
> > +{
> > +       char *paths[2] = {(char *)sctx->dirname, NULL};
> > +       FTS *ftsp;
> > +       FTSENT *ftsent;
> > +       int ret = 0;
> > +
> > +       ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
> > +       if (ftsp == NULL) {
> > +               print_err(_("Failed to fts open %s:%s\n"),
> > +                           sctx->dirname, strerror(errno));
> > +               return -1;
> > +       }
> > +
> > +       while ((ftsent = fts_read(ftsp)) != NULL) {
> > +               int err;
> > +
> > +               print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
> > +                       (ftsent->fts_info == FTS_D) ? "d" :
> > +                       (ftsent->fts_info == FTS_DNR) ? "dnr" :
> > +                       (ftsent->fts_info == FTS_DP) ? "dp" :
> > +                       (ftsent->fts_info == FTS_F) ? "f" :
> > +                       (ftsent->fts_info == FTS_NS) ? "ns" :
> > +                       (ftsent->fts_info == FTS_SL) ? "sl" :
> > +                       (ftsent->fts_info == FTS_SLNONE) ? "sln" :
> > +                       (ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
> > +                       ftsent->fts_level, ftsent->fts_statp->st_size,
> > +                       ftsent->fts_path, ftsent->fts_name);
> > +
> > +
> > +               /* Fillup base context */
> > +               sctx->pathname = ftsent->fts_path;
> > +               sctx->pathlen = ftsent->fts_pathlen;
> > +               sctx->filename = ftsent->fts_name;
> > +               sctx->filelen = ftsent->fts_namelen;
> > +               sctx->st = ftsent->fts_statp;
> > +
> > +               switch (ftsent->fts_info) {
> > +               case FTS_DEFAULT:
> > +                       /* Check whiteouts */
> > +                       err = __check_entry(sctx, sop->whiteout);
> > +                       ret = (ret || !err) ? ret : err;
> > +                       break;
> > +               case FTS_D:
> > +                       /* Check opaque and redirect */
> > +                       err = __check_entry(sctx, sop->opaque);
> > +                       ret = (ret || !err) ? ret : err;
> > +
> > +                       err = __check_entry(sctx, sop->redirect);
> > +                       ret = (ret || !err) ? ret : err;
> > +                       break;
> > +               case FTS_NS:
> > +               case FTS_DNR:
> > +               case FTS_ERR:
> > +                       print_err(_("Failed to fts read %s:%s\n"),
> > +                                   ftsent->fts_path, strerror(ftsent->fts_errno));
> > +                       goto out;
> > +               }
> > +       }
> > +out:
> > +       fts_close(ftsp);
> > +       return ret;
> > +}
> > diff --git a/lib.h b/lib.h
> > new file mode 100644
> > index 0000000..463b263
> > --- /dev/null
> > +++ b/lib.h
> > @@ -0,0 +1,73 @@
> > +#ifndef OVL_LIB_H
> > +#define OVL_LIB_H
> > +
> > +/* Common return value */
> > +#define FSCK_OK          0     /* No errors */
> > +#define FSCK_NONDESTRUCT 1     /* File system errors corrected */
> > +#define FSCK_REBOOT      2     /* System should be rebooted */
> > +#define FSCK_UNCORRECTED 4     /* File system errors left uncorrected */
> > +#define FSCK_ERROR       8     /* Operational error */
> > +#define FSCK_USAGE       16    /* Usage or syntax error */
> > +#define FSCK_CANCELED   32     /* Aborted with a signal or ^C */
> > +#define FSCK_LIBRARY     128   /* Shared library error */
> > +
> > +/* Fsck status */
> > +#define OVL_ST_INCONSISTNECY   (1 << 0)
> > +#define OVL_ST_ABORT           (1 << 1)
> > +
> > +/* Option flags */
> > +#define FL_VERBOSE     (1 << 0)        /* verbose */
> > +#define FL_UPPER       (1 << 1)        /* specify upper directory */
> > +#define FL_WORK                (1 << 2)        /* specify work directory */
> > +#define FL_AUTO                (1 << 3)        /* automactically scan dirs and repair */
> > +
> > +/* Scan path type */
> > +#define OVL_UPPER      0
> > +#define OVL_LOWER      1
> > +#define OVL_WORKER     2
> > +#define OVL_PTYPE_MAX  3
> > +
> > +/* Xattr */
> > +#define OVL_OPAQUE_XATTR       "trusted.overlay.opaque"
> > +#define OVL_REDIRECT_XATTR     "trusted.overlay.redirect"
> > +
> > +/* Directories scan data struct */
> > +struct scan_ctx {
> > +       const char *dirname;    /* upper/lower root dir */
> > +       size_t dirlen;          /* strlen(dirlen) */
> > +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> > +       int num;                /* lower dir depth, lower type use only */
> > +
> > +       const char *pathname;   /* file path from the root */
> > +       size_t pathlen;         /* strlen(pathname) */
> > +       const char *filename;   /* filename */
> > +       size_t filelen;         /* strlen(filename) */
> > +       struct stat *st;        /* file stat */
> > +};
> > +
> > +/* Directories scan callback operations struct */
> > +struct scan_operations {
> > +       int (*whiteout)(struct scan_ctx *);
> > +       int (*opaque)(struct scan_ctx *);
> > +       int (*redirect)(struct scan_ctx *);
> > +};
> > +
> > +static inline void set_st_inconsistency(int *st)
> > +{
> > +       *st |= OVL_ST_INCONSISTNECY;
> > +}
> > +
> > +static inline void set_st_abort(int *st)
> > +{
> > +       *st |= OVL_ST_ABORT;
> > +}
> > +
> > +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
> > +int ask_question(const char *question, int def);
> > +ssize_t get_xattr(const char *pathname, const char *xattrname,
> > +                 char **value, bool *exist);
> > +int set_xattr(const char *pathname, const char *xattrname,
> > +             void *value, size_t size);
> > +int remove_xattr(const char *pathname, const char *xattrname);
> > +
> > +#endif /* OVL_LIB_H */
> > diff --git a/mount.c b/mount.c
> > new file mode 100644
> > index 0000000..28ce8e5
> > --- /dev/null
> > +++ b/mount.c
> > @@ -0,0 +1,319 @@
> > +/*
> > + *
> > + *     Check mounted overlay
> > + *
> > + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> > + *
> > + */
> > +
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <errno.h>
> > +#include <getopt.h>
> > +#include <libgen.h>
> > +#include <stdbool.h>
> > +#include <mntent.h>
> > +#include <sys/types.h>
> > +#include <sys/stat.h>
> > +#include <linux/limits.h>
> > +
> > +#include "common.h"
> > +#include "config.h"
> > +#include "lib.h"
> > +#include "check.h"
> > +#include "mount.h"
> > +
> > +struct ovl_mnt_entry {
> > +       char *lowers;
> > +       char **lowerdir;
> > +       unsigned int lowernum;
> > +       char upperdir[PATH_MAX];
> > +       char workdir[PATH_MAX];
> > +};
> > +
> > +/* Mount buf allocate a time */
> > +#define ALLOC_NUM      16
> > +
> > +extern char **lowerdir;
> > +extern char upperdir[];
> > +extern char workdir[];
> > +extern unsigned int lower_num;
> > +
> > +/*
> > + * Split directories to individual one.
> > + * (copied from linux kernel, see fs/overlayfs/super.c)
> > + */
> 
> It would be nice if such functions could be maintained in a file/dir
> that is synced with the kernel, like the xfs/lib files.
> For the first release we don't really have to sync this file with the
> kernel, but at least keep them at a specific file/dir and maybe the
> kernel driver will follow up later.
> 
> > +static unsigned int ovl_split_lowerdirs(char *lower)
> > +{
> > +       unsigned int ctr = 1;
> > +       char *s, *d;
> > +
> > +       for (s = d = lower;; s++, d++) {
> > +               if (*s == '\\') {
> > +                       s++;
> > +               } else if (*s == ':') {
> > +                       *d = '\0';
> > +                       ctr++;
> > +                       continue;
> > +               }
> > +               *d = *s;
> > +               if (!*s)
> > +                       break;
> > +       }
> > +       return ctr;
> > +}
> > +
> > +/* Resolve each lower directories and check the validity */
> > +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> > +                         unsigned int *lowernum)
> > +{
> > +       unsigned int num;
> > +       char **dirs;
> > +       char *p;
> > +       int i;
> > +
> > +       num = ovl_split_lowerdirs(loweropt);
> > +       if (num > OVL_MAX_STACK) {
> > +               print_err(_("Too many lower directories:%u, max:%u\n"),
> > +                           num, OVL_MAX_STACK);
> > +               return -1;
> > +       }
> > +
> > +       dirs = smalloc(sizeof(char *) * num);
> > +
> > +       p = loweropt;
> > +       for (i = 0; i < num; i++) {
> > +               dirs[i] = smalloc(PATH_MAX);
> > +               if (!realpath(p, dirs[i])) {
> > +                       print_err(_("Failed to resolve lowerdir:%s:%s\n"),
> > +                                   p, strerror(errno));
> > +                       goto err;
> > +               }
> > +               print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
> > +               p = strchr(p, '\0') + 1;
> > +       }
> > +
> > +       *lowerdir = dirs;
> > +       *lowernum = num;
> > +
> > +       return 0;
> > +err:
> > +       for (i--; i >= 0; i--)
> > +               free(dirs[i]);
> > +       free(dirs);
> > +       *lowernum = 0;
> > +       return -1;
> > +}
> > +
> > +/*
> > + * Split and return next opt.
> > + * (copied from linux kernel, see fs/overlayfs/super.c)
> > + */
> > +static char *ovl_next_opt(char **s)
> > +{
> > +       char *sbegin = *s;
> > +       char *p;
> > +
> > +       if (sbegin == NULL)
> > +               return NULL;
> > +
> > +       for (p = sbegin; *p; p++) {
> > +               if (*p == '\\') {
> > +                       p++;
> > +                       if (!*p)
> > +                               break;
> > +               } else if (*p == ',') {
> > +                       *p = '\0';
> > +                       *s = p + 1;
> > +                       return sbegin;
> > +               }
> > +       }
> > +       *s = NULL;
> > +       return sbegin;
> > +}
> > +
> > +/*
> > + * Split and parse opt to each directories.
> > + *
> > + * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
> > + * mounted use relative path, so there may have misjudgment.
> > + */
> > +static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
> > +{
> > +       char tmp[PATH_MAX] = {0};
> > +       char *p;
> > +       int len;
> > +       int ret;
> > +       int i;
> > +
> > +       while ((p = ovl_next_opt(&opt)) != NULL) {
> > +               if (!*p)
> > +                       continue;
> > +
> > +               if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
> > +                       len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
> > +                       strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
> > +
> > +                       if (!realpath(tmp, entry->upperdir)) {
> > +                               print_err(_("Faile to resolve path:%s:%s\n"),
> > +                                           tmp, strerror(errno));
> > +                               ret = -1;
> > +                               goto errout;
> > +                       }
> > +               } else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
> > +                       len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
> > +                       entry->lowers = smalloc(len);
> > +                       strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
> > +
> > +                       if ((ret = ovl_resolve_lowerdirs(entry->lowers,
> > +                                       &entry->lowerdir, &entry->lowernum)))
> > +                               goto errout;
> > +
> > +               } else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
> > +                       len = strlen(p) - strlen(OPT_WORKDIR) + 1;
> > +                       strncpy(tmp, p+strlen(OPT_WORKDIR), len);
> > +
> > +                       if (!realpath(tmp, entry->workdir)) {
> > +                               print_err(_("Faile to resolve path:%s:%s\n"),
> > +                                           tmp, strerror(errno));
> > +                               ret = -1;
> > +                               goto errout;
> > +                       }
> > +               }
> > +       }
> > +
> > +errout:
> > +       if (entry->lowernum) {
> > +               for (i = 0; i < entry->lowernum; i++)
> > +                       free(entry->lowerdir[i]);
> > +               free(entry->lowerdir);
> > +               entry->lowerdir = NULL;
> > +               entry->lowernum = 0;
> > +       }
> > +       free(entry->lowers);
> > +       entry->lowers = NULL;
> > +
> > +       return ret;
> > +}
> > +
> > +/* Scan current mounted overlayfs and get used underlying directories */
> > +static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
> > +                              int *ovl_mnt_count)
> > +{
> > +       struct ovl_mnt_entry *mnt_entries;
> > +       struct mntent *mnt;
> > +       FILE *fp;
> > +       char *opt;
> > +       int allocated, num = 0;
> > +
> > +       fp = setmntent(MOUNT_TAB, "r");
> > +       if (!fp) {
> > +               print_err(_("Fail to setmntent %s:%s\n"),
> > +                           MOUNT_TAB, strerror(errno));
> > +               return -1;
> > +       }
> > +
> > +       allocated = ALLOC_NUM;
> > +       mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
> > +
> > +       while ((mnt = getmntent(fp))) {
> > +               if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
> > +                   !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
> > +
> > +                       opt = sstrdup(mnt->mnt_opts);
> > +                       if (ovl_parse_opt(opt, &mnt_entries[num])) {
> > +                               free(opt);
> > +                               continue;
> > +                       }
> > +
> > +                       num++;
> > +                       if (num % ALLOC_NUM == 0) {
> > +                               allocated += ALLOC_NUM;
> > +                               mnt_entries = srealloc(mnt_entries,
> > +                                    sizeof(struct ovl_mnt_entry) * allocated);
> > +                       }
> > +
> > +                       free(opt);
> > +               }
> > +       }
> > +
> > +       *ovl_mnt_entries = mnt_entries;
> > +       *ovl_mnt_count = num;
> > +
> > +       endmntent(fp);
> > +       return 0;
> > +}
> > +
> > +static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
> > +                               int ovl_mnt_count)
> > +{
> > +       int i,j;
> > +
> > +       for (i = 0; i < ovl_mnt_count; i++) {
> > +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
> > +                       free(ovl_mnt_entries[i].lowerdir[j]);
> > +               free(ovl_mnt_entries[i].lowerdir);
> > +               free(ovl_mnt_entries[i].lowers);
> > +       }
> > +       free(ovl_mnt_entries);
> > +}
> > +
> > +/*
> > + * Scan every mounted filesystem, check the overlay directories want
> > + * to check is already mounted. Check and fix an online overlay is not
> > + * allowed.
> > + *
> > + * Note: fsck may modify lower layers, so even match only one directory
> > + *       is triggered as mounted.
> > + */
> > +int ovl_check_mount(bool *pass)
> > +{
> > +       struct ovl_mnt_entry *ovl_mnt_entries;
> > +       int ovl_mnt_entry_count;
> > +       char *mounted_path;
> > +       bool mounted;
> > +       int i,j,k;
> > +       int ret;
> > +
> > +       ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
> > +       if (ret)
> > +               return ret;
> > +
> > +       /* Only check hard matching */
> > +       for (i = 0; i < ovl_mnt_entry_count; i++) {
> > +               /* Check lower */
> > +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
> > +                       for (k = 0; k < lower_num; k++) {
> > +                               if (!strcmp(lowerdir[k],
> > +                                           ovl_mnt_entries[i].lowerdir[j])) {
> > +                                       mounted_path = lowerdir[k];
> > +                                       mounted = true;
> > +                                       goto out;
> > +                               }
> > +                       }
> > +               }
> > +
> > +               /* Check upper */
> > +               if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
> > +                       mounted_path = upperdir;
> > +                       mounted = true;
> > +                       goto out;
> > +               }
> > +
> > +               /* Check worker */
> > +               if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
> > +                       mounted_path = workdir;
> > +                       mounted = true;
> > +                       goto out;
> > +               }
> > +       }
> > +out:
> > +       ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
> > +
> > +       if (mounted)
> > +               print_info(_("Dir %s is mounted\n"), mounted_path);
> > +       *pass = !mounted;
> > +
> > +       return 0;
> > +}
> > diff --git a/mount.h b/mount.h
> > new file mode 100644
> > index 0000000..8a3762d
> > --- /dev/null
> > +++ b/mount.h
> > @@ -0,0 +1,8 @@
> > +#ifndef OVL_MOUNT_H
> > +#define OVL_MOUNT_H
> > +
> > +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> > +                          unsigned int *lowernum);
> > +int ovl_check_mount(bool *mounted);
> > +
> > +#endif /* OVL_MOUNT_H */
> > diff --git a/test/README.md b/test/README.md
> > new file mode 100644
> > index 0000000..71dbad8
> > --- /dev/null
> > +++ b/test/README.md
> > @@ -0,0 +1,12 @@
> > +fsck.overlay test
> > +=================
> > +
> > +This test cases simulate different inconsistency of overlay filesystem
> > +underlying directories, test fsck.overlay can repair them correctly or not.
> > +
> > +USAGE
> > +=====
> > +
> > +1. Check "local.config", modify config value to appropriate one.
> > +2. ./auto_test.sh
> > +3. After testing, the result file and log file created in the result directory.
> 
> This really sounds like a mini fork of xfstests.
> I'd prefer if those tests went into xfstests and used fsck.overlay either as an
> external program (like the rest of fsck.*) or as in-house test helper.
> 
> > diff --git a/test/auto_test.sh b/test/auto_test.sh
> > new file mode 100755
> > index 0000000..8248e24
> > --- /dev/null
> > +++ b/test/auto_test.sh
> > @@ -0,0 +1,46 @@
> > +#!/bin/bash
> > +
> > +. ./src/prepare.sh
> > +
> > +echo "***************"
> > +
> > +# 1. Test whiteout
> > +echo -e "1.Test Whiteout\n"
> > +$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
> > +if [ $? -ne 0 ]; then
> > +       echo -e "Result:\033[31m Fail\033[0m\n"
> > +       RESULT="Fail"
> > +else
> > +       echo -e "Result:\033[32m Pass\033[0m\n"
> > +       RESULT="Pass"
> > +fi
> > +
> > +echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
> > +
> > +# 2. Test opaque dir
> > +echo -e "2.Test opaque directory\n"
> > +$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
> > +if [ $? -ne 0 ]; then
> > +       echo -e "Result:\033[31m Fail\033[0m\n"
> > +       RESULT="Fail"
> > +else
> > +       echo -e "Result:\033[32m Pass\033[0m\n"
> > +       RESULT="Pass"
> > +fi
> > +
> > +echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
> > +
> > +# 3. Test redirect dir
> > +echo -e "3.Test redirect direcotry\n"
> > +$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
> > +if [ $? -ne 0 ]; then
> > +       echo -e "Result:\033[31m Fail\033[0m\n"
> > +       RESULT="Fail"
> > +else
> > +       echo -e "Result:\033[32m Pass\033[0m\n"
> > +       RESULT="Pass"
> > +fi
> > +
> > +echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
> > +
> > +echo "***************"
> > diff --git a/test/clean.sh b/test/clean.sh
> > new file mode 100755
> > index 0000000..80f360b
> > --- /dev/null
> > +++ b/test/clean.sh
> > @@ -0,0 +1,6 @@
> > +#!/bin/bash
> > +
> > +. ./src/prepare.sh
> > +
> > +rm -rf $RESULT_DIR
> > +rm -rf $TEST_DIR
> > diff --git a/test/local.config b/test/local.config
> > new file mode 100644
> > index 0000000..990395f
> > --- /dev/null
> > +++ b/test/local.config
> > @@ -0,0 +1,16 @@
> > +# Config
> > +export OVL_FSCK=fsck.overlay
> > +export TEST_DIR=/mnt/test
> > +
> > +# Base environment
> > +export PWD=`pwd`
> > +export RESULT_DIR=$PWD/result
> > +export SOURCE_DIR=$PWD/src
> > +
> > +# Overlayfs config
> > +export LOWER_DIR="lower"
> > +export UPPER_DIR="upper"
> > +export WORK_DIR="worker"
> 
> worker? this is a strange name for work dir location..
> it does not have the same meaning in English as upp-er and low-er.
> 
> > +export MERGE_DIR="merge"
> > +export LOWER_DIR1="lower1"
> > +export WORK_DIR1="worker1"
> > diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
> > new file mode 100755
> > index 0000000..7cb5030
> > --- /dev/null
> > +++ b/test/src/opaque_test.sh
> > @@ -0,0 +1,144 @@
> > +#!/bin/bash
> > +
> > +. ./local.config
> > +
> > +__clean()
> > +{
> > +       rm -rf $TEST_DIR/*
> > +       cd $PWD
> > +}
> > +
> > +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> > +OVL_OPAQUE_XATTR_VAL="y"
> > +RET=0
> > +
> > +cd $TEST_DIR
> > +rm -rf *
> > +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> > +
> > +# 1.Test lower invalid opaque directory
> > +echo "***** 1.Test lower invalid opaque directory *****"
> > +mkdir $LOWER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: lower's invalid opaque xattr not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 2.Test upper invalid opaque directory
> > +echo "***** 2.Test upper invalid opaque directory *****"
> > +mkdir -p $UPPER_DIR/testdir0/testdir1
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: upper's invalid opaque xattr not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +
> > +# 3.Test upper opaque direcotry in merged directory
> > +echo "***** 3.Test upper opaque direcotry in merged directory *****"
> > +mkdir $UPPER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +
> > +# 4.Test upper opaque direcotry cover lower file
> > +echo "***** 4.Test upper opaque direcotry cover lower file *****"
> > +mkdir $UPPER_DIR/testdir
> > +mkdir $LOWER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 5.Test upper opaque direcotry cover lower directory
> > +echo "***** 5.Test upper opaque direcotry cover lower directory *****"
> > +mkdir $UPPER_DIR/testdir
> > +touch $LOWER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 6.Test lower invalid opaque directory in middle layer
> > +echo "***** 6.Test lower invalid opaque directory in middle layer *****"
> > +mkdir $LOWER_DIR/testdir0/testdir1
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: middle's opaque xattr not removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 7.Test lower invalid opaque directory in bottom layer
> > +echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
> > +mkdir $LOWER_DIR1/testdir0/testdir1
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
> > +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: middle's opaque xattr not removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $LOWER_DIR1/*
> > +
> > +# 8.Test middle opaque direcotry cover bottom directory
> > +echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
> > +mkdir $LOWER_DIR1/testdir
> > +mkdir $LOWER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: middle's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $LOWER_DIR1/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 9.Test double opaque direcotry in middle and upper layer
> > +echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
> > +mkdir $LOWER_DIR1/testdir
> > +mkdir $LOWER_DIR/testdir
> > +mkdir $UPPER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> > +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: middle's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's opaque xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $LOWER_DIR1/*
> > +rm -rf $LOWER_DIR/*
> > +rm -rf $UPPER_DIR/*
> > +
> > +__clean
> > +exit $RET
> > diff --git a/test/src/prepare.sh b/test/src/prepare.sh
> > new file mode 100755
> > index 0000000..138539a
> > --- /dev/null
> > +++ b/test/src/prepare.sh
> > @@ -0,0 +1,15 @@
> > +#!/bin/bash
> > +
> > +. ./local.config
> > +
> > +NOW=`date +"%Y-%m-%d-%H-%M-%S"`
> > +LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
> > +RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
> > +
> > +# creat test base dir
> > +if [ ! -d "$TEST_DIR" ]; then
> > +       mkdir -p $TEST_DIR
> > +fi
> > +
> > +# creat result dir
> > +mkdir -p $RESULT_DIR
> > diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
> > new file mode 100755
> > index 0000000..be2ce80
> > --- /dev/null
> > +++ b/test/src/redirect_test.sh
> > @@ -0,0 +1,163 @@
> > +#!/bin/bash
> > +
> > +. ./local.config
> > +
> > +__clean()
> > +{
> > +       rm -rf $TEST_DIR/*
> > +       cd $PWD
> > +}
> > +
> > +OVL_REDIRECT_XATTR="trusted.overlay.redirect"
> > +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> > +RET=0
> > +
> > +cd $TEST_DIR
> > +rm -rf *
> > +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> > +
> > +# 1.Test no exist redirect origin
> > +echo "***** 1.Test no exist redirect origin *****"
> > +mkdir $UPPER_DIR/testdir
> > +setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: upper's redirect xattr not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +
> > +# 2.Test redirect origin is file
> > +echo "***** 2.Test redirect origin is file *****"
> > +mkdir $UPPER_DIR/testdir
> > +touch $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: upper's redirect xattr not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 3.Test valid redirect xattr
> > +echo "***** 3.Test valid redirect xattr *****"
> > +mkdir $UPPER_DIR/testdir
> > +mknod $UPPER_DIR/origin c 0 0
> > +mkdir $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 4.Test valid redirect xattr start from root
> > +echo "***** 4.Test valid redirect xattr start from root *****"
> > +mkdir -p $UPPER_DIR/testdir0/testdir1
> > +mknod $UPPER_DIR/origin c 0 0
> > +mkdir $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 5.Test missing whiteout in redirect parent directory
> > +echo "***** 5.Test missing whiteout in redirect parent directory *****"
> > +mkdir $UPPER_DIR/testdir
> > +mkdir $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +if [ ! -e "$UPPER_DIR/origin" ];then
> > +       echo "ERROR: upper's missing whiteout not create"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $UPPER_DIR/*
> > +
> > +# 6.Test missing opaque in redirect parent directory
> > +echo "***** 6.Test missing opaque in redirect parent directory *****"
> > +mkdir -p $UPPER_DIR/testdir0/testdir1
> > +mkdir $UPPER_DIR/origin
> > +mkdir $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's missing opaque not set"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 7.Test duplicate redirect directory in one layer
> > +echo "***** 7.Test duplicate redirect directory in one layer *****"
> > +mkdir $UPPER_DIR/testdir0
> > +mkdir $UPPER_DIR/testdir1
> > +mknod $UPPER_DIR/origin c 0 0
> > +mkdir $LOWER_DIR/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +if [[ $? -eq 0 ]];then
> > +       echo "ERROR: fsck check incorrect pass"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +
> > +# 8.Test duplicate redirect directory in different layer
> > +echo "***** 8.Test duplicate redirect directory in different layer *****"
> > +mkdir $UPPER_DIR/testdir0
> > +mkdir $LOWER_DIR/testdir1
> > +mkdir $LOWER_DIR1/origin
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> > +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
> > +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> > +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: upper's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
> > +if [[ $? -ne 0 ]];then
> > +       echo "ERROR: lower's redirect xattr incorrect removed"
> > +       RET=$(($RET+1))
> > +fi
> > +
> > +if [ ! -e "$LOWER_DIR/origin" ];then
> > +       echo "ERROR: upper's missing whiteout not create"
> > +       RET=$(($RET+1))
> > +fi
> > +if [ ! -e "$LOWER_DIR/origin" ];then
> > +       echo "ERROR: lower's missing whiteout not create"
> > +       RET=$(($RET+1))
> > +fi
> > +
> > +rm -rf $UPPER_DIR/*
> > +rm -rf $LOWER_DIR/*
> > +rm -rf $LOWER_DIR1/*
> > +
> > +__clean
> > +
> > +exit $RET
> > diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
> > new file mode 100755
> > index 0000000..c0e1555
> > --- /dev/null
> > +++ b/test/src/whiteout_test.sh
> > @@ -0,0 +1,63 @@
> > +#!/bin/bash
> > +
> > +. ./local.config
> > +
> > +__clean()
> > +{
> > +       rm -rf $TEST_DIR/*
> > +       cd $PWD
> > +}
> > +
> > +RET=0
> > +
> > +cd $TEST_DIR
> > +rm -rf *
> > +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> > +
> > +# 1.Test lower orphan whiteout
> > +echo "***** 1.Test lower orphan whiteout *****"
> > +mknod $LOWER_DIR/foo c 0 0
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +if [ -e "$LOWER_DIR/foo" ];then
> > +       echo "lower's whiteout not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -f $LOWER_DIR/*
> > +
> > +# 2.Test upper orphan whiteout
> > +echo "***** 2.Test upper orphan whiteout *****"
> > +mknod $UPPER_DIR/foo c 0 0
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +if [ -e "$UPPER_DIR/foo" ];then
> > +       echo "upper's whiteout not remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -f $UPPER_DIR/*
> > +
> > +# 3.Test upper inuse whiteout
> > +echo "***** 3.Test upper inuse whiteout *****"
> > +touch $LOWER_DIR/foo
> > +mknod $UPPER_DIR/foo c 0 0
> > +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +if [ ! -e "$UPPER_DIR/foo" ];then
> > +       echo "upper's whiteout incorrect remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -f $UPPER_DIR/*
> > +rm -f $LOWER_DIR/*
> > +
> > +# 4.Test lower inuse whiteout
> > +echo "***** 4.Test lower inuse whiteout *****"
> > +touch $LOWER_DIR/foo
> > +mknod $LOWER_DIR1/foo c 0 0
> > +$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> > +if [ ! -e "$LOWER_DIR1/foo" ];then
> > +       echo "lower's whiteout incorrect remove"
> > +       RET=$(($RET+1))
> > +fi
> > +rm -f $LOWER_DIR1/*
> > +rm -f $LOWER_DIR/*
> > +
> > +__clean
> > +
> > +exit $RET
> > --
> > 2.9.5
> >
> --
> To unsubscribe from this list: send the line "unsubscribe fstests" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-17 17:13 ` Amir Goldstein
  2017-11-17 18:39   ` Darrick J. Wong
@ 2017-11-20  6:56   ` zhangyi (F)
  2017-11-20  7:29     ` Amir Goldstein
  2017-11-20  7:42     ` Eryu Guan
  2018-03-07  9:25   ` yangerkun
  2 siblings, 2 replies; 19+ messages in thread
From: zhangyi (F) @ 2017-11-20  6:56 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

On 2017/11/18 1:13, Amir Goldstein Wrote:
> [adding fstests in CC with full patch inline to collect wisdom from
> other fs developers]
> 
> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>> Hi,
>>
>> Here is the origin version of fsck.overlay utility I mentioned last
>> week. It scan each underlying directory and check orphan whiteout,
>> invalid opaque xattr and invalid redirect xattr. We can use it to
>> check filesystem inconsistency before mount overlayfs to avoid some
>> unexpected results.
> 
> Thanks for working on this!
> It looks like a great start.
> 
Hi Amir,

Thanks a lot for review and your suggestions.

>>
>> This patch can am to an empty git repository. I have already do basic
>> test list in 'test' directory. Please do a review give some suggestions.
>> Thanks a lot.
> 
> Please consider, instead of writing overlay tests in new repo,
> to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
> _check_scratch_fs.
> See for example very basic index sanity checks I implemented here:
> https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
> 

I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
and put in the same place. Now I notice that all xfs test cases put together
in xfstests, so It's also a good choice, not sure which one is better. How
about Eryu's opinion ?

> It may be useful, if Eryu agrees, to merge the "incubator" version of
> fsck.overlayfs
> into xfstests as a helper program until it starts getting packaged and
> distributed.
> Please consider this option.
> 

Can we applay for a repository in git.kernel.org like xfsprogs-dev and e2fsprogs ?

>>
>> Thanks,
>> Yi.
>>
>> ---------------------------
>>
>> fsck.overlay
>> ============
>>
>> fsck.overlay is used to check and optionally repair underlying
>> directories of overlay-filesystem.
>>
>> Check the following points:
>>
>> Whiteouts
>> ---------
>>
>> A whiteout is a character device with 0/0 device number. It is used to
>> record the removed files or directories, When a whiteout is found in a
>> directory, there should be at least one directory or file with the same
>> name in any of the corresponding lower layers. If not exist, the whiteout
>> will be treated as orphan whiteout and remove.
>>
>> Opaque directories
>> ------------------
>>
>> An opaque directory is a directory with "trusted.overlay.opaque" xattr
>> valued "y". There are two legal situations of making opaque directory: 1)
>> create directory over whiteout 2) creat directory in merged directory. If an
>> opaque directory is found, corresponding matching name in lower layers might
>> exist or parent directory might merged, If not, the opaque xattr will be
>> treated as invalid and remove.
>>
>> Redirect directories
>> --------------------
>>
>> An redirect directory is a directory with "trusted.overlay.redirect"
>> xattr valued to the path of the original location from the root of the
>> overlay. It is only used when renaming a directory and "redirect dir"
>> feature is enabled. If an redirect directory is found, the following
>> must be met:
>>
>> 1) The directory store in redirect xattr should exist in one of lower
>> layers.
>> 2) The origin directory should be redirected only once in one layer,
>> which mean there is only one redirect xattr point to this origin directory in
>> the specified layer.
>> 3) A whiteout or an opaque directory with the same name to origin should
>> exist in the same directory as the redirect directory.
>>
>> If not, 1) The redirect xattr is invalid and need to remove 2) One of
>> the redirect xattr is redundant but not sure which one is, ask user 3)
>> Create a whiteout device or set opaque xattr to an existing directory if the
>> parent directory was meregd or remove xattr if not.
>>
>> Usage
>> =====
>>
>> 1. Ensure overlay filesystem is not mounted based on directories which
>> need to check.
>>
>> 2. Run fsck.overlay program:
>>    Usage:
>>    fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
>>
>>    Options:
>>    -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
>>                              multiple lower use ':' as separator.
>>    -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
>>    -w, --workdir=WORKDIR     specify work directory of overlayfs
>>    -a, --auto                repair automatically (no questions)
> 
> Let stick with e2fsck conventions when possible.
> -a should be -y and we should definitely have -n, because this is
> how xfstest would run it.

Yes, will fix it.

>>    -v, --verbose             print more messages of overlayfs
>>    -h, --help                display this usage of overlayfs
>>    -V, --version             display version information
>>
>> 3. Exit value:
>>    0      No errors
>>    1      Filesystem errors corrected
>>    2      System should be rebooted
>>    4      Filesystem errors left uncorrected
>>    8      Operational error
>>    16     Usage or syntax error
>>    32     Checking canceled by user request
>>    128    Shared-library error
>>
>> Todo
>> ====
>>
>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>> online. Now, We cannot distinguish mounted directories if overlayfs was
>> mounted with relative path.
> 
> This should be handled by kernel.
> We now already grab an advisory exclusive I_OVL_INUSE lock on both
> upperdir and workdir.
> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
> Read the man page section on O_EXCL and block device. This is how
> e2fsck and friends get exclusive access w.r.t mount.
>

Good suggesttion, will consider it later.

>> 2. Symbolic link check.
>> 3. Check origin/impure/nlink xattr.
>> 4. ...
>>
>> Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
>> ---
>>  Makefile                  |  31 +++
>>  README.md                 |  88 +++++++++
>>  check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
>>  check.h                   |   7 +
>>  common.c                  |  95 +++++++++
>>  common.h                  |  34 ++++
>>  config.h                  |  22 +++
>>  fsck.c                    | 179 +++++++++++++++++
>>  lib.c                     | 197 +++++++++++++++++++
>>  lib.h                     |  73 +++++++
>>  mount.c                   | 319 ++++++++++++++++++++++++++++++
>>  mount.h                   |   8 +
>>  test/README.md            |  12 ++
>>  test/auto_test.sh         |  46 +++++
>>  test/clean.sh             |   6 +
>>  test/local.config         |  16 ++
>>  test/src/opaque_test.sh   | 144 ++++++++++++++
>>  test/src/prepare.sh       |  15 ++
>>  test/src/redirect_test.sh | 163 ++++++++++++++++
>>  test/src/whiteout_test.sh |  63 ++++++
>>  20 files changed, 2000 insertions(+)
>>  create mode 100644 Makefile
>>  create mode 100644 README.md
>>  create mode 100644 check.c
>>  create mode 100644 check.h
>>  create mode 100644 common.c
>>  create mode 100644 common.h
>>  create mode 100644 config.h
>>  create mode 100644 fsck.c
>>  create mode 100644 lib.c
>>  create mode 100644 lib.h
>>  create mode 100644 mount.c
>>  create mode 100644 mount.h
>>  create mode 100644 test/README.md
>>  create mode 100755 test/auto_test.sh
>>  create mode 100755 test/clean.sh
>>  create mode 100644 test/local.config
>>  create mode 100755 test/src/opaque_test.sh
>>  create mode 100755 test/src/prepare.sh
>>  create mode 100755 test/src/redirect_test.sh
>>  create mode 100755 test/src/whiteout_test.sh
>>
>> diff --git a/Makefile b/Makefile
>> new file mode 100644
>> index 0000000..ced5005
>> --- /dev/null
>> +++ b/Makefile
>> @@ -0,0 +1,31 @@
>> +CFLAGS = -Wall -g
>> +LFLAGS = -lm
>> +CC = gcc
>> +
>> +all: overlay
>> +
>> +overlay: fsck.o common.o lib.o check.o mount.o
>> +       $(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
>> +
>> +fsck.o:
>> +       $(CC) $(CFLAGS) -c fsck.c
>> +
>> +common.o:
>> +       $(CC) $(CFLAGS) -c common.c
>> +
>> +lib.o:
>> +       $(CC) $(CFLAGS) -c lib.c
>> +
>> +check.o:
>> +       $(CC) $(CFLAGS) -c check.c
>> +
>> +mount.o:
>> +       $(CC) $(CFLAGS) -c mount.c
>> +
>> +clean:
>> +       rm -f *.o fsck.overlay
>> +       rm -rf bin
>> +
>> +install: all
>> +       mkdir bin
>> +       cp fsck.overlay bin
>> diff --git a/README.md b/README.md
>> new file mode 100644
>> index 0000000..8de69cd
>> --- /dev/null
>> +++ b/README.md
>> @@ -0,0 +1,88 @@
>> +fsck.overlay
>> +============
>> +
>> +fsck.overlay is used to check and optionally repair underlying directories
>> +of overlay-filesystem.
>> +
>> +Check the following points:
>> +
>> +Whiteouts
>> +---------
>> +
>> +A whiteout is a character device with 0/0 device number. It is used to record
>> +the removed files or directories, When a whiteout is found in a directory,
>> +there should be at least one directory or file with the same name in any of the
>> +corresponding lower layers. If not exist, the whiteout will be treated as orphan
>> +whiteout and remove.
>> +
>> +
>> +Opaque directories
>> +------------------
>> +
>> +An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
>> +"y". There are two legal situations of making opaque directory: 1) create
>> +directory over whiteout 2) creat directory in merged directory. If an opaque
>> +directory is found, corresponding matching name in lower layers might exist or
>> +parent directory might merged, If not, the opaque xattr will be treated as
>> +invalid and remove.
>> +
>> +
>> +Redirect directories
>> +--------------------
>> +
>> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
>> +valued to the path of the original location from the root of the overlay. It
>> +is only used when renaming a directory and "redirect dir" feature is enabled.
>> +If an redirect directory is found, the following must be met:
>> +
>> +1) The directory store in redirect xattr should exist in one of lower layers.
>> +2) The origin directory should be redirected only once in one layer, which mean
>> +   there is only one redirect xattr point to this origin directory in the
>> +   specified layer.
>> +3) A whiteout or an opaque directory with the same name to origin should exist
>> +   in the same directory as the redirect directory.
> another redirected upper dir can also be covering the redirect lower target
> 
Do you mean the following case: upper's dir_rd cover lower1's redirect dir_rd ?

Upper:                    dir_rd(redurected,opaque)   dir2(whiteout)
Lower1:   dir(whiteout)   dir_rd(redurected)          dir2
Lower0:   dir

If so, I think my program handled this case already.
>> +
>> +If not, 1) The redirect xattr is invalid and need to remove 2) One of the
>> +redirect xattr is redundant but not sure which one is, ask user 3) Create a
>> +whiteout device or set opaque xattr to an existing directory if the parent
>> +directory was meregd or remove xattr if not.
> 
> You can consult origin xattr, decode it and fix redirect accordingly.
> I have plans to implement this in kernel as well with 'verify_dir' mount option,
> but maybe is it better to do this with fsck tool.
> Restoring unset origin xattr, which I also sent a kernel patch for can also be
> done by fsck as Miklos also suggested.
> 

Yes, I tried to use origin xattr and get the ovl_fh. We can put the redirect
information into ovl_fh to let fsck fix redirect latter.

> 
>> +
>> +Usage
>> +=====
>> +
>> +1. Ensure overlay filesystem is not mounted based on directories which need to
>> +   check.
>> +
>> +2. Run fsck.overlay program:
>> +   Usage:
>> +   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
>> +
>> +   Options:
>> +   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
>> +                             multiple lower use ':' as separator.
>> +   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
>> +   -w, --workdir=WORKDIR     specify work directory of overlayfs
>> +   -a, --auto                repair automatically (no questions)
>> +   -v, --verbose             print more messages of overlayfs
>> +   -h, --help                display this usage of overlayfs
>> +   -V, --version             display version information
>> +
>> +3. Exit value:
>> +   0      No errors
>> +   1      Filesystem errors corrected
>> +   2      System should be rebooted
>> +   4      Filesystem errors left uncorrected
>> +   8      Operational error
>> +   16     Usage or syntax error
>> +   32     Checking canceled by user request
>> +   128    Shared-library error
>> +
>> +Todo
>> +====
>> +
>> +1. Overlay filesystem mounted check. Prevent fscking when overlay is
>> +   online. Now, We cannot distinguish mounted directories if overlayfs was
>> +   mounted with relative path.
>> +2. Symbolic link check.
>> +3. Check origin/impure/nlink xattr.
>> +4. ...
>> diff --git a/check.c b/check.c
>> new file mode 100644
>> index 0000000..1794501
>> --- /dev/null
>> +++ b/check.c
>> @@ -0,0 +1,482 @@
>> +/*
>> + *
>> + *     Check and fix inconsistency for all underlying layers of overlay
>> + *
>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> 
> I guess you are not obliged to, but it would probably be better for
> you to assign
> copyright either to you or to your employer.
> And choosing a license explicitly is also recommended.
> IMO it would be best if you could keep at least part of the code
> under LGPL (or a like) and start with a mini liboverlay that can grow over time
> and be used by other projects.
> 

will fix it.

>> + *
>> + */
>> +
>> +#include <stdlib.h>
>> +#include <stdio.h>
>> +#include <string.h>
>> +#include <errno.h>
>> +#include <unistd.h>
>> +#include <stdbool.h>
>> +#include <sys/types.h>
>> +#include <sys/xattr.h>
>> +#include <sys/stat.h>
>> +#include <linux/limits.h>
>> +
>> +#include "common.h"
>> +#include "lib.h"
>> +#include "check.h"
>> +
>> +/* Underlying information */
>> +struct ovl_lower_check {
>> +       unsigned int type;      /* check extent type */
>> +
>> +       bool exist;
>> +       char path[PATH_MAX];    /* exist pathname found, only valid if exist */
>> +       struct stat st;         /* only valid if exist */
>> +};
>> +
>> +/* Redirect information */
>> +struct ovl_redirect_entry {
>> +       struct ovl_redirect_entry *next;
>> +
>> +       char origin[PATH_MAX];  /* origin directory path */
>> +
>> +       char path[PATH_MAX];    /* redirect directory */
>> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
>> +       int dirnum;             /* only valid when dirtype==OVL_LOWER */
>> +};
>> +
>> +/* Whiteout */
>> +#define WHITEOUT_DEV   0
>> +#define WHITEOUT_MOD   0
>> +
>> +extern char **lowerdir;
>> +extern char upperdir[];
>> +extern char workdir[];
>> +extern unsigned int lower_num;
>> +extern int flags;
>> +extern int status;
>> +
>> +static inline mode_t file_type(const struct stat *status)
>> +{
>> +       return status->st_mode & S_IFMT;
>> +}
>> +
>> +static inline bool is_whiteout(const struct stat *status)
>> +{
>> +       return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
>> +}
>> +
>> +static inline bool is_dir(const struct stat *status)
>> +{
>> +       return file_type(status) == S_IFDIR;
>> +}
>> +
>> +static bool is_dir_xattr(const char *pathname, const char *xattrname)
>> +{
>> +       char val;
>> +       ssize_t ret;
>> +
>> +       ret = getxattr(pathname, xattrname, &val, 1);
>> +       if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
>> +               print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
>> +                           xattrname, strerror(errno));
>> +               return false;
>> +       }
>> +
>> +       return (ret == 1 && val == 'y') ? true : false;
>> +}
>> +
>> +static inline bool ovl_is_opaque(const char *pathname)
>> +{
>> +       return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
>> +}
>> +
>> +static inline int ovl_remove_opaque(const char *pathname)
>> +{
>> +       return remove_xattr(pathname, OVL_OPAQUE_XATTR);
>> +}
>> +
>> +static inline int ovl_set_opaque(const char *pathname)
>> +{
>> +       return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
>> +}
>> +
>> +static int ovl_get_redirect(const char *pathname, size_t dirlen,
>> +                           size_t filelen, char **redirect)
>> +{
>> +       char *buf = NULL;
>> +       ssize_t ret;
>> +
>> +       ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
>> +       if (ret <= 0 || !buf)
>> +               return ret;
>> +
>> +       if (buf[0] != '/') {
>> +               size_t baselen = strlen(pathname)-filelen-dirlen;
>> +
>> +               buf = srealloc(buf, ret + baselen + 1);
>> +               memmove(buf + baselen, buf, ret);
>> +               memcpy(buf, pathname+dirlen, baselen);
>> +               buf[ret + baselen] = '\0';
>> +       }
>> +
>> +       *redirect = buf;
>> +       return 0;
>> +}
>> +
>> +static inline int ovl_remove_redirect(const char *pathname)
>> +{
>> +       return remove_xattr(pathname, OVL_REDIRECT_XATTR);
>> +}
>> +
>> +static inline int ovl_create_whiteout(const char *pathname)
>> +{
>> +       int ret;
>> +
>> +       ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
>> +       if (ret)
>> +               print_err(_("Cannot mknod %s:%s\n"),
>> +                           pathname, strerror(errno));
>> +       return ret;
>> +}
>> +
>> +/*
>> + * Scan each lower dir lower than 'start' and check type matching,
>> + * we stop scan if we found something.
>> + *
>> + * skip: skip whiteout.
>> + *
>> + */
>> +static int ovl_check_lower(const char *path, unsigned int start,
>> +                          struct ovl_lower_check *chk, bool skip)
>> +{
>> +       char lower_path[PATH_MAX];
>> +       struct stat st;
>> +       unsigned int i;
>> +
>> +       for (i = start; i < lower_num; i++) {
>> +               snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
>> +
>> +               if (lstat(lower_path, &st) != 0) {
> 
> IMO it would be better to keep open fd for lower layers root and use fstatat()
> with layer root fd.
> 

It's a good idea, will change it.

>> +                       if (errno != ENOENT && errno != ENOTDIR) {
>> +                               print_err(_("Cannot stat %s: %s\n"),
>> +                                           lower_path, strerror(errno));
>> +                               return -1;
>> +                       }
>> +                       continue;
>> +               }
>> +
>> +               if (skip && is_whiteout(&st))
>> +                       continue;
>> +
>> +               chk->exist = true;
>> +               chk->st = st;
>> +               strncpy(chk->path, lower_path, sizeof(chk->path));
>> +               break;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +/*
>> + * Scan each underlying dirs under specified dir if a whiteout is
>> + * found, check it's orphan or not. In auto-mode, orphan whiteouts
>> + * will be removed directly.
>> + */
>> +static int ovl_check_whiteout(struct scan_ctx *sctx)
>> +{
>> +       const char *pathname = sctx->pathname;
>> +       const struct stat *st = sctx->st;
>> +       struct ovl_lower_check chk = {0};
>> +       unsigned int start;
>> +       int ret = 0;
>> +
>> +       /* Is a whiteout ? */
>> +       if (!is_whiteout(st))
>> +               return 0;
>> +
>> +       /* Is Whiteout in the bottom lower dir ? */
>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>> +               goto remove;
>> +
>> +       /*
>> +        * Scan each corresponding lower directroy under this layer,
>> +        * check is there a file or dir with the same name.
>> +        */
>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
>> +       if (ret)
>> +               return ret;
>> +       if (chk.exist && !is_whiteout(&chk.st))
>> +               goto out;
>> +
>> +remove:
>> +       /* Remove orphan whiteout directly or ask user */
>> +       print_info(_("Orphan whiteout: %s "), pathname);
>> +       if (!ask_question("Remove", 1))
>> +               return 0;
>> +
>> +       ret = unlink(pathname);
>> +       if (ret) {
>> +               print_err(_("Cannot unlink %s: %s\n"), pathname,
>> +                           strerror(errno));
>> +               return ret;
>> +       }
>> +out:
>> +       return ret;
>> +}
>> +
>> +/*
>> + * Scan each underlying under specified dir if an opaque dir is found,
>> + * check the opaque xattr is invalid or not. In auto-mode, invalid
>> + * opaque xattr will be removed directly.
>> + * Do the follow checking:
>> + * 1) Check lower matching name exist or not.
>> + * 2) Check parent dir is merged or pure.
>> + * If no lower matching and parent is not merged, remove opaque xattr.
>> + */
>> +static int ovl_check_opaque(struct scan_ctx *sctx)
>> +{
>> +       const char *pathname = sctx->pathname;
>> +       char parent_path[PATH_MAX];
>> +       struct ovl_lower_check chk = {0};
>> +       unsigned int start;
>> +       int ret = 0;
>> +
>> +       /* Is opaque ? */
>> +       if (!ovl_is_opaque(pathname))
>> +               goto out;
>> +
>> +       /* Opaque dir in last lower dir ? */
>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>> +               goto remove;
>> +
>> +       /*
>> +        * Scan each corresponding lower directroy under this layer,
>> +        * check if there is a file or dir with the same name.
>> +        */
>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
>> +       if (ret)
>> +               return ret;
>> +       if (chk.exist && !is_whiteout(&chk.st))
>> +               goto out;
>> +
>> +       /* Check parent directory merged or pure */
>> +       memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
>> +       ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
>> +       if (ret)
>> +               return ret;
>> +       if (chk.exist && is_dir(&chk.st))
>> +               goto out;
>> +
>> +remove:
>> +       /* Remove opaque xattr or ask user */
>> +       print_info(_("Invalid opaque xattr: %s "), pathname);
>> +       if (!ask_question("Remove", 1))
>> +               return 0;
>> +
>> +       ret = ovl_remove_opaque(pathname);
>> +out:
>> +       return ret;
>> +}
>> +
>> +static struct ovl_redirect_entry *redirect_list = NULL;
>> +
>> +static void ovl_redirect_entry_add(const char *path, int dirtype,
>> +                                  int dirnum, const char *origin)
>> +{
>> +       struct ovl_redirect_entry *last, *new;
>> +
>> +       new = smalloc(sizeof(*new));
>> +
>> +       print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
>> +                     origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
>> +                     (dirtype == OVL_UPPER) ? 0 : dirnum);
>> +
>> +       if (!redirect_list) {
>> +               redirect_list = new;
>> +       } else {
>> +               for (last = redirect_list; last->next; last = last->next);
>> +               last->next = new;
>> +       }
>> +       new->next = NULL;
>> +       new->dirtype = dirtype;
>> +       new->dirnum = dirnum;
>> +       strncpy(new->origin, origin, sizeof(new->origin));
>> +       strncpy(new->path, path, sizeof(new->path));
>> +}
>> +
>> +static bool vol_redirect_entry_find(const char *origin, int dirtype,
>> +                                   int dirnum, char **founded)
>> +{
>> +       struct ovl_redirect_entry *entry;
>> +
>> +       if (!redirect_list)
>> +               return false;
>> +
>> +       for (entry = redirect_list; entry; entry = entry->next) {
>> +               bool same_layer;
>> +
>> +               print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
>> +                             entry->origin, entry->path,
>> +                             (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
>> +                             (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
>> +
>> +               same_layer = ((entry->dirtype == dirtype) &&
>> +                             (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
>> +
>> +               if (same_layer && !strcmp(entry->origin, origin)) {
>> +                       *founded = entry->path;
>> +                       return true;
>> +               }
>> +       }
>> +
>> +       return false;
>> +}
>> +
>> +static void vol_redirect_free(void)
>> +{
>> +       struct ovl_redirect_entry *entry;
>> +
>> +       while (redirect_list) {
>> +               entry = redirect_list;
>> +               redirect_list = redirect_list->next;
>> +               free(entry);
>> +       }
>> +}
>> +
>> +/*
>> + * Get redirect origin directory stored in the xattr, check it's invlaid
>> + * or not, In auto-mode, invalid redirect xattr will be removed directly.
>> + * Do the follow checking:
>> + * 1) Check the origin directory exist or not. If not, remove xattr.
>> + * 2) Count how many directories the origin directory was redirected by.
>> + *    If more than one in the same layer, there must be some inconsistency
>> + *    but not sure, just warn.
>> + * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
>> + */
>> +static int ovl_check_redirect(struct scan_ctx *sctx)
>> +{
>> +       const char *pathname = sctx->pathname;
>> +       struct ovl_lower_check chk = {0};
>> +       char redirect_rpath[PATH_MAX];
>> +       struct stat rst;
>> +       char *redirect = NULL;
>> +       unsigned int start;
>> +       int ret = 0;
>> +
>> +       /* Get redirect */
>> +       ret = ovl_get_redirect(pathname, sctx->dirlen,
>> +                              sctx->filelen, &redirect);
>> +       if (ret || !redirect)
>> +               return ret;
>> +
>> +       print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
>> +
>> +       /* Redirect dir in last lower dir ? */
>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>> +               goto remove;
>> +
>> +       /* Scan lower directories to check redirect dir exist or not */
>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>> +       ret = ovl_check_lower(redirect, start, &chk, false);
>> +       if (ret)
>> +               goto out;
>> +       if (chk.exist && is_dir(&chk.st)) {
>> +               char *tmp;
>> +
>> +               /* Check duplicate in same layer */
>> +               if (vol_redirect_entry_find(chk.path, sctx->dirtype,
>> +                                           sctx->num, &tmp)) {
>> +                       print_info("Duplicate redirect directories found:\n");
>> +                       print_info("origin:%s current:%s latest:%s\n",
>> +                                  chk.path, pathname, tmp);
>> +
>> +                       set_st_inconsistency(&status);
>> +               }
>> +
>> +               ovl_redirect_entry_add(pathname, sctx->dirtype,
>> +                                      sctx->num, chk.path);
>> +
>> +               /* Check and fix whiteout or opaque dir */
>> +               snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
>> +                        sctx->dirname, redirect);
>> +               if (lstat(redirect_rpath, &rst) != 0) {
>> +                       if (errno != ENOENT && errno != ENOTDIR) {
>> +                               print_err(_("Cannot stat %s: %s\n"),
>> +                                           redirect_rpath, strerror(errno));
>> +                               goto out;
>> +                       }
>> +
>> +                       /* Found nothing, create a whiteout */
>> +                       ret = ovl_create_whiteout(redirect_rpath);
>> +
>> +               } else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
>> +                       /* Found a directory but not opaqued, fix opaque xattr */
>> +                       ret = ovl_set_opaque(redirect_rpath);
>> +               }
>> +
>> +               goto out;
>> +       }
>> +
>> +remove:
>> +       /* Remove redirect xattr or ask user */
>> +       print_info(_("Invalid redirect xattr: %s "), pathname);
>> +       if (!ask_question("Remove", 1))
>> +               goto out;
>> +
>> +       ret = ovl_remove_redirect(pathname);
>> +out:
>> +       free(redirect);
>> +       return ret;
>> +}
>> +
>> +static struct scan_operations ovl_scan_ops = {
>> +       .whiteout = ovl_check_whiteout,
>> +       .opaque = ovl_check_opaque,
>> +       .redirect = ovl_check_redirect,
>> +};
>> +
>> +static void ovl_scan_clean(void)
>> +{
>> +       /* Clean redirect entry record */
>> +       vol_redirect_free();
>> +}
>> +
>> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
>> +int ovl_scan_fix(void)
>> +{
>> +       struct scan_ctx sctx;
>> +       unsigned int i;
>> +       int ret = 0;
>> +
>> +       if (flags | FL_VERBOSE)
>> +               print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
>> +
>> +       /* Scan upper directory */
>> +       if (flags | FL_VERBOSE)
>> +               print_info(_("Scan upper directory: %s\n"), upperdir);
>> +
>> +       sctx.dirname = upperdir;
>> +       sctx.dirlen = strlen(upperdir);
>> +       sctx.dirtype = OVL_UPPER;
>> +
>> +       ret = scan_dir(&sctx, &ovl_scan_ops);
>> +       if (ret)
>> +               goto out;
>> +
>> +       /* Scan every lower directories */
>> +       for (i = 0; i < lower_num; i++) {
>> +               if (flags | FL_VERBOSE)
>> +                       print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
>> +
>> +               sctx.dirname = lowerdir[i];
>> +               sctx.dirlen = strlen(lowerdir[i]);
>> +               sctx.dirtype = OVL_LOWER;
>> +               sctx.num = i;
>> +
>> +               ret = scan_dir(&sctx, &ovl_scan_ops);
>> +               if (ret)
>> +                       goto out;
>> +       }
>> +out:
>> +       ovl_scan_clean();
>> +       return ret;
>> +}
>> diff --git a/check.h b/check.h
>> new file mode 100644
>> index 0000000..373ff3a
>> --- /dev/null
>> +++ b/check.h
>> @@ -0,0 +1,7 @@
>> +#ifndef OVL_WHITECHECK_H
>> +#define OVL_WHITECHECK_H
>> +
>> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
>> +int ovl_scan_fix(void);
>> +
>> +#endif /* OVL_WHITECHECK_H */
>> diff --git a/common.c b/common.c
>> new file mode 100644
>> index 0000000..4e77045
>> --- /dev/null
>> +++ b/common.c
>> @@ -0,0 +1,95 @@
>> +/*
>> + *
>> + *     Common things for all utilities
>> + *
>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>> + *
>> + */
>> +
>> +#include <stdio.h>
>> +#include <string.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <stdarg.h>
>> +#include <errno.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include "common.h"
>> +#include "config.h"
>> +
>> +extern char *program_name;
>> +
>> +/* #define DEBUG 1 */
>> +#ifdef DEBUG
>> +void print_debug(char *fmtstr, ...)
>> +{
>> +       va_list args;
>> +
>> +       va_start(args, fmtstr);
>> +       fprintf(stdout, "%s:[Debug]: ", program_name);
>> +       vfprintf(stdout, fmtstr, args);
>> +       va_end(args);
>> +}
>> +#else
>> +void print_debug (char *fmtstr, ...) {}
>> +#endif
>> +
>> +void print_info(char *fmtstr, ...)
>> +{
>> +       va_list args;
>> +
>> +       va_start(args, fmtstr);
>> +       vfprintf(stdout, fmtstr, args);
>> +       va_end(args);
>> +}
>> +
>> +void print_err(char *fmtstr, ...)
>> +{
>> +       va_list args;
>> +
>> +       va_start(args, fmtstr);
>> +       fprintf(stderr, "%s:[Error]: ", program_name);
>> +       vfprintf(stderr, fmtstr, args);
>> +       va_end(args);
>> +}
>> +
>> +void *smalloc(size_t size)
>> +{
>> +       void *new = malloc(size);
>> +
>> +       if (!new) {
>> +               print_err(_("malloc error:%s\n"), strerror(errno));
>> +               exit(1);
>> +       }
>> +
>> +       memset(new, 0, size);
>> +       return new;
>> +}
>> +
>> +void *srealloc(void *addr, size_t size)
>> +{
>> +       void *re = realloc(addr, size);
>> +
>> +       if (!re) {
>> +               print_err(_("malloc error:%s\n"), strerror(errno));
>> +               exit(1);
>> +       }
>> +       return re;
>> +}
>> +
>> +char *sstrdup(const char *src)
>> +{
>> +       char *dst = strdup(src);
>> +
>> +       if (!dst) {
>> +               print_err(_("strdup error:%s\n"), strerror(errno));
>> +               exit(1);
>> +       }
>> +
>> +       return dst;
>> +}
>> +
>> +void version(void)
>> +{
>> +       printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
>> +}
>> diff --git a/common.h b/common.h
>> new file mode 100644
>> index 0000000..c4707e7
>> --- /dev/null
>> +++ b/common.h
>> @@ -0,0 +1,34 @@
>> +#ifndef OVL_COMMON_H
>> +#define OVL_COMMON_H
>> +
>> +#ifndef __attribute__
>> +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
>> +#  define __attribute__(x)
>> +# endif
>> +#endif
>> +
>> +#ifdef USE_GETTEXT
>> +#include <libintl.h>
>> +#define _(x)   gettext((x))
>> +#else
>> +#define _(x)   (x)
>> +#endif
>> +
>> +/* Print an error message */
>> +void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>> +
>> +/* Print an info message */
>> +void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>> +
>> +/* Print an debug message */
>> +void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>> +
>> +/* Safety wrapper */
>> +void *smalloc(size_t size);
>> +void *srealloc(void *addr, size_t size);
>> +char *sstrdup(const char *src);
>> +
>> +/* Print program version */
>> +void version(void);
>> +
>> +#endif /* OVL_COMMON_H */
>> diff --git a/config.h b/config.h
>> new file mode 100644
>> index 0000000..deac089
>> --- /dev/null
>> +++ b/config.h
>> @@ -0,0 +1,22 @@
>> +#ifndef OVL_CONFIG_H
>> +#define OVL_CONFIG_H
>> +
>> +/* program version */
>> +#define PACKAGE_VERSION        "v1.0.0"
>> +
>> +/* overlay max lower stacks (the same to kernel overlayfs driver) */
>> +#define OVL_MAX_STACK 500
>> +
>> +/* File with mounted filesystems */
>> +#define MOUNT_TAB "/proc/mounts"
>> +
>> +/* Name of overlay filesystem type */
>> +#define OVERLAY_NAME "overlay"
>> +#define OVERLAY_NAME_OLD "overlayfs"
>> +
>> +/* Mount options */
>> +#define OPT_LOWERDIR "lowerdir="
>> +#define OPT_UPPERDIR "upperdir="
>> +#define OPT_WORKDIR "workdir="
>> +
>> +#endif
>> diff --git a/fsck.c b/fsck.c
>> new file mode 100644
>> index 0000000..cbcb8e9
>> --- /dev/null
>> +++ b/fsck.c
>> @@ -0,0 +1,179 @@
>> +/*
>> + *
>> + *     Utility to fsck overlay
>> + *
>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>> + *
>> + */
>> +
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <errno.h>
>> +#include <getopt.h>
>> +#include <libgen.h>
>> +#include <stdbool.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <linux/limits.h>
>> +
>> +#include "common.h"
>> +#include "config.h"
>> +#include "lib.h"
>> +#include "check.h"
>> +#include "mount.h"
>> +
>> +char *program_name;
>> +
>> +char **lowerdir;
>> +char upperdir[PATH_MAX] = {0};
>> +char workdir[PATH_MAX] = {0};
>> +unsigned int lower_num;
>> +int flags;             /* user input option flags */
>> +int status;            /* fsck scan status */
>> +
>> +/* Cleanup lower directories buf */
>> +static void ovl_clean_lowerdirs(void)
>> +{
>> +       unsigned int i;
>> +
>> +       for (i = 0; i < lower_num; i++) {
>> +               free(lowerdir[i]);
>> +               lowerdir[i] = NULL;
>> +               lower_num = 0;
>> +       }
>> +       free(lowerdir);
>> +       lowerdir = NULL;
>> +}
>> +
>> +static void usage(void)
>> +{
>> +       print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
>> +                   "[-avhV]\n\n"), program_name);
>> +       print_info(_("Options:\n"
>> +                   "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
>> +                   "                          multiple lower use ':' as separator\n"
>> +                   "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
>> +                   "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
>> +                   "-a, --auto                repair automatically (no questions)\n"
>> +                   "-v, --verbose             print more messages of overlayfs\n"
>> +                   "-h, --help                display this usage of overlayfs\n"
>> +                   "-V, --version             display version information\n"));
>> +       exit(1);
>> +}
>> +
>> +static void parse_options(int argc, char *argv[])
>> +{
>> +       char *lowertemp;
>> +       int c;
>> +       int ret = 0;
>> +
>> +       struct option long_options[] = {
>> +               {"lowerdir", required_argument, NULL, 'l'},
>> +               {"upperdir", required_argument, NULL, 'u'},
>> +               {"workdir", required_argument, NULL, 'w'},
>> +               {"auto", no_argument, NULL, 'a'},
>> +               {"verbose", no_argument, NULL, 'v'},
>> +               {"version", no_argument, NULL, 'V'},
>> +               {"help", no_argument, NULL, 'h'},
>> +               {NULL, 0, NULL, 0}
>> +       };
>> +
>> +       while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
>> +               switch (c) {
>> +               case 'l':
>> +                       lowertemp = strdup(optarg);
>> +                       ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
>> +                       free(lowertemp);
>> +                       break;
>> +               case 'u':
>> +                       if (realpath(optarg, upperdir)) {
>> +                               print_debug(_("Upperdir:%s\n"), upperdir);
>> +                               flags |= FL_UPPER;
>> +                       } else {
>> +                               print_err(_("Failed to resolve upperdir:%s\n"), optarg);
>> +                               ret = -1;
>> +                       }
>> +                       break;
>> +               case 'w':
>> +                       if (realpath(optarg, workdir)) {
>> +                               print_debug(_("Workdir:%s\n"), workdir);
>> +                               flags |= FL_WORK;
>> +                       } else {
>> +                               print_err(_("Failed to resolve workdir:%s\n"), optarg);
>> +                               ret = -1;
>> +                       }
>> +                       break;
>> +               case 'a':
>> +                       flags |= FL_AUTO;
>> +                       break;
>> +               case 'v':
>> +                       flags |= FL_VERBOSE;
>> +                       break;
>> +               case 'V':
>> +                       version();
>> +                       return;
>> +               case 'h':
>> +               default:
>> +                       usage();
>> +                       return;
>> +               }
>> +
>> +               if (ret)
>> +                       exit(1);
>> +       }
>> +
>> +       if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
>> +               print_info(_("Please specify correct lowerdirs and upperdir\n"));
>> +               usage();
>> +       }
>> +}
>> +
>> +void fsck_status_check(int *val)
>> +{
>> +       if (status & OVL_ST_INCONSISTNECY) {
>> +               *val |= FSCK_UNCORRECTED;
>> +               print_info(_("Still have unexpected inconsistency!\n"));
>> +       }
>> +
>> +       if (status & OVL_ST_ABORT) {
>> +               *val |= FSCK_ERROR;
>> +               print_info(_("Cannot continue, aborting\n"));
>> +       }
>> +}
>> +
>> +int main(int argc, char *argv[])
>> +{
>> +       bool mounted;
>> +       int err = 0;
>> +       int exit_value = 0;
>> +
>> +       program_name = basename(argv[0]);
>> +
>> +       parse_options(argc, argv);
>> +
>> +       /* Ensure overlay filesystem not mounted */
>> +       if ((err = ovl_check_mount(&mounted)))
>> +               goto out;
>> +       if (!mounted) {
>> +               set_st_abort(&status);
>> +               goto out;
>> +       }
>> +
>> +       /* Scan and fix */
>> +       if ((err = ovl_scan_fix()))
>> +               goto out;
>> +
>> +out:
>> +       fsck_status_check(&exit_value);
>> +       ovl_clean_lowerdirs();
>> +
>> +       if (err)
>> +               exit_value |= FSCK_ERROR;
>> +       if (exit_value)
>> +               print_info("WARNING: Filesystem check failed, may not clean\n");
>> +       else
>> +               print_info("Filesystem clean\n");
>> +
>> +       return exit_value;
>> +}
>> diff --git a/lib.c b/lib.c
>> new file mode 100644
>> index 0000000..a6832fe
>> --- /dev/null
>> +++ b/lib.c
>> @@ -0,0 +1,197 @@
>> +/*
>> + *
>> + *     Common things for all utilities
>> + *
>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>> + *
>> + */
>> +
>> +#include <stdlib.h>
>> +#include <stdio.h>
>> +#include <unistd.h>
>> +#include <errno.h>
>> +#include <string.h>
>> +#include <stdbool.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <sys/xattr.h>
>> +#include <fts.h>
>> +
>> +#include "common.h"
>> +#include "lib.h"
>> +
>> +extern int flags;
>> +extern int status;
>> +
>> +static int ask_yn(const char *question, int def)
>> +{
>> +       char ans[16];
>> +
>> +       print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
>> +       fflush(stdout);
>> +       while (fgets(ans, sizeof(ans)-1, stdin)) {
>> +               if (ans[0] == '\n')
>> +                       return def;
>> +               else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
>> +                       return 1;
>> +               else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
>> +                       return 0;
>> +               else
>> +                       print_info(_("Illegal answer. Please input y/n or yes/no:"));
>> +               fflush(stdout);
>> +       }
>> +       return def;
>> +}
>> +
>> +/* Ask user */
>> +int ask_question(const char *question, int def)
>> +{
>> +       if (flags & FL_AUTO) {
>> +               print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
>> +               return def;
>> +       }
>> +
>> +       return ask_yn(question, def);
>> +}
>> +
>> +ssize_t get_xattr(const char *pathname, const char *xattrname,
>> +                 char **value, bool *exist)
>> +{
>> +       char *buf = NULL;
>> +       ssize_t ret;
>> +
>> +       ret = getxattr(pathname, xattrname, NULL, 0);
>> +       if (ret < 0) {
>> +               if (errno == ENODATA || errno == ENOTSUP) {
>> +                       if (exist)
>> +                               *exist = false;
>> +                       return 0;
>> +               } else {
>> +                       goto fail;
>> +               }
>> +       }
>> +
>> +       /* Zero size value means xattr exist but value unknown */
>> +       if (exist)
>> +               *exist = true;
>> +       if (ret == 0 || !value)
>> +               return 0;
>> +
>> +       buf = smalloc(ret+1);
>> +       ret = getxattr(pathname, xattrname, buf, ret);
>> +       if (ret <= 0)
>> +               goto fail2;
>> +
>> +       buf[ret] = '\0';
>> +       *value = buf;
>> +       return ret;
>> +
>> +fail2:
>> +       free(buf);
>> +fail:
>> +       print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
>> +                   xattrname, strerror(errno));
>> +       return -1;
>> +}
>> +
>> +int set_xattr(const char *pathname, const char *xattrname,
>> +               void *value, size_t size)
>> +{
>> +       int ret;
>> +
>> +       ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
>> +       if (ret && ret != EEXIST)
>> +               goto fail;
>> +
>> +       if (ret == EEXIST) {
>> +               ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
>> +               if (ret)
>> +                       goto fail;
>> +       }
>> +
>> +       return 0;
>> +fail:
>> +       print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
>> +                   xattrname, strerror(errno));
>> +       return ret;
>> +}
>> +
>> +int remove_xattr(const char *pathname, const char *xattrname)
>> +{
>> +       int ret;
>> +       if ((ret = removexattr(pathname, xattrname)))
>> +               print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
>> +                           xattrname, strerror(errno));
>> +       return ret;
>> +}
>> +
>> +static inline int __check_entry(struct scan_ctx *sctx,
>> +                                 int (*do_check)(struct scan_ctx *))
>> +{
>> +       return do_check ? do_check(sctx) : 0;
>> +}
>> +
>> +/* Scan specified directories and invoke callback */
>> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
>> +{
>> +       char *paths[2] = {(char *)sctx->dirname, NULL};
>> +       FTS *ftsp;
>> +       FTSENT *ftsent;
>> +       int ret = 0;
>> +
>> +       ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
>> +       if (ftsp == NULL) {
>> +               print_err(_("Failed to fts open %s:%s\n"),
>> +                           sctx->dirname, strerror(errno));
>> +               return -1;
>> +       }
>> +
>> +       while ((ftsent = fts_read(ftsp)) != NULL) {
>> +               int err;
>> +
>> +               print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
>> +                       (ftsent->fts_info == FTS_D) ? "d" :
>> +                       (ftsent->fts_info == FTS_DNR) ? "dnr" :
>> +                       (ftsent->fts_info == FTS_DP) ? "dp" :
>> +                       (ftsent->fts_info == FTS_F) ? "f" :
>> +                       (ftsent->fts_info == FTS_NS) ? "ns" :
>> +                       (ftsent->fts_info == FTS_SL) ? "sl" :
>> +                       (ftsent->fts_info == FTS_SLNONE) ? "sln" :
>> +                       (ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
>> +                       ftsent->fts_level, ftsent->fts_statp->st_size,
>> +                       ftsent->fts_path, ftsent->fts_name);
>> +
>> +
>> +               /* Fillup base context */
>> +               sctx->pathname = ftsent->fts_path;
>> +               sctx->pathlen = ftsent->fts_pathlen;
>> +               sctx->filename = ftsent->fts_name;
>> +               sctx->filelen = ftsent->fts_namelen;
>> +               sctx->st = ftsent->fts_statp;
>> +
>> +               switch (ftsent->fts_info) {
>> +               case FTS_DEFAULT:
>> +                       /* Check whiteouts */
>> +                       err = __check_entry(sctx, sop->whiteout);
>> +                       ret = (ret || !err) ? ret : err;
>> +                       break;
>> +               case FTS_D:
>> +                       /* Check opaque and redirect */
>> +                       err = __check_entry(sctx, sop->opaque);
>> +                       ret = (ret || !err) ? ret : err;
>> +
>> +                       err = __check_entry(sctx, sop->redirect);
>> +                       ret = (ret || !err) ? ret : err;
>> +                       break;
>> +               case FTS_NS:
>> +               case FTS_DNR:
>> +               case FTS_ERR:
>> +                       print_err(_("Failed to fts read %s:%s\n"),
>> +                                   ftsent->fts_path, strerror(ftsent->fts_errno));
>> +                       goto out;
>> +               }
>> +       }
>> +out:
>> +       fts_close(ftsp);
>> +       return ret;
>> +}
>> diff --git a/lib.h b/lib.h
>> new file mode 100644
>> index 0000000..463b263
>> --- /dev/null
>> +++ b/lib.h
>> @@ -0,0 +1,73 @@
>> +#ifndef OVL_LIB_H
>> +#define OVL_LIB_H
>> +
>> +/* Common return value */
>> +#define FSCK_OK          0     /* No errors */
>> +#define FSCK_NONDESTRUCT 1     /* File system errors corrected */
>> +#define FSCK_REBOOT      2     /* System should be rebooted */
>> +#define FSCK_UNCORRECTED 4     /* File system errors left uncorrected */
>> +#define FSCK_ERROR       8     /* Operational error */
>> +#define FSCK_USAGE       16    /* Usage or syntax error */
>> +#define FSCK_CANCELED   32     /* Aborted with a signal or ^C */
>> +#define FSCK_LIBRARY     128   /* Shared library error */
>> +
>> +/* Fsck status */
>> +#define OVL_ST_INCONSISTNECY   (1 << 0)
>> +#define OVL_ST_ABORT           (1 << 1)
>> +
>> +/* Option flags */
>> +#define FL_VERBOSE     (1 << 0)        /* verbose */
>> +#define FL_UPPER       (1 << 1)        /* specify upper directory */
>> +#define FL_WORK                (1 << 2)        /* specify work directory */
>> +#define FL_AUTO                (1 << 3)        /* automactically scan dirs and repair */
>> +
>> +/* Scan path type */
>> +#define OVL_UPPER      0
>> +#define OVL_LOWER      1
>> +#define OVL_WORKER     2
>> +#define OVL_PTYPE_MAX  3
>> +
>> +/* Xattr */
>> +#define OVL_OPAQUE_XATTR       "trusted.overlay.opaque"
>> +#define OVL_REDIRECT_XATTR     "trusted.overlay.redirect"
>> +
>> +/* Directories scan data struct */
>> +struct scan_ctx {
>> +       const char *dirname;    /* upper/lower root dir */
>> +       size_t dirlen;          /* strlen(dirlen) */
>> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
>> +       int num;                /* lower dir depth, lower type use only */
>> +
>> +       const char *pathname;   /* file path from the root */
>> +       size_t pathlen;         /* strlen(pathname) */
>> +       const char *filename;   /* filename */
>> +       size_t filelen;         /* strlen(filename) */
>> +       struct stat *st;        /* file stat */
>> +};
>> +
>> +/* Directories scan callback operations struct */
>> +struct scan_operations {
>> +       int (*whiteout)(struct scan_ctx *);
>> +       int (*opaque)(struct scan_ctx *);
>> +       int (*redirect)(struct scan_ctx *);
>> +};
>> +
>> +static inline void set_st_inconsistency(int *st)
>> +{
>> +       *st |= OVL_ST_INCONSISTNECY;
>> +}
>> +
>> +static inline void set_st_abort(int *st)
>> +{
>> +       *st |= OVL_ST_ABORT;
>> +}
>> +
>> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
>> +int ask_question(const char *question, int def);
>> +ssize_t get_xattr(const char *pathname, const char *xattrname,
>> +                 char **value, bool *exist);
>> +int set_xattr(const char *pathname, const char *xattrname,
>> +             void *value, size_t size);
>> +int remove_xattr(const char *pathname, const char *xattrname);
>> +
>> +#endif /* OVL_LIB_H */
>> diff --git a/mount.c b/mount.c
>> new file mode 100644
>> index 0000000..28ce8e5
>> --- /dev/null
>> +++ b/mount.c
>> @@ -0,0 +1,319 @@
>> +/*
>> + *
>> + *     Check mounted overlay
>> + *
>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>> + *
>> + */
>> +
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <errno.h>
>> +#include <getopt.h>
>> +#include <libgen.h>
>> +#include <stdbool.h>
>> +#include <mntent.h>
>> +#include <sys/types.h>
>> +#include <sys/stat.h>
>> +#include <linux/limits.h>
>> +
>> +#include "common.h"
>> +#include "config.h"
>> +#include "lib.h"
>> +#include "check.h"
>> +#include "mount.h"
>> +
>> +struct ovl_mnt_entry {
>> +       char *lowers;
>> +       char **lowerdir;
>> +       unsigned int lowernum;
>> +       char upperdir[PATH_MAX];
>> +       char workdir[PATH_MAX];
>> +};
>> +
>> +/* Mount buf allocate a time */
>> +#define ALLOC_NUM      16
>> +
>> +extern char **lowerdir;
>> +extern char upperdir[];
>> +extern char workdir[];
>> +extern unsigned int lower_num;
>> +
>> +/*
>> + * Split directories to individual one.
>> + * (copied from linux kernel, see fs/overlayfs/super.c)
>> + */
> 
> It would be nice if such functions could be maintained in a file/dir
> that is synced with the kernel, like the xfs/lib files.
> For the first release we don't really have to sync this file with the
> kernel, but at least keep them at a specific file/dir and maybe the
> kernel driver will follow up later.
> 

will consider about it, thx.

>> +static unsigned int ovl_split_lowerdirs(char *lower)
>> +{
>> +       unsigned int ctr = 1;
>> +       char *s, *d;
>> +
>> +       for (s = d = lower;; s++, d++) {
>> +               if (*s == '\\') {
>> +                       s++;
>> +               } else if (*s == ':') {
>> +                       *d = '\0';
>> +                       ctr++;
>> +                       continue;
>> +               }
>> +               *d = *s;
>> +               if (!*s)
>> +                       break;
>> +       }
>> +       return ctr;
>> +}
>> +
>> +/* Resolve each lower directories and check the validity */
>> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
>> +                         unsigned int *lowernum)
>> +{
>> +       unsigned int num;
>> +       char **dirs;
>> +       char *p;
>> +       int i;
>> +
>> +       num = ovl_split_lowerdirs(loweropt);
>> +       if (num > OVL_MAX_STACK) {
>> +               print_err(_("Too many lower directories:%u, max:%u\n"),
>> +                           num, OVL_MAX_STACK);
>> +               return -1;
>> +       }
>> +
>> +       dirs = smalloc(sizeof(char *) * num);
>> +
>> +       p = loweropt;
>> +       for (i = 0; i < num; i++) {
>> +               dirs[i] = smalloc(PATH_MAX);
>> +               if (!realpath(p, dirs[i])) {
>> +                       print_err(_("Failed to resolve lowerdir:%s:%s\n"),
>> +                                   p, strerror(errno));
>> +                       goto err;
>> +               }
>> +               print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
>> +               p = strchr(p, '\0') + 1;
>> +       }
>> +
>> +       *lowerdir = dirs;
>> +       *lowernum = num;
>> +
>> +       return 0;
>> +err:
>> +       for (i--; i >= 0; i--)
>> +               free(dirs[i]);
>> +       free(dirs);
>> +       *lowernum = 0;
>> +       return -1;
>> +}
>> +
>> +/*
>> + * Split and return next opt.
>> + * (copied from linux kernel, see fs/overlayfs/super.c)
>> + */
>> +static char *ovl_next_opt(char **s)
>> +{
>> +       char *sbegin = *s;
>> +       char *p;
>> +
>> +       if (sbegin == NULL)
>> +               return NULL;
>> +
>> +       for (p = sbegin; *p; p++) {
>> +               if (*p == '\\') {
>> +                       p++;
>> +                       if (!*p)
>> +                               break;
>> +               } else if (*p == ',') {
>> +                       *p = '\0';
>> +                       *s = p + 1;
>> +                       return sbegin;
>> +               }
>> +       }
>> +       *s = NULL;
>> +       return sbegin;
>> +}
>> +
>> +/*
>> + * Split and parse opt to each directories.
>> + *
>> + * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
>> + * mounted use relative path, so there may have misjudgment.
>> + */
>> +static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
>> +{
>> +       char tmp[PATH_MAX] = {0};
>> +       char *p;
>> +       int len;
>> +       int ret;
>> +       int i;
>> +
>> +       while ((p = ovl_next_opt(&opt)) != NULL) {
>> +               if (!*p)
>> +                       continue;
>> +
>> +               if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
>> +                       len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
>> +                       strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
>> +
>> +                       if (!realpath(tmp, entry->upperdir)) {
>> +                               print_err(_("Faile to resolve path:%s:%s\n"),
>> +                                           tmp, strerror(errno));
>> +                               ret = -1;
>> +                               goto errout;
>> +                       }
>> +               } else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
>> +                       len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
>> +                       entry->lowers = smalloc(len);
>> +                       strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
>> +
>> +                       if ((ret = ovl_resolve_lowerdirs(entry->lowers,
>> +                                       &entry->lowerdir, &entry->lowernum)))
>> +                               goto errout;
>> +
>> +               } else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
>> +                       len = strlen(p) - strlen(OPT_WORKDIR) + 1;
>> +                       strncpy(tmp, p+strlen(OPT_WORKDIR), len);
>> +
>> +                       if (!realpath(tmp, entry->workdir)) {
>> +                               print_err(_("Faile to resolve path:%s:%s\n"),
>> +                                           tmp, strerror(errno));
>> +                               ret = -1;
>> +                               goto errout;
>> +                       }
>> +               }
>> +       }
>> +
>> +errout:
>> +       if (entry->lowernum) {
>> +               for (i = 0; i < entry->lowernum; i++)
>> +                       free(entry->lowerdir[i]);
>> +               free(entry->lowerdir);
>> +               entry->lowerdir = NULL;
>> +               entry->lowernum = 0;
>> +       }
>> +       free(entry->lowers);
>> +       entry->lowers = NULL;
>> +
>> +       return ret;
>> +}
>> +
>> +/* Scan current mounted overlayfs and get used underlying directories */
>> +static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
>> +                              int *ovl_mnt_count)
>> +{
>> +       struct ovl_mnt_entry *mnt_entries;
>> +       struct mntent *mnt;
>> +       FILE *fp;
>> +       char *opt;
>> +       int allocated, num = 0;
>> +
>> +       fp = setmntent(MOUNT_TAB, "r");
>> +       if (!fp) {
>> +               print_err(_("Fail to setmntent %s:%s\n"),
>> +                           MOUNT_TAB, strerror(errno));
>> +               return -1;
>> +       }
>> +
>> +       allocated = ALLOC_NUM;
>> +       mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
>> +
>> +       while ((mnt = getmntent(fp))) {
>> +               if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
>> +                   !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
>> +
>> +                       opt = sstrdup(mnt->mnt_opts);
>> +                       if (ovl_parse_opt(opt, &mnt_entries[num])) {
>> +                               free(opt);
>> +                               continue;
>> +                       }
>> +
>> +                       num++;
>> +                       if (num % ALLOC_NUM == 0) {
>> +                               allocated += ALLOC_NUM;
>> +                               mnt_entries = srealloc(mnt_entries,
>> +                                    sizeof(struct ovl_mnt_entry) * allocated);
>> +                       }
>> +
>> +                       free(opt);
>> +               }
>> +       }
>> +
>> +       *ovl_mnt_entries = mnt_entries;
>> +       *ovl_mnt_count = num;
>> +
>> +       endmntent(fp);
>> +       return 0;
>> +}
>> +
>> +static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
>> +                               int ovl_mnt_count)
>> +{
>> +       int i,j;
>> +
>> +       for (i = 0; i < ovl_mnt_count; i++) {
>> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
>> +                       free(ovl_mnt_entries[i].lowerdir[j]);
>> +               free(ovl_mnt_entries[i].lowerdir);
>> +               free(ovl_mnt_entries[i].lowers);
>> +       }
>> +       free(ovl_mnt_entries);
>> +}
>> +
>> +/*
>> + * Scan every mounted filesystem, check the overlay directories want
>> + * to check is already mounted. Check and fix an online overlay is not
>> + * allowed.
>> + *
>> + * Note: fsck may modify lower layers, so even match only one directory
>> + *       is triggered as mounted.
>> + */
>> +int ovl_check_mount(bool *pass)
>> +{
>> +       struct ovl_mnt_entry *ovl_mnt_entries;
>> +       int ovl_mnt_entry_count;
>> +       char *mounted_path;
>> +       bool mounted;
>> +       int i,j,k;
>> +       int ret;
>> +
>> +       ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
>> +       if (ret)
>> +               return ret;
>> +
>> +       /* Only check hard matching */
>> +       for (i = 0; i < ovl_mnt_entry_count; i++) {
>> +               /* Check lower */
>> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
>> +                       for (k = 0; k < lower_num; k++) {
>> +                               if (!strcmp(lowerdir[k],
>> +                                           ovl_mnt_entries[i].lowerdir[j])) {
>> +                                       mounted_path = lowerdir[k];
>> +                                       mounted = true;
>> +                                       goto out;
>> +                               }
>> +                       }
>> +               }
>> +
>> +               /* Check upper */
>> +               if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
>> +                       mounted_path = upperdir;
>> +                       mounted = true;
>> +                       goto out;
>> +               }
>> +
>> +               /* Check worker */
>> +               if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
>> +                       mounted_path = workdir;
>> +                       mounted = true;
>> +                       goto out;
>> +               }
>> +       }
>> +out:
>> +       ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
>> +
>> +       if (mounted)
>> +               print_info(_("Dir %s is mounted\n"), mounted_path);
>> +       *pass = !mounted;
>> +
>> +       return 0;
>> +}
>> diff --git a/mount.h b/mount.h
>> new file mode 100644
>> index 0000000..8a3762d
>> --- /dev/null
>> +++ b/mount.h
>> @@ -0,0 +1,8 @@
>> +#ifndef OVL_MOUNT_H
>> +#define OVL_MOUNT_H
>> +
>> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
>> +                          unsigned int *lowernum);
>> +int ovl_check_mount(bool *mounted);
>> +
>> +#endif /* OVL_MOUNT_H */
>> diff --git a/test/README.md b/test/README.md
>> new file mode 100644
>> index 0000000..71dbad8
>> --- /dev/null
>> +++ b/test/README.md
>> @@ -0,0 +1,12 @@
>> +fsck.overlay test
>> +=================
>> +
>> +This test cases simulate different inconsistency of overlay filesystem
>> +underlying directories, test fsck.overlay can repair them correctly or not.
>> +
>> +USAGE
>> +=====
>> +
>> +1. Check "local.config", modify config value to appropriate one.
>> +2. ./auto_test.sh
>> +3. After testing, the result file and log file created in the result directory.
> 
> This really sounds like a mini fork of xfstests.
> I'd prefer if those tests went into xfstests and used fsck.overlay either as an
> external program (like the rest of fsck.*) or as in-house test helper.
> 
>> diff --git a/test/auto_test.sh b/test/auto_test.sh
>> new file mode 100755
>> index 0000000..8248e24
>> --- /dev/null
>> +++ b/test/auto_test.sh
>> @@ -0,0 +1,46 @@
>> +#!/bin/bash
>> +
>> +. ./src/prepare.sh
>> +
>> +echo "***************"
>> +
>> +# 1. Test whiteout
>> +echo -e "1.Test Whiteout\n"
>> +$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
>> +if [ $? -ne 0 ]; then
>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>> +       RESULT="Fail"
>> +else
>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>> +       RESULT="Pass"
>> +fi
>> +
>> +echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
>> +
>> +# 2. Test opaque dir
>> +echo -e "2.Test opaque directory\n"
>> +$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
>> +if [ $? -ne 0 ]; then
>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>> +       RESULT="Fail"
>> +else
>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>> +       RESULT="Pass"
>> +fi
>> +
>> +echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
>> +
>> +# 3. Test redirect dir
>> +echo -e "3.Test redirect direcotry\n"
>> +$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
>> +if [ $? -ne 0 ]; then
>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>> +       RESULT="Fail"
>> +else
>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>> +       RESULT="Pass"
>> +fi
>> +
>> +echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
>> +
>> +echo "***************"
>> diff --git a/test/clean.sh b/test/clean.sh
>> new file mode 100755
>> index 0000000..80f360b
>> --- /dev/null
>> +++ b/test/clean.sh
>> @@ -0,0 +1,6 @@
>> +#!/bin/bash
>> +
>> +. ./src/prepare.sh
>> +
>> +rm -rf $RESULT_DIR
>> +rm -rf $TEST_DIR
>> diff --git a/test/local.config b/test/local.config
>> new file mode 100644
>> index 0000000..990395f
>> --- /dev/null
>> +++ b/test/local.config
>> @@ -0,0 +1,16 @@
>> +# Config
>> +export OVL_FSCK=fsck.overlay
>> +export TEST_DIR=/mnt/test
>> +
>> +# Base environment
>> +export PWD=`pwd`
>> +export RESULT_DIR=$PWD/result
>> +export SOURCE_DIR=$PWD/src
>> +
>> +# Overlayfs config
>> +export LOWER_DIR="lower"
>> +export UPPER_DIR="upper"
>> +export WORK_DIR="worker"
> 
> worker? this is a strange name for work dir location..
> it does not have the same meaning in English as upp-er and low-er.
> 
sorry, clerical error, will fix it.

>> +export MERGE_DIR="merge"
>> +export LOWER_DIR1="lower1"
>> +export WORK_DIR1="worker1"
>> diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
>> new file mode 100755
>> index 0000000..7cb5030
>> --- /dev/null
>> +++ b/test/src/opaque_test.sh
>> @@ -0,0 +1,144 @@
>> +#!/bin/bash
>> +
>> +. ./local.config
>> +
>> +__clean()
>> +{
>> +       rm -rf $TEST_DIR/*
>> +       cd $PWD
>> +}
>> +
>> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
>> +OVL_OPAQUE_XATTR_VAL="y"
>> +RET=0
>> +
>> +cd $TEST_DIR
>> +rm -rf *
>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>> +
>> +# 1.Test lower invalid opaque directory
>> +echo "***** 1.Test lower invalid opaque directory *****"
>> +mkdir $LOWER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: lower's invalid opaque xattr not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 2.Test upper invalid opaque directory
>> +echo "***** 2.Test upper invalid opaque directory *****"
>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: upper's invalid opaque xattr not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +
>> +# 3.Test upper opaque direcotry in merged directory
>> +echo "***** 3.Test upper opaque direcotry in merged directory *****"
>> +mkdir $UPPER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +
>> +# 4.Test upper opaque direcotry cover lower file
>> +echo "***** 4.Test upper opaque direcotry cover lower file *****"
>> +mkdir $UPPER_DIR/testdir
>> +mkdir $LOWER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 5.Test upper opaque direcotry cover lower directory
>> +echo "***** 5.Test upper opaque direcotry cover lower directory *****"
>> +mkdir $UPPER_DIR/testdir
>> +touch $LOWER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 6.Test lower invalid opaque directory in middle layer
>> +echo "***** 6.Test lower invalid opaque directory in middle layer *****"
>> +mkdir $LOWER_DIR/testdir0/testdir1
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: middle's opaque xattr not removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 7.Test lower invalid opaque directory in bottom layer
>> +echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
>> +mkdir $LOWER_DIR1/testdir0/testdir1
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: middle's opaque xattr not removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $LOWER_DIR1/*
>> +
>> +# 8.Test middle opaque direcotry cover bottom directory
>> +echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
>> +mkdir $LOWER_DIR1/testdir
>> +mkdir $LOWER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: middle's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $LOWER_DIR1/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 9.Test double opaque direcotry in middle and upper layer
>> +echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
>> +mkdir $LOWER_DIR1/testdir
>> +mkdir $LOWER_DIR/testdir
>> +mkdir $UPPER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: middle's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $LOWER_DIR1/*
>> +rm -rf $LOWER_DIR/*
>> +rm -rf $UPPER_DIR/*
>> +
>> +__clean
>> +exit $RET
>> diff --git a/test/src/prepare.sh b/test/src/prepare.sh
>> new file mode 100755
>> index 0000000..138539a
>> --- /dev/null
>> +++ b/test/src/prepare.sh
>> @@ -0,0 +1,15 @@
>> +#!/bin/bash
>> +
>> +. ./local.config
>> +
>> +NOW=`date +"%Y-%m-%d-%H-%M-%S"`
>> +LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
>> +RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
>> +
>> +# creat test base dir
>> +if [ ! -d "$TEST_DIR" ]; then
>> +       mkdir -p $TEST_DIR
>> +fi
>> +
>> +# creat result dir
>> +mkdir -p $RESULT_DIR
>> diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
>> new file mode 100755
>> index 0000000..be2ce80
>> --- /dev/null
>> +++ b/test/src/redirect_test.sh
>> @@ -0,0 +1,163 @@
>> +#!/bin/bash
>> +
>> +. ./local.config
>> +
>> +__clean()
>> +{
>> +       rm -rf $TEST_DIR/*
>> +       cd $PWD
>> +}
>> +
>> +OVL_REDIRECT_XATTR="trusted.overlay.redirect"
>> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
>> +RET=0
>> +
>> +cd $TEST_DIR
>> +rm -rf *
>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>> +
>> +# 1.Test no exist redirect origin
>> +echo "***** 1.Test no exist redirect origin *****"
>> +mkdir $UPPER_DIR/testdir
>> +setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: upper's redirect xattr not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +
>> +# 2.Test redirect origin is file
>> +echo "***** 2.Test redirect origin is file *****"
>> +mkdir $UPPER_DIR/testdir
>> +touch $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: upper's redirect xattr not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 3.Test valid redirect xattr
>> +echo "***** 3.Test valid redirect xattr *****"
>> +mkdir $UPPER_DIR/testdir
>> +mknod $UPPER_DIR/origin c 0 0
>> +mkdir $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 4.Test valid redirect xattr start from root
>> +echo "***** 4.Test valid redirect xattr start from root *****"
>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>> +mknod $UPPER_DIR/origin c 0 0
>> +mkdir $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 5.Test missing whiteout in redirect parent directory
>> +echo "***** 5.Test missing whiteout in redirect parent directory *****"
>> +mkdir $UPPER_DIR/testdir
>> +mkdir $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +if [ ! -e "$UPPER_DIR/origin" ];then
>> +       echo "ERROR: upper's missing whiteout not create"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $UPPER_DIR/*
>> +
>> +# 6.Test missing opaque in redirect parent directory
>> +echo "***** 6.Test missing opaque in redirect parent directory *****"
>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>> +mkdir $UPPER_DIR/origin
>> +mkdir $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's missing opaque not set"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 7.Test duplicate redirect directory in one layer
>> +echo "***** 7.Test duplicate redirect directory in one layer *****"
>> +mkdir $UPPER_DIR/testdir0
>> +mkdir $UPPER_DIR/testdir1
>> +mknod $UPPER_DIR/origin c 0 0
>> +mkdir $LOWER_DIR/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +if [[ $? -eq 0 ]];then
>> +       echo "ERROR: fsck check incorrect pass"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +
>> +# 8.Test duplicate redirect directory in different layer
>> +echo "***** 8.Test duplicate redirect directory in different layer *****"
>> +mkdir $UPPER_DIR/testdir0
>> +mkdir $LOWER_DIR/testdir1
>> +mkdir $LOWER_DIR1/origin
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
>> +if [[ $? -ne 0 ]];then
>> +       echo "ERROR: lower's redirect xattr incorrect removed"
>> +       RET=$(($RET+1))
>> +fi
>> +
>> +if [ ! -e "$LOWER_DIR/origin" ];then
>> +       echo "ERROR: upper's missing whiteout not create"
>> +       RET=$(($RET+1))
>> +fi
>> +if [ ! -e "$LOWER_DIR/origin" ];then
>> +       echo "ERROR: lower's missing whiteout not create"
>> +       RET=$(($RET+1))
>> +fi
>> +
>> +rm -rf $UPPER_DIR/*
>> +rm -rf $LOWER_DIR/*
>> +rm -rf $LOWER_DIR1/*
>> +
>> +__clean
>> +
>> +exit $RET
>> diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
>> new file mode 100755
>> index 0000000..c0e1555
>> --- /dev/null
>> +++ b/test/src/whiteout_test.sh
>> @@ -0,0 +1,63 @@
>> +#!/bin/bash
>> +
>> +. ./local.config
>> +
>> +__clean()
>> +{
>> +       rm -rf $TEST_DIR/*
>> +       cd $PWD
>> +}
>> +
>> +RET=0
>> +
>> +cd $TEST_DIR
>> +rm -rf *
>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>> +
>> +# 1.Test lower orphan whiteout
>> +echo "***** 1.Test lower orphan whiteout *****"
>> +mknod $LOWER_DIR/foo c 0 0
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +if [ -e "$LOWER_DIR/foo" ];then
>> +       echo "lower's whiteout not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -f $LOWER_DIR/*
>> +
>> +# 2.Test upper orphan whiteout
>> +echo "***** 2.Test upper orphan whiteout *****"
>> +mknod $UPPER_DIR/foo c 0 0
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +if [ -e "$UPPER_DIR/foo" ];then
>> +       echo "upper's whiteout not remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -f $UPPER_DIR/*
>> +
>> +# 3.Test upper inuse whiteout
>> +echo "***** 3.Test upper inuse whiteout *****"
>> +touch $LOWER_DIR/foo
>> +mknod $UPPER_DIR/foo c 0 0
>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +if [ ! -e "$UPPER_DIR/foo" ];then
>> +       echo "upper's whiteout incorrect remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -f $UPPER_DIR/*
>> +rm -f $LOWER_DIR/*
>> +
>> +# 4.Test lower inuse whiteout
>> +echo "***** 4.Test lower inuse whiteout *****"
>> +touch $LOWER_DIR/foo
>> +mknod $LOWER_DIR1/foo c 0 0
>> +$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>> +if [ ! -e "$LOWER_DIR1/foo" ];then
>> +       echo "lower's whiteout incorrect remove"
>> +       RET=$(($RET+1))
>> +fi
>> +rm -f $LOWER_DIR1/*
>> +rm -f $LOWER_DIR/*
>> +
>> +__clean
>> +
>> +exit $RET
>> --
>> 2.9.5
>>
> 
> .
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-17 18:39   ` Darrick J. Wong
@ 2017-11-20  7:12     ` zhangyi (F)
  0 siblings, 0 replies; 19+ messages in thread
From: zhangyi (F) @ 2017-11-20  7:12 UTC (permalink / raw)
  To: Darrick J. Wong, Amir Goldstein
  Cc: overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

On 2017/11/18 2:39, Darrick J. Wong Wrote:
> On Fri, Nov 17, 2017 at 07:13:23PM +0200, Amir Goldstein wrote:
>> [adding fstests in CC with full patch inline to collect wisdom from
>> other fs developers]
>>
>> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>>> Hi,
>>>
>>> Here is the origin version of fsck.overlay utility I mentioned last
>>> week. It scan each underlying directory and check orphan whiteout,
>>> invalid opaque xattr and invalid redirect xattr. We can use it to
>>> check filesystem inconsistency before mount overlayfs to avoid some
>>> unexpected results.
>>
>> Thanks for working on this!
>> It looks like a great start.
>>
>>>
>>> This patch can am to an empty git repository. I have already do basic
>>> test list in 'test' directory. Please do a review give some suggestions.
>>> Thanks a lot.
>>
>> Please consider, instead of writing overlay tests in new repo,
>> to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
>> _check_scratch_fs.
>> See for example very basic index sanity checks I implemented here:
>> https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
>>
>> It may be useful, if Eryu agrees, to merge the "incubator" version of
>> fsck.overlayfs
>> into xfstests as a helper program until it starts getting packaged and
>> distributed.
>> Please consider this option.
>>
>>>
>>> Thanks,
>>> Yi.
>>>
>>> ---------------------------
>>>
>>> fsck.overlay
>>> ============
>>>
>>> fsck.overlay is used to check and optionally repair underlying
>>> directories of overlay-filesystem.
>>>
>>> Check the following points:
>>>
>>> Whiteouts
>>> ---------
>>>
>>> A whiteout is a character device with 0/0 device number. It is used to
>>> record the removed files or directories, When a whiteout is found in a
>>> directory, there should be at least one directory or file with the same
>>> name in any of the corresponding lower layers. If not exist, the whiteout
>>> will be treated as orphan whiteout and remove.
>>>
>>> Opaque directories
>>> ------------------
>>>
>>> An opaque directory is a directory with "trusted.overlay.opaque" xattr
>>> valued "y". There are two legal situations of making opaque directory: 1)
>>> create directory over whiteout 2) creat directory in merged directory. If an
>>> opaque directory is found, corresponding matching name in lower layers might
>>> exist or parent directory might merged, If not, the opaque xattr will be
>>> treated as invalid and remove.
>>>
>>> Redirect directories
>>> --------------------
>>>
>>> An redirect directory is a directory with "trusted.overlay.redirect"
>>> xattr valued to the path of the original location from the root of the
>>> overlay. It is only used when renaming a directory and "redirect dir"
>>> feature is enabled. If an redirect directory is found, the following
>>> must be met:
>>>
>>> 1) The directory store in redirect xattr should exist in one of lower
>>> layers.
>>> 2) The origin directory should be redirected only once in one layer,
>>> which mean there is only one redirect xattr point to this origin directory in
>>> the specified layer.
>>> 3) A whiteout or an opaque directory with the same name to origin should
>>> exist in the same directory as the redirect directory.
>>>
>>> If not, 1) The redirect xattr is invalid and need to remove 2) One of
>>> the redirect xattr is redundant but not sure which one is, ask user 3)
>>> Create a whiteout device or set opaque xattr to an existing directory if the
>>> parent directory was meregd or remove xattr if not.
>>>
>>> Usage
>>> =====
>>>
>>> 1. Ensure overlay filesystem is not mounted based on directories which
>>> need to check.
>>>
>>> 2. Run fsck.overlay program:
>>>    Usage:
>>>    fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
>>>
>>>    Options:
>>>    -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
>>>                              multiple lower use ':' as separator.
>>>    -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
>>>    -w, --workdir=WORKDIR     specify work directory of overlayfs
>>>    -a, --auto                repair automatically (no questions)
>>
>> Let stick with e2fsck conventions when possible.
>> -a should be -y and we should definitely have -n, because this is
>> how xfstest would run it.
> 
> If it's e2fsck conventions you want, then consider that e2fsck has three
> action options -- -n (fix nothing), -y (fix everything), and -p (fix the
> easy stuff that doesn't require interaction); and -a maps to -p.
> 
> In other words, think about whether or not repairs to overlayfs could
> someday be classified into easy fixes vs. difficult fixes.  Even if that
> never happens, if you're going to mirror e2fsck then you might as well
> mirror the exact options behavior.
> 

Thanks for your suggestion, I will add -n, -y and -p options and distinguish
between -y and -p.

Thanks,
Yi.

> 
>>
>>>    -v, --verbose             print more messages of overlayfs
>>>    -h, --help                display this usage of overlayfs
>>>    -V, --version             display version information
>>>
>>> 3. Exit value:
>>>    0      No errors
>>>    1      Filesystem errors corrected
>>>    2      System should be rebooted
>>>    4      Filesystem errors left uncorrected
>>>    8      Operational error
>>>    16     Usage or syntax error
>>>    32     Checking canceled by user request
>>>    128    Shared-library error
>>>
>>> Todo
>>> ====
>>>
>>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>> online. Now, We cannot distinguish mounted directories if overlayfs was
>>> mounted with relative path.
>>
>> This should be handled by kernel.
>> We now already grab an advisory exclusive I_OVL_INUSE lock on both
>> upperdir and workdir.
>> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
>> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
>> Read the man page section on O_EXCL and block device. This is how
>> e2fsck and friends get exclusive access w.r.t mount.
>>
>>> 2. Symbolic link check.
>>> 3. Check origin/impure/nlink xattr.
>>> 4. ...
>>>
>>> Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
>>> ---
>>>  Makefile                  |  31 +++
>>>  README.md                 |  88 +++++++++
>>>  check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
>>>  check.h                   |   7 +
>>>  common.c                  |  95 +++++++++
>>>  common.h                  |  34 ++++
>>>  config.h                  |  22 +++
>>>  fsck.c                    | 179 +++++++++++++++++
>>>  lib.c                     | 197 +++++++++++++++++++
>>>  lib.h                     |  73 +++++++
>>>  mount.c                   | 319 ++++++++++++++++++++++++++++++
>>>  mount.h                   |   8 +
>>>  test/README.md            |  12 ++
>>>  test/auto_test.sh         |  46 +++++
>>>  test/clean.sh             |   6 +
>>>  test/local.config         |  16 ++
>>>  test/src/opaque_test.sh   | 144 ++++++++++++++
>>>  test/src/prepare.sh       |  15 ++
>>>  test/src/redirect_test.sh | 163 ++++++++++++++++
>>>  test/src/whiteout_test.sh |  63 ++++++
>>>  20 files changed, 2000 insertions(+)
>>>  create mode 100644 Makefile
>>>  create mode 100644 README.md
>>>  create mode 100644 check.c
>>>  create mode 100644 check.h
>>>  create mode 100644 common.c
>>>  create mode 100644 common.h
>>>  create mode 100644 config.h
>>>  create mode 100644 fsck.c
>>>  create mode 100644 lib.c
>>>  create mode 100644 lib.h
>>>  create mode 100644 mount.c
>>>  create mode 100644 mount.h
>>>  create mode 100644 test/README.md
>>>  create mode 100755 test/auto_test.sh
>>>  create mode 100755 test/clean.sh
>>>  create mode 100644 test/local.config
>>>  create mode 100755 test/src/opaque_test.sh
>>>  create mode 100755 test/src/prepare.sh
>>>  create mode 100755 test/src/redirect_test.sh
>>>  create mode 100755 test/src/whiteout_test.sh
>>>
>>> diff --git a/Makefile b/Makefile
>>> new file mode 100644
>>> index 0000000..ced5005
>>> --- /dev/null
>>> +++ b/Makefile
>>> @@ -0,0 +1,31 @@
>>> +CFLAGS = -Wall -g
>>> +LFLAGS = -lm
>>> +CC = gcc
>>> +
>>> +all: overlay
>>> +
>>> +overlay: fsck.o common.o lib.o check.o mount.o
>>> +       $(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
>>> +
>>> +fsck.o:
>>> +       $(CC) $(CFLAGS) -c fsck.c
>>> +
>>> +common.o:
>>> +       $(CC) $(CFLAGS) -c common.c
>>> +
>>> +lib.o:
>>> +       $(CC) $(CFLAGS) -c lib.c
>>> +
>>> +check.o:
>>> +       $(CC) $(CFLAGS) -c check.c
>>> +
>>> +mount.o:
>>> +       $(CC) $(CFLAGS) -c mount.c
>>> +
>>> +clean:
>>> +       rm -f *.o fsck.overlay
>>> +       rm -rf bin
>>> +
>>> +install: all
>>> +       mkdir bin
>>> +       cp fsck.overlay bin
>>> diff --git a/README.md b/README.md
>>> new file mode 100644
>>> index 0000000..8de69cd
>>> --- /dev/null
>>> +++ b/README.md
>>> @@ -0,0 +1,88 @@
>>> +fsck.overlay
>>> +============
>>> +
>>> +fsck.overlay is used to check and optionally repair underlying directories
>>> +of overlay-filesystem.
>>> +
>>> +Check the following points:
>>> +
>>> +Whiteouts
>>> +---------
>>> +
>>> +A whiteout is a character device with 0/0 device number. It is used to record
>>> +the removed files or directories, When a whiteout is found in a directory,
>>> +there should be at least one directory or file with the same name in any of the
>>> +corresponding lower layers. If not exist, the whiteout will be treated as orphan
>>> +whiteout and remove.
>>> +
>>> +
>>> +Opaque directories
>>> +------------------
>>> +
>>> +An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
>>> +"y". There are two legal situations of making opaque directory: 1) create
>>> +directory over whiteout 2) creat directory in merged directory. If an opaque
>>> +directory is found, corresponding matching name in lower layers might exist or
>>> +parent directory might merged, If not, the opaque xattr will be treated as
>>> +invalid and remove.
>>> +
>>> +
>>> +Redirect directories
>>> +--------------------
>>> +
>>> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
>>> +valued to the path of the original location from the root of the overlay. It
>>> +is only used when renaming a directory and "redirect dir" feature is enabled.
>>> +If an redirect directory is found, the following must be met:
>>> +
>>> +1) The directory store in redirect xattr should exist in one of lower layers.
>>> +2) The origin directory should be redirected only once in one layer, which mean
>>> +   there is only one redirect xattr point to this origin directory in the
>>> +   specified layer.
>>> +3) A whiteout or an opaque directory with the same name to origin should exist
>>> +   in the same directory as the redirect directory.
>> another redirected upper dir can also be covering the redirect lower target
>>
>>> +
>>> +If not, 1) The redirect xattr is invalid and need to remove 2) One of the
>>> +redirect xattr is redundant but not sure which one is, ask user 3) Create a
>>> +whiteout device or set opaque xattr to an existing directory if the parent
>>> +directory was meregd or remove xattr if not.
>>
>> You can consult origin xattr, decode it and fix redirect accordingly.
>> I have plans to implement this in kernel as well with 'verify_dir' mount option,
>> but maybe is it better to do this with fsck tool.
>> Restoring unset origin xattr, which I also sent a kernel patch for can also be
>> done by fsck as Miklos also suggested.
>>
>>
>>> +
>>> +Usage
>>> +=====
>>> +
>>> +1. Ensure overlay filesystem is not mounted based on directories which need to
>>> +   check.
>>> +
>>> +2. Run fsck.overlay program:
>>> +   Usage:
>>> +   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
>>> +
>>> +   Options:
>>> +   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
>>> +                             multiple lower use ':' as separator.
>>> +   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
>>> +   -w, --workdir=WORKDIR     specify work directory of overlayfs
>>> +   -a, --auto                repair automatically (no questions)
>>> +   -v, --verbose             print more messages of overlayfs
>>> +   -h, --help                display this usage of overlayfs
>>> +   -V, --version             display version information
>>> +
>>> +3. Exit value:
>>> +   0      No errors
>>> +   1      Filesystem errors corrected
>>> +   2      System should be rebooted
>>> +   4      Filesystem errors left uncorrected
>>> +   8      Operational error
>>> +   16     Usage or syntax error
>>> +   32     Checking canceled by user request
>>> +   128    Shared-library error
>>> +
>>> +Todo
>>> +====
>>> +
>>> +1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>> +   online. Now, We cannot distinguish mounted directories if overlayfs was
>>> +   mounted with relative path.
>>> +2. Symbolic link check.
>>> +3. Check origin/impure/nlink xattr.
>>> +4. ...
>>> diff --git a/check.c b/check.c
>>> new file mode 100644
>>> index 0000000..1794501
>>> --- /dev/null
>>> +++ b/check.c
>>> @@ -0,0 +1,482 @@
>>> +/*
>>> + *
>>> + *     Check and fix inconsistency for all underlying layers of overlay
>>> + *
>>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>>
>> I guess you are not obliged to, but it would probably be better for
>> you to assign
>> copyright either to you or to your employer.
>> And choosing a license explicitly is also recommended.
>> IMO it would be best if you could keep at least part of the code
>> under LGPL (or a like) and start with a mini liboverlay that can grow over time
>> and be used by other projects.
>>
>>> + *
>>> + */
>>> +
>>> +#include <stdlib.h>
>>> +#include <stdio.h>
>>> +#include <string.h>
>>> +#include <errno.h>
>>> +#include <unistd.h>
>>> +#include <stdbool.h>
>>> +#include <sys/types.h>
>>> +#include <sys/xattr.h>
>>> +#include <sys/stat.h>
>>> +#include <linux/limits.h>
>>> +
>>> +#include "common.h"
>>> +#include "lib.h"
>>> +#include "check.h"
>>> +
>>> +/* Underlying information */
>>> +struct ovl_lower_check {
>>> +       unsigned int type;      /* check extent type */
>>> +
>>> +       bool exist;
>>> +       char path[PATH_MAX];    /* exist pathname found, only valid if exist */
>>> +       struct stat st;         /* only valid if exist */
>>> +};
>>> +
>>> +/* Redirect information */
>>> +struct ovl_redirect_entry {
>>> +       struct ovl_redirect_entry *next;
>>> +
>>> +       char origin[PATH_MAX];  /* origin directory path */
>>> +
>>> +       char path[PATH_MAX];    /* redirect directory */
>>> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
>>> +       int dirnum;             /* only valid when dirtype==OVL_LOWER */
>>> +};
>>> +
>>> +/* Whiteout */
>>> +#define WHITEOUT_DEV   0
>>> +#define WHITEOUT_MOD   0
>>> +
>>> +extern char **lowerdir;
>>> +extern char upperdir[];
>>> +extern char workdir[];
>>> +extern unsigned int lower_num;
>>> +extern int flags;
>>> +extern int status;
>>> +
>>> +static inline mode_t file_type(const struct stat *status)
>>> +{
>>> +       return status->st_mode & S_IFMT;
>>> +}
>>> +
>>> +static inline bool is_whiteout(const struct stat *status)
>>> +{
>>> +       return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
>>> +}
>>> +
>>> +static inline bool is_dir(const struct stat *status)
>>> +{
>>> +       return file_type(status) == S_IFDIR;
>>> +}
>>> +
>>> +static bool is_dir_xattr(const char *pathname, const char *xattrname)
>>> +{
>>> +       char val;
>>> +       ssize_t ret;
>>> +
>>> +       ret = getxattr(pathname, xattrname, &val, 1);
>>> +       if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
>>> +               print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
>>> +                           xattrname, strerror(errno));
>>> +               return false;
>>> +       }
>>> +
>>> +       return (ret == 1 && val == 'y') ? true : false;
>>> +}
>>> +
>>> +static inline bool ovl_is_opaque(const char *pathname)
>>> +{
>>> +       return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
>>> +}
>>> +
>>> +static inline int ovl_remove_opaque(const char *pathname)
>>> +{
>>> +       return remove_xattr(pathname, OVL_OPAQUE_XATTR);
>>> +}
>>> +
>>> +static inline int ovl_set_opaque(const char *pathname)
>>> +{
>>> +       return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
>>> +}
>>> +
>>> +static int ovl_get_redirect(const char *pathname, size_t dirlen,
>>> +                           size_t filelen, char **redirect)
>>> +{
>>> +       char *buf = NULL;
>>> +       ssize_t ret;
>>> +
>>> +       ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
>>> +       if (ret <= 0 || !buf)
>>> +               return ret;
>>> +
>>> +       if (buf[0] != '/') {
>>> +               size_t baselen = strlen(pathname)-filelen-dirlen;
>>> +
>>> +               buf = srealloc(buf, ret + baselen + 1);
>>> +               memmove(buf + baselen, buf, ret);
>>> +               memcpy(buf, pathname+dirlen, baselen);
>>> +               buf[ret + baselen] = '\0';
>>> +       }
>>> +
>>> +       *redirect = buf;
>>> +       return 0;
>>> +}
>>> +
>>> +static inline int ovl_remove_redirect(const char *pathname)
>>> +{
>>> +       return remove_xattr(pathname, OVL_REDIRECT_XATTR);
>>> +}
>>> +
>>> +static inline int ovl_create_whiteout(const char *pathname)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
>>> +       if (ret)
>>> +               print_err(_("Cannot mknod %s:%s\n"),
>>> +                           pathname, strerror(errno));
>>> +       return ret;
>>> +}
>>> +
>>> +/*
>>> + * Scan each lower dir lower than 'start' and check type matching,
>>> + * we stop scan if we found something.
>>> + *
>>> + * skip: skip whiteout.
>>> + *
>>> + */
>>> +static int ovl_check_lower(const char *path, unsigned int start,
>>> +                          struct ovl_lower_check *chk, bool skip)
>>> +{
>>> +       char lower_path[PATH_MAX];
>>> +       struct stat st;
>>> +       unsigned int i;
>>> +
>>> +       for (i = start; i < lower_num; i++) {
>>> +               snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
>>> +
>>> +               if (lstat(lower_path, &st) != 0) {
>>
>> IMO it would be better to keep open fd for lower layers root and use fstatat()
>> with layer root fd.
>>
>>> +                       if (errno != ENOENT && errno != ENOTDIR) {
>>> +                               print_err(_("Cannot stat %s: %s\n"),
>>> +                                           lower_path, strerror(errno));
>>> +                               return -1;
>>> +                       }
>>> +                       continue;
>>> +               }
>>> +
>>> +               if (skip && is_whiteout(&st))
>>> +                       continue;
>>> +
>>> +               chk->exist = true;
>>> +               chk->st = st;
>>> +               strncpy(chk->path, lower_path, sizeof(chk->path));
>>> +               break;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +/*
>>> + * Scan each underlying dirs under specified dir if a whiteout is
>>> + * found, check it's orphan or not. In auto-mode, orphan whiteouts
>>> + * will be removed directly.
>>> + */
>>> +static int ovl_check_whiteout(struct scan_ctx *sctx)
>>> +{
>>> +       const char *pathname = sctx->pathname;
>>> +       const struct stat *st = sctx->st;
>>> +       struct ovl_lower_check chk = {0};
>>> +       unsigned int start;
>>> +       int ret = 0;
>>> +
>>> +       /* Is a whiteout ? */
>>> +       if (!is_whiteout(st))
>>> +               return 0;
>>> +
>>> +       /* Is Whiteout in the bottom lower dir ? */
>>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>>> +               goto remove;
>>> +
>>> +       /*
>>> +        * Scan each corresponding lower directroy under this layer,
>>> +        * check is there a file or dir with the same name.
>>> +        */
>>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>>> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
>>> +       if (ret)
>>> +               return ret;
>>> +       if (chk.exist && !is_whiteout(&chk.st))
>>> +               goto out;
>>> +
>>> +remove:
>>> +       /* Remove orphan whiteout directly or ask user */
>>> +       print_info(_("Orphan whiteout: %s "), pathname);
>>> +       if (!ask_question("Remove", 1))
>>> +               return 0;
>>> +
>>> +       ret = unlink(pathname);
>>> +       if (ret) {
>>> +               print_err(_("Cannot unlink %s: %s\n"), pathname,
>>> +                           strerror(errno));
>>> +               return ret;
>>> +       }
>>> +out:
>>> +       return ret;
>>> +}
>>> +
>>> +/*
>>> + * Scan each underlying under specified dir if an opaque dir is found,
>>> + * check the opaque xattr is invalid or not. In auto-mode, invalid
>>> + * opaque xattr will be removed directly.
>>> + * Do the follow checking:
>>> + * 1) Check lower matching name exist or not.
>>> + * 2) Check parent dir is merged or pure.
>>> + * If no lower matching and parent is not merged, remove opaque xattr.
>>> + */
>>> +static int ovl_check_opaque(struct scan_ctx *sctx)
>>> +{
>>> +       const char *pathname = sctx->pathname;
>>> +       char parent_path[PATH_MAX];
>>> +       struct ovl_lower_check chk = {0};
>>> +       unsigned int start;
>>> +       int ret = 0;
>>> +
>>> +       /* Is opaque ? */
>>> +       if (!ovl_is_opaque(pathname))
>>> +               goto out;
>>> +
>>> +       /* Opaque dir in last lower dir ? */
>>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>>> +               goto remove;
>>> +
>>> +       /*
>>> +        * Scan each corresponding lower directroy under this layer,
>>> +        * check if there is a file or dir with the same name.
>>> +        */
>>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>>> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
>>> +       if (ret)
>>> +               return ret;
>>> +       if (chk.exist && !is_whiteout(&chk.st))
>>> +               goto out;
>>> +
>>> +       /* Check parent directory merged or pure */
>>> +       memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
>>> +       ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
>>> +       if (ret)
>>> +               return ret;
>>> +       if (chk.exist && is_dir(&chk.st))
>>> +               goto out;
>>> +
>>> +remove:
>>> +       /* Remove opaque xattr or ask user */
>>> +       print_info(_("Invalid opaque xattr: %s "), pathname);
>>> +       if (!ask_question("Remove", 1))
>>> +               return 0;
>>> +
>>> +       ret = ovl_remove_opaque(pathname);
>>> +out:
>>> +       return ret;
>>> +}
>>> +
>>> +static struct ovl_redirect_entry *redirect_list = NULL;
>>> +
>>> +static void ovl_redirect_entry_add(const char *path, int dirtype,
>>> +                                  int dirnum, const char *origin)
>>> +{
>>> +       struct ovl_redirect_entry *last, *new;
>>> +
>>> +       new = smalloc(sizeof(*new));
>>> +
>>> +       print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
>>> +                     origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
>>> +                     (dirtype == OVL_UPPER) ? 0 : dirnum);
>>> +
>>> +       if (!redirect_list) {
>>> +               redirect_list = new;
>>> +       } else {
>>> +               for (last = redirect_list; last->next; last = last->next);
>>> +               last->next = new;
>>> +       }
>>> +       new->next = NULL;
>>> +       new->dirtype = dirtype;
>>> +       new->dirnum = dirnum;
>>> +       strncpy(new->origin, origin, sizeof(new->origin));
>>> +       strncpy(new->path, path, sizeof(new->path));
>>> +}
>>> +
>>> +static bool vol_redirect_entry_find(const char *origin, int dirtype,
>>> +                                   int dirnum, char **founded)
>>> +{
>>> +       struct ovl_redirect_entry *entry;
>>> +
>>> +       if (!redirect_list)
>>> +               return false;
>>> +
>>> +       for (entry = redirect_list; entry; entry = entry->next) {
>>> +               bool same_layer;
>>> +
>>> +               print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
>>> +                             entry->origin, entry->path,
>>> +                             (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
>>> +                             (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
>>> +
>>> +               same_layer = ((entry->dirtype == dirtype) &&
>>> +                             (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
>>> +
>>> +               if (same_layer && !strcmp(entry->origin, origin)) {
>>> +                       *founded = entry->path;
>>> +                       return true;
>>> +               }
>>> +       }
>>> +
>>> +       return false;
>>> +}
>>> +
>>> +static void vol_redirect_free(void)
>>> +{
>>> +       struct ovl_redirect_entry *entry;
>>> +
>>> +       while (redirect_list) {
>>> +               entry = redirect_list;
>>> +               redirect_list = redirect_list->next;
>>> +               free(entry);
>>> +       }
>>> +}
>>> +
>>> +/*
>>> + * Get redirect origin directory stored in the xattr, check it's invlaid
>>> + * or not, In auto-mode, invalid redirect xattr will be removed directly.
>>> + * Do the follow checking:
>>> + * 1) Check the origin directory exist or not. If not, remove xattr.
>>> + * 2) Count how many directories the origin directory was redirected by.
>>> + *    If more than one in the same layer, there must be some inconsistency
>>> + *    but not sure, just warn.
>>> + * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
>>> + */
>>> +static int ovl_check_redirect(struct scan_ctx *sctx)
>>> +{
>>> +       const char *pathname = sctx->pathname;
>>> +       struct ovl_lower_check chk = {0};
>>> +       char redirect_rpath[PATH_MAX];
>>> +       struct stat rst;
>>> +       char *redirect = NULL;
>>> +       unsigned int start;
>>> +       int ret = 0;
>>> +
>>> +       /* Get redirect */
>>> +       ret = ovl_get_redirect(pathname, sctx->dirlen,
>>> +                              sctx->filelen, &redirect);
>>> +       if (ret || !redirect)
>>> +               return ret;
>>> +
>>> +       print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
>>> +
>>> +       /* Redirect dir in last lower dir ? */
>>> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
>>> +               goto remove;
>>> +
>>> +       /* Scan lower directories to check redirect dir exist or not */
>>> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
>>> +       ret = ovl_check_lower(redirect, start, &chk, false);
>>> +       if (ret)
>>> +               goto out;
>>> +       if (chk.exist && is_dir(&chk.st)) {
>>> +               char *tmp;
>>> +
>>> +               /* Check duplicate in same layer */
>>> +               if (vol_redirect_entry_find(chk.path, sctx->dirtype,
>>> +                                           sctx->num, &tmp)) {
>>> +                       print_info("Duplicate redirect directories found:\n");
>>> +                       print_info("origin:%s current:%s latest:%s\n",
>>> +                                  chk.path, pathname, tmp);
>>> +
>>> +                       set_st_inconsistency(&status);
>>> +               }
>>> +
>>> +               ovl_redirect_entry_add(pathname, sctx->dirtype,
>>> +                                      sctx->num, chk.path);
>>> +
>>> +               /* Check and fix whiteout or opaque dir */
>>> +               snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
>>> +                        sctx->dirname, redirect);
>>> +               if (lstat(redirect_rpath, &rst) != 0) {
>>> +                       if (errno != ENOENT && errno != ENOTDIR) {
>>> +                               print_err(_("Cannot stat %s: %s\n"),
>>> +                                           redirect_rpath, strerror(errno));
>>> +                               goto out;
>>> +                       }
>>> +
>>> +                       /* Found nothing, create a whiteout */
>>> +                       ret = ovl_create_whiteout(redirect_rpath);
>>> +
>>> +               } else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
>>> +                       /* Found a directory but not opaqued, fix opaque xattr */
>>> +                       ret = ovl_set_opaque(redirect_rpath);
>>> +               }
>>> +
>>> +               goto out;
>>> +       }
>>> +
>>> +remove:
>>> +       /* Remove redirect xattr or ask user */
>>> +       print_info(_("Invalid redirect xattr: %s "), pathname);
>>> +       if (!ask_question("Remove", 1))
>>> +               goto out;
>>> +
>>> +       ret = ovl_remove_redirect(pathname);
>>> +out:
>>> +       free(redirect);
>>> +       return ret;
>>> +}
>>> +
>>> +static struct scan_operations ovl_scan_ops = {
>>> +       .whiteout = ovl_check_whiteout,
>>> +       .opaque = ovl_check_opaque,
>>> +       .redirect = ovl_check_redirect,
>>> +};
>>> +
>>> +static void ovl_scan_clean(void)
>>> +{
>>> +       /* Clean redirect entry record */
>>> +       vol_redirect_free();
>>> +}
>>> +
>>> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
>>> +int ovl_scan_fix(void)
>>> +{
>>> +       struct scan_ctx sctx;
>>> +       unsigned int i;
>>> +       int ret = 0;
>>> +
>>> +       if (flags | FL_VERBOSE)
>>> +               print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
>>> +
>>> +       /* Scan upper directory */
>>> +       if (flags | FL_VERBOSE)
>>> +               print_info(_("Scan upper directory: %s\n"), upperdir);
>>> +
>>> +       sctx.dirname = upperdir;
>>> +       sctx.dirlen = strlen(upperdir);
>>> +       sctx.dirtype = OVL_UPPER;
>>> +
>>> +       ret = scan_dir(&sctx, &ovl_scan_ops);
>>> +       if (ret)
>>> +               goto out;
>>> +
>>> +       /* Scan every lower directories */
>>> +       for (i = 0; i < lower_num; i++) {
>>> +               if (flags | FL_VERBOSE)
>>> +                       print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
>>> +
>>> +               sctx.dirname = lowerdir[i];
>>> +               sctx.dirlen = strlen(lowerdir[i]);
>>> +               sctx.dirtype = OVL_LOWER;
>>> +               sctx.num = i;
>>> +
>>> +               ret = scan_dir(&sctx, &ovl_scan_ops);
>>> +               if (ret)
>>> +                       goto out;
>>> +       }
>>> +out:
>>> +       ovl_scan_clean();
>>> +       return ret;
>>> +}
>>> diff --git a/check.h b/check.h
>>> new file mode 100644
>>> index 0000000..373ff3a
>>> --- /dev/null
>>> +++ b/check.h
>>> @@ -0,0 +1,7 @@
>>> +#ifndef OVL_WHITECHECK_H
>>> +#define OVL_WHITECHECK_H
>>> +
>>> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
>>> +int ovl_scan_fix(void);
>>> +
>>> +#endif /* OVL_WHITECHECK_H */
>>> diff --git a/common.c b/common.c
>>> new file mode 100644
>>> index 0000000..4e77045
>>> --- /dev/null
>>> +++ b/common.c
>>> @@ -0,0 +1,95 @@
>>> +/*
>>> + *
>>> + *     Common things for all utilities
>>> + *
>>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>>> + *
>>> + */
>>> +
>>> +#include <stdio.h>
>>> +#include <string.h>
>>> +#include <stdlib.h>
>>> +#include <unistd.h>
>>> +#include <stdarg.h>
>>> +#include <errno.h>
>>> +#include <sys/types.h>
>>> +#include <sys/stat.h>
>>> +#include "common.h"
>>> +#include "config.h"
>>> +
>>> +extern char *program_name;
>>> +
>>> +/* #define DEBUG 1 */
>>> +#ifdef DEBUG
>>> +void print_debug(char *fmtstr, ...)
>>> +{
>>> +       va_list args;
>>> +
>>> +       va_start(args, fmtstr);
>>> +       fprintf(stdout, "%s:[Debug]: ", program_name);
>>> +       vfprintf(stdout, fmtstr, args);
>>> +       va_end(args);
>>> +}
>>> +#else
>>> +void print_debug (char *fmtstr, ...) {}
>>> +#endif
>>> +
>>> +void print_info(char *fmtstr, ...)
>>> +{
>>> +       va_list args;
>>> +
>>> +       va_start(args, fmtstr);
>>> +       vfprintf(stdout, fmtstr, args);
>>> +       va_end(args);
>>> +}
>>> +
>>> +void print_err(char *fmtstr, ...)
>>> +{
>>> +       va_list args;
>>> +
>>> +       va_start(args, fmtstr);
>>> +       fprintf(stderr, "%s:[Error]: ", program_name);
>>> +       vfprintf(stderr, fmtstr, args);
>>> +       va_end(args);
>>> +}
>>> +
>>> +void *smalloc(size_t size)
>>> +{
>>> +       void *new = malloc(size);
>>> +
>>> +       if (!new) {
>>> +               print_err(_("malloc error:%s\n"), strerror(errno));
>>> +               exit(1);
>>> +       }
>>> +
>>> +       memset(new, 0, size);
>>> +       return new;
>>> +}
>>> +
>>> +void *srealloc(void *addr, size_t size)
>>> +{
>>> +       void *re = realloc(addr, size);
>>> +
>>> +       if (!re) {
>>> +               print_err(_("malloc error:%s\n"), strerror(errno));
>>> +               exit(1);
>>> +       }
>>> +       return re;
>>> +}
>>> +
>>> +char *sstrdup(const char *src)
>>> +{
>>> +       char *dst = strdup(src);
>>> +
>>> +       if (!dst) {
>>> +               print_err(_("strdup error:%s\n"), strerror(errno));
>>> +               exit(1);
>>> +       }
>>> +
>>> +       return dst;
>>> +}
>>> +
>>> +void version(void)
>>> +{
>>> +       printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
>>> +}
>>> diff --git a/common.h b/common.h
>>> new file mode 100644
>>> index 0000000..c4707e7
>>> --- /dev/null
>>> +++ b/common.h
>>> @@ -0,0 +1,34 @@
>>> +#ifndef OVL_COMMON_H
>>> +#define OVL_COMMON_H
>>> +
>>> +#ifndef __attribute__
>>> +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
>>> +#  define __attribute__(x)
>>> +# endif
>>> +#endif
>>> +
>>> +#ifdef USE_GETTEXT
>>> +#include <libintl.h>
>>> +#define _(x)   gettext((x))
>>> +#else
>>> +#define _(x)   (x)
>>> +#endif
>>> +
>>> +/* Print an error message */
>>> +void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>>> +
>>> +/* Print an info message */
>>> +void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>>> +
>>> +/* Print an debug message */
>>> +void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
>>> +
>>> +/* Safety wrapper */
>>> +void *smalloc(size_t size);
>>> +void *srealloc(void *addr, size_t size);
>>> +char *sstrdup(const char *src);
>>> +
>>> +/* Print program version */
>>> +void version(void);
>>> +
>>> +#endif /* OVL_COMMON_H */
>>> diff --git a/config.h b/config.h
>>> new file mode 100644
>>> index 0000000..deac089
>>> --- /dev/null
>>> +++ b/config.h
>>> @@ -0,0 +1,22 @@
>>> +#ifndef OVL_CONFIG_H
>>> +#define OVL_CONFIG_H
>>> +
>>> +/* program version */
>>> +#define PACKAGE_VERSION        "v1.0.0"
>>> +
>>> +/* overlay max lower stacks (the same to kernel overlayfs driver) */
>>> +#define OVL_MAX_STACK 500
>>> +
>>> +/* File with mounted filesystems */
>>> +#define MOUNT_TAB "/proc/mounts"
>>> +
>>> +/* Name of overlay filesystem type */
>>> +#define OVERLAY_NAME "overlay"
>>> +#define OVERLAY_NAME_OLD "overlayfs"
>>> +
>>> +/* Mount options */
>>> +#define OPT_LOWERDIR "lowerdir="
>>> +#define OPT_UPPERDIR "upperdir="
>>> +#define OPT_WORKDIR "workdir="
>>> +
>>> +#endif
>>> diff --git a/fsck.c b/fsck.c
>>> new file mode 100644
>>> index 0000000..cbcb8e9
>>> --- /dev/null
>>> +++ b/fsck.c
>>> @@ -0,0 +1,179 @@
>>> +/*
>>> + *
>>> + *     Utility to fsck overlay
>>> + *
>>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>>> + *
>>> + */
>>> +
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <errno.h>
>>> +#include <getopt.h>
>>> +#include <libgen.h>
>>> +#include <stdbool.h>
>>> +#include <sys/types.h>
>>> +#include <sys/stat.h>
>>> +#include <linux/limits.h>
>>> +
>>> +#include "common.h"
>>> +#include "config.h"
>>> +#include "lib.h"
>>> +#include "check.h"
>>> +#include "mount.h"
>>> +
>>> +char *program_name;
>>> +
>>> +char **lowerdir;
>>> +char upperdir[PATH_MAX] = {0};
>>> +char workdir[PATH_MAX] = {0};
>>> +unsigned int lower_num;
>>> +int flags;             /* user input option flags */
>>> +int status;            /* fsck scan status */
>>> +
>>> +/* Cleanup lower directories buf */
>>> +static void ovl_clean_lowerdirs(void)
>>> +{
>>> +       unsigned int i;
>>> +
>>> +       for (i = 0; i < lower_num; i++) {
>>> +               free(lowerdir[i]);
>>> +               lowerdir[i] = NULL;
>>> +               lower_num = 0;
>>> +       }
>>> +       free(lowerdir);
>>> +       lowerdir = NULL;
>>> +}
>>> +
>>> +static void usage(void)
>>> +{
>>> +       print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
>>> +                   "[-avhV]\n\n"), program_name);
>>> +       print_info(_("Options:\n"
>>> +                   "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
>>> +                   "                          multiple lower use ':' as separator\n"
>>> +                   "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
>>> +                   "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
>>> +                   "-a, --auto                repair automatically (no questions)\n"
>>> +                   "-v, --verbose             print more messages of overlayfs\n"
>>> +                   "-h, --help                display this usage of overlayfs\n"
>>> +                   "-V, --version             display version information\n"));
>>> +       exit(1);
>>> +}
>>> +
>>> +static void parse_options(int argc, char *argv[])
>>> +{
>>> +       char *lowertemp;
>>> +       int c;
>>> +       int ret = 0;
>>> +
>>> +       struct option long_options[] = {
>>> +               {"lowerdir", required_argument, NULL, 'l'},
>>> +               {"upperdir", required_argument, NULL, 'u'},
>>> +               {"workdir", required_argument, NULL, 'w'},
>>> +               {"auto", no_argument, NULL, 'a'},
>>> +               {"verbose", no_argument, NULL, 'v'},
>>> +               {"version", no_argument, NULL, 'V'},
>>> +               {"help", no_argument, NULL, 'h'},
>>> +               {NULL, 0, NULL, 0}
>>> +       };
>>> +
>>> +       while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
>>> +               switch (c) {
>>> +               case 'l':
>>> +                       lowertemp = strdup(optarg);
>>> +                       ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
>>> +                       free(lowertemp);
>>> +                       break;
>>> +               case 'u':
>>> +                       if (realpath(optarg, upperdir)) {
>>> +                               print_debug(_("Upperdir:%s\n"), upperdir);
>>> +                               flags |= FL_UPPER;
>>> +                       } else {
>>> +                               print_err(_("Failed to resolve upperdir:%s\n"), optarg);
>>> +                               ret = -1;
>>> +                       }
>>> +                       break;
>>> +               case 'w':
>>> +                       if (realpath(optarg, workdir)) {
>>> +                               print_debug(_("Workdir:%s\n"), workdir);
>>> +                               flags |= FL_WORK;
>>> +                       } else {
>>> +                               print_err(_("Failed to resolve workdir:%s\n"), optarg);
>>> +                               ret = -1;
>>> +                       }
>>> +                       break;
>>> +               case 'a':
>>> +                       flags |= FL_AUTO;
>>> +                       break;
>>> +               case 'v':
>>> +                       flags |= FL_VERBOSE;
>>> +                       break;
>>> +               case 'V':
>>> +                       version();
>>> +                       return;
>>> +               case 'h':
>>> +               default:
>>> +                       usage();
>>> +                       return;
>>> +               }
>>> +
>>> +               if (ret)
>>> +                       exit(1);
>>> +       }
>>> +
>>> +       if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
>>> +               print_info(_("Please specify correct lowerdirs and upperdir\n"));
>>> +               usage();
>>> +       }
>>> +}
>>> +
>>> +void fsck_status_check(int *val)
>>> +{
>>> +       if (status & OVL_ST_INCONSISTNECY) {
>>> +               *val |= FSCK_UNCORRECTED;
>>> +               print_info(_("Still have unexpected inconsistency!\n"));
>>> +       }
>>> +
>>> +       if (status & OVL_ST_ABORT) {
>>> +               *val |= FSCK_ERROR;
>>> +               print_info(_("Cannot continue, aborting\n"));
>>> +       }
>>> +}
>>> +
>>> +int main(int argc, char *argv[])
>>> +{
>>> +       bool mounted;
>>> +       int err = 0;
>>> +       int exit_value = 0;
>>> +
>>> +       program_name = basename(argv[0]);
>>> +
>>> +       parse_options(argc, argv);
>>> +
>>> +       /* Ensure overlay filesystem not mounted */
>>> +       if ((err = ovl_check_mount(&mounted)))
>>> +               goto out;
>>> +       if (!mounted) {
>>> +               set_st_abort(&status);
>>> +               goto out;
>>> +       }
>>> +
>>> +       /* Scan and fix */
>>> +       if ((err = ovl_scan_fix()))
>>> +               goto out;
>>> +
>>> +out:
>>> +       fsck_status_check(&exit_value);
>>> +       ovl_clean_lowerdirs();
>>> +
>>> +       if (err)
>>> +               exit_value |= FSCK_ERROR;
>>> +       if (exit_value)
>>> +               print_info("WARNING: Filesystem check failed, may not clean\n");
>>> +       else
>>> +               print_info("Filesystem clean\n");
>>> +
>>> +       return exit_value;
>>> +}
>>> diff --git a/lib.c b/lib.c
>>> new file mode 100644
>>> index 0000000..a6832fe
>>> --- /dev/null
>>> +++ b/lib.c
>>> @@ -0,0 +1,197 @@
>>> +/*
>>> + *
>>> + *     Common things for all utilities
>>> + *
>>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>>> + *
>>> + */
>>> +
>>> +#include <stdlib.h>
>>> +#include <stdio.h>
>>> +#include <unistd.h>
>>> +#include <errno.h>
>>> +#include <string.h>
>>> +#include <stdbool.h>
>>> +#include <sys/types.h>
>>> +#include <sys/stat.h>
>>> +#include <sys/xattr.h>
>>> +#include <fts.h>
>>> +
>>> +#include "common.h"
>>> +#include "lib.h"
>>> +
>>> +extern int flags;
>>> +extern int status;
>>> +
>>> +static int ask_yn(const char *question, int def)
>>> +{
>>> +       char ans[16];
>>> +
>>> +       print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
>>> +       fflush(stdout);
>>> +       while (fgets(ans, sizeof(ans)-1, stdin)) {
>>> +               if (ans[0] == '\n')
>>> +                       return def;
>>> +               else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
>>> +                       return 1;
>>> +               else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
>>> +                       return 0;
>>> +               else
>>> +                       print_info(_("Illegal answer. Please input y/n or yes/no:"));
>>> +               fflush(stdout);
>>> +       }
>>> +       return def;
>>> +}
>>> +
>>> +/* Ask user */
>>> +int ask_question(const char *question, int def)
>>> +{
>>> +       if (flags & FL_AUTO) {
>>> +               print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
>>> +               return def;
>>> +       }
>>> +
>>> +       return ask_yn(question, def);
>>> +}
>>> +
>>> +ssize_t get_xattr(const char *pathname, const char *xattrname,
>>> +                 char **value, bool *exist)
>>> +{
>>> +       char *buf = NULL;
>>> +       ssize_t ret;
>>> +
>>> +       ret = getxattr(pathname, xattrname, NULL, 0);
>>> +       if (ret < 0) {
>>> +               if (errno == ENODATA || errno == ENOTSUP) {
>>> +                       if (exist)
>>> +                               *exist = false;
>>> +                       return 0;
>>> +               } else {
>>> +                       goto fail;
>>> +               }
>>> +       }
>>> +
>>> +       /* Zero size value means xattr exist but value unknown */
>>> +       if (exist)
>>> +               *exist = true;
>>> +       if (ret == 0 || !value)
>>> +               return 0;
>>> +
>>> +       buf = smalloc(ret+1);
>>> +       ret = getxattr(pathname, xattrname, buf, ret);
>>> +       if (ret <= 0)
>>> +               goto fail2;
>>> +
>>> +       buf[ret] = '\0';
>>> +       *value = buf;
>>> +       return ret;
>>> +
>>> +fail2:
>>> +       free(buf);
>>> +fail:
>>> +       print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
>>> +                   xattrname, strerror(errno));
>>> +       return -1;
>>> +}
>>> +
>>> +int set_xattr(const char *pathname, const char *xattrname,
>>> +               void *value, size_t size)
>>> +{
>>> +       int ret;
>>> +
>>> +       ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
>>> +       if (ret && ret != EEXIST)
>>> +               goto fail;
>>> +
>>> +       if (ret == EEXIST) {
>>> +               ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
>>> +               if (ret)
>>> +                       goto fail;
>>> +       }
>>> +
>>> +       return 0;
>>> +fail:
>>> +       print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
>>> +                   xattrname, strerror(errno));
>>> +       return ret;
>>> +}
>>> +
>>> +int remove_xattr(const char *pathname, const char *xattrname)
>>> +{
>>> +       int ret;
>>> +       if ((ret = removexattr(pathname, xattrname)))
>>> +               print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
>>> +                           xattrname, strerror(errno));
>>> +       return ret;
>>> +}
>>> +
>>> +static inline int __check_entry(struct scan_ctx *sctx,
>>> +                                 int (*do_check)(struct scan_ctx *))
>>> +{
>>> +       return do_check ? do_check(sctx) : 0;
>>> +}
>>> +
>>> +/* Scan specified directories and invoke callback */
>>> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
>>> +{
>>> +       char *paths[2] = {(char *)sctx->dirname, NULL};
>>> +       FTS *ftsp;
>>> +       FTSENT *ftsent;
>>> +       int ret = 0;
>>> +
>>> +       ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
>>> +       if (ftsp == NULL) {
>>> +               print_err(_("Failed to fts open %s:%s\n"),
>>> +                           sctx->dirname, strerror(errno));
>>> +               return -1;
>>> +       }
>>> +
>>> +       while ((ftsent = fts_read(ftsp)) != NULL) {
>>> +               int err;
>>> +
>>> +               print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
>>> +                       (ftsent->fts_info == FTS_D) ? "d" :
>>> +                       (ftsent->fts_info == FTS_DNR) ? "dnr" :
>>> +                       (ftsent->fts_info == FTS_DP) ? "dp" :
>>> +                       (ftsent->fts_info == FTS_F) ? "f" :
>>> +                       (ftsent->fts_info == FTS_NS) ? "ns" :
>>> +                       (ftsent->fts_info == FTS_SL) ? "sl" :
>>> +                       (ftsent->fts_info == FTS_SLNONE) ? "sln" :
>>> +                       (ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
>>> +                       ftsent->fts_level, ftsent->fts_statp->st_size,
>>> +                       ftsent->fts_path, ftsent->fts_name);
>>> +
>>> +
>>> +               /* Fillup base context */
>>> +               sctx->pathname = ftsent->fts_path;
>>> +               sctx->pathlen = ftsent->fts_pathlen;
>>> +               sctx->filename = ftsent->fts_name;
>>> +               sctx->filelen = ftsent->fts_namelen;
>>> +               sctx->st = ftsent->fts_statp;
>>> +
>>> +               switch (ftsent->fts_info) {
>>> +               case FTS_DEFAULT:
>>> +                       /* Check whiteouts */
>>> +                       err = __check_entry(sctx, sop->whiteout);
>>> +                       ret = (ret || !err) ? ret : err;
>>> +                       break;
>>> +               case FTS_D:
>>> +                       /* Check opaque and redirect */
>>> +                       err = __check_entry(sctx, sop->opaque);
>>> +                       ret = (ret || !err) ? ret : err;
>>> +
>>> +                       err = __check_entry(sctx, sop->redirect);
>>> +                       ret = (ret || !err) ? ret : err;
>>> +                       break;
>>> +               case FTS_NS:
>>> +               case FTS_DNR:
>>> +               case FTS_ERR:
>>> +                       print_err(_("Failed to fts read %s:%s\n"),
>>> +                                   ftsent->fts_path, strerror(ftsent->fts_errno));
>>> +                       goto out;
>>> +               }
>>> +       }
>>> +out:
>>> +       fts_close(ftsp);
>>> +       return ret;
>>> +}
>>> diff --git a/lib.h b/lib.h
>>> new file mode 100644
>>> index 0000000..463b263
>>> --- /dev/null
>>> +++ b/lib.h
>>> @@ -0,0 +1,73 @@
>>> +#ifndef OVL_LIB_H
>>> +#define OVL_LIB_H
>>> +
>>> +/* Common return value */
>>> +#define FSCK_OK          0     /* No errors */
>>> +#define FSCK_NONDESTRUCT 1     /* File system errors corrected */
>>> +#define FSCK_REBOOT      2     /* System should be rebooted */
>>> +#define FSCK_UNCORRECTED 4     /* File system errors left uncorrected */
>>> +#define FSCK_ERROR       8     /* Operational error */
>>> +#define FSCK_USAGE       16    /* Usage or syntax error */
>>> +#define FSCK_CANCELED   32     /* Aborted with a signal or ^C */
>>> +#define FSCK_LIBRARY     128   /* Shared library error */
>>> +
>>> +/* Fsck status */
>>> +#define OVL_ST_INCONSISTNECY   (1 << 0)
>>> +#define OVL_ST_ABORT           (1 << 1)
>>> +
>>> +/* Option flags */
>>> +#define FL_VERBOSE     (1 << 0)        /* verbose */
>>> +#define FL_UPPER       (1 << 1)        /* specify upper directory */
>>> +#define FL_WORK                (1 << 2)        /* specify work directory */
>>> +#define FL_AUTO                (1 << 3)        /* automactically scan dirs and repair */
>>> +
>>> +/* Scan path type */
>>> +#define OVL_UPPER      0
>>> +#define OVL_LOWER      1
>>> +#define OVL_WORKER     2
>>> +#define OVL_PTYPE_MAX  3
>>> +
>>> +/* Xattr */
>>> +#define OVL_OPAQUE_XATTR       "trusted.overlay.opaque"
>>> +#define OVL_REDIRECT_XATTR     "trusted.overlay.redirect"
>>> +
>>> +/* Directories scan data struct */
>>> +struct scan_ctx {
>>> +       const char *dirname;    /* upper/lower root dir */
>>> +       size_t dirlen;          /* strlen(dirlen) */
>>> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
>>> +       int num;                /* lower dir depth, lower type use only */
>>> +
>>> +       const char *pathname;   /* file path from the root */
>>> +       size_t pathlen;         /* strlen(pathname) */
>>> +       const char *filename;   /* filename */
>>> +       size_t filelen;         /* strlen(filename) */
>>> +       struct stat *st;        /* file stat */
>>> +};
>>> +
>>> +/* Directories scan callback operations struct */
>>> +struct scan_operations {
>>> +       int (*whiteout)(struct scan_ctx *);
>>> +       int (*opaque)(struct scan_ctx *);
>>> +       int (*redirect)(struct scan_ctx *);
>>> +};
>>> +
>>> +static inline void set_st_inconsistency(int *st)
>>> +{
>>> +       *st |= OVL_ST_INCONSISTNECY;
>>> +}
>>> +
>>> +static inline void set_st_abort(int *st)
>>> +{
>>> +       *st |= OVL_ST_ABORT;
>>> +}
>>> +
>>> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
>>> +int ask_question(const char *question, int def);
>>> +ssize_t get_xattr(const char *pathname, const char *xattrname,
>>> +                 char **value, bool *exist);
>>> +int set_xattr(const char *pathname, const char *xattrname,
>>> +             void *value, size_t size);
>>> +int remove_xattr(const char *pathname, const char *xattrname);
>>> +
>>> +#endif /* OVL_LIB_H */
>>> diff --git a/mount.c b/mount.c
>>> new file mode 100644
>>> index 0000000..28ce8e5
>>> --- /dev/null
>>> +++ b/mount.c
>>> @@ -0,0 +1,319 @@
>>> +/*
>>> + *
>>> + *     Check mounted overlay
>>> + *
>>> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
>>> + *
>>> + */
>>> +
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <string.h>
>>> +#include <errno.h>
>>> +#include <getopt.h>
>>> +#include <libgen.h>
>>> +#include <stdbool.h>
>>> +#include <mntent.h>
>>> +#include <sys/types.h>
>>> +#include <sys/stat.h>
>>> +#include <linux/limits.h>
>>> +
>>> +#include "common.h"
>>> +#include "config.h"
>>> +#include "lib.h"
>>> +#include "check.h"
>>> +#include "mount.h"
>>> +
>>> +struct ovl_mnt_entry {
>>> +       char *lowers;
>>> +       char **lowerdir;
>>> +       unsigned int lowernum;
>>> +       char upperdir[PATH_MAX];
>>> +       char workdir[PATH_MAX];
>>> +};
>>> +
>>> +/* Mount buf allocate a time */
>>> +#define ALLOC_NUM      16
>>> +
>>> +extern char **lowerdir;
>>> +extern char upperdir[];
>>> +extern char workdir[];
>>> +extern unsigned int lower_num;
>>> +
>>> +/*
>>> + * Split directories to individual one.
>>> + * (copied from linux kernel, see fs/overlayfs/super.c)
>>> + */
>>
>> It would be nice if such functions could be maintained in a file/dir
>> that is synced with the kernel, like the xfs/lib files.
>> For the first release we don't really have to sync this file with the
>> kernel, but at least keep them at a specific file/dir and maybe the
>> kernel driver will follow up later.
>>
>>> +static unsigned int ovl_split_lowerdirs(char *lower)
>>> +{
>>> +       unsigned int ctr = 1;
>>> +       char *s, *d;
>>> +
>>> +       for (s = d = lower;; s++, d++) {
>>> +               if (*s == '\\') {
>>> +                       s++;
>>> +               } else if (*s == ':') {
>>> +                       *d = '\0';
>>> +                       ctr++;
>>> +                       continue;
>>> +               }
>>> +               *d = *s;
>>> +               if (!*s)
>>> +                       break;
>>> +       }
>>> +       return ctr;
>>> +}
>>> +
>>> +/* Resolve each lower directories and check the validity */
>>> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
>>> +                         unsigned int *lowernum)
>>> +{
>>> +       unsigned int num;
>>> +       char **dirs;
>>> +       char *p;
>>> +       int i;
>>> +
>>> +       num = ovl_split_lowerdirs(loweropt);
>>> +       if (num > OVL_MAX_STACK) {
>>> +               print_err(_("Too many lower directories:%u, max:%u\n"),
>>> +                           num, OVL_MAX_STACK);
>>> +               return -1;
>>> +       }
>>> +
>>> +       dirs = smalloc(sizeof(char *) * num);
>>> +
>>> +       p = loweropt;
>>> +       for (i = 0; i < num; i++) {
>>> +               dirs[i] = smalloc(PATH_MAX);
>>> +               if (!realpath(p, dirs[i])) {
>>> +                       print_err(_("Failed to resolve lowerdir:%s:%s\n"),
>>> +                                   p, strerror(errno));
>>> +                       goto err;
>>> +               }
>>> +               print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
>>> +               p = strchr(p, '\0') + 1;
>>> +       }
>>> +
>>> +       *lowerdir = dirs;
>>> +       *lowernum = num;
>>> +
>>> +       return 0;
>>> +err:
>>> +       for (i--; i >= 0; i--)
>>> +               free(dirs[i]);
>>> +       free(dirs);
>>> +       *lowernum = 0;
>>> +       return -1;
>>> +}
>>> +
>>> +/*
>>> + * Split and return next opt.
>>> + * (copied from linux kernel, see fs/overlayfs/super.c)
>>> + */
>>> +static char *ovl_next_opt(char **s)
>>> +{
>>> +       char *sbegin = *s;
>>> +       char *p;
>>> +
>>> +       if (sbegin == NULL)
>>> +               return NULL;
>>> +
>>> +       for (p = sbegin; *p; p++) {
>>> +               if (*p == '\\') {
>>> +                       p++;
>>> +                       if (!*p)
>>> +                               break;
>>> +               } else if (*p == ',') {
>>> +                       *p = '\0';
>>> +                       *s = p + 1;
>>> +                       return sbegin;
>>> +               }
>>> +       }
>>> +       *s = NULL;
>>> +       return sbegin;
>>> +}
>>> +
>>> +/*
>>> + * Split and parse opt to each directories.
>>> + *
>>> + * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
>>> + * mounted use relative path, so there may have misjudgment.
>>> + */
>>> +static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
>>> +{
>>> +       char tmp[PATH_MAX] = {0};
>>> +       char *p;
>>> +       int len;
>>> +       int ret;
>>> +       int i;
>>> +
>>> +       while ((p = ovl_next_opt(&opt)) != NULL) {
>>> +               if (!*p)
>>> +                       continue;
>>> +
>>> +               if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
>>> +                       len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
>>> +                       strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
>>> +
>>> +                       if (!realpath(tmp, entry->upperdir)) {
>>> +                               print_err(_("Faile to resolve path:%s:%s\n"),
>>> +                                           tmp, strerror(errno));
>>> +                               ret = -1;
>>> +                               goto errout;
>>> +                       }
>>> +               } else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
>>> +                       len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
>>> +                       entry->lowers = smalloc(len);
>>> +                       strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
>>> +
>>> +                       if ((ret = ovl_resolve_lowerdirs(entry->lowers,
>>> +                                       &entry->lowerdir, &entry->lowernum)))
>>> +                               goto errout;
>>> +
>>> +               } else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
>>> +                       len = strlen(p) - strlen(OPT_WORKDIR) + 1;
>>> +                       strncpy(tmp, p+strlen(OPT_WORKDIR), len);
>>> +
>>> +                       if (!realpath(tmp, entry->workdir)) {
>>> +                               print_err(_("Faile to resolve path:%s:%s\n"),
>>> +                                           tmp, strerror(errno));
>>> +                               ret = -1;
>>> +                               goto errout;
>>> +                       }
>>> +               }
>>> +       }
>>> +
>>> +errout:
>>> +       if (entry->lowernum) {
>>> +               for (i = 0; i < entry->lowernum; i++)
>>> +                       free(entry->lowerdir[i]);
>>> +               free(entry->lowerdir);
>>> +               entry->lowerdir = NULL;
>>> +               entry->lowernum = 0;
>>> +       }
>>> +       free(entry->lowers);
>>> +       entry->lowers = NULL;
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +/* Scan current mounted overlayfs and get used underlying directories */
>>> +static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
>>> +                              int *ovl_mnt_count)
>>> +{
>>> +       struct ovl_mnt_entry *mnt_entries;
>>> +       struct mntent *mnt;
>>> +       FILE *fp;
>>> +       char *opt;
>>> +       int allocated, num = 0;
>>> +
>>> +       fp = setmntent(MOUNT_TAB, "r");
>>> +       if (!fp) {
>>> +               print_err(_("Fail to setmntent %s:%s\n"),
>>> +                           MOUNT_TAB, strerror(errno));
>>> +               return -1;
>>> +       }
>>> +
>>> +       allocated = ALLOC_NUM;
>>> +       mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
>>> +
>>> +       while ((mnt = getmntent(fp))) {
>>> +               if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
>>> +                   !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
>>> +
>>> +                       opt = sstrdup(mnt->mnt_opts);
>>> +                       if (ovl_parse_opt(opt, &mnt_entries[num])) {
>>> +                               free(opt);
>>> +                               continue;
>>> +                       }
>>> +
>>> +                       num++;
>>> +                       if (num % ALLOC_NUM == 0) {
>>> +                               allocated += ALLOC_NUM;
>>> +                               mnt_entries = srealloc(mnt_entries,
>>> +                                    sizeof(struct ovl_mnt_entry) * allocated);
>>> +                       }
>>> +
>>> +                       free(opt);
>>> +               }
>>> +       }
>>> +
>>> +       *ovl_mnt_entries = mnt_entries;
>>> +       *ovl_mnt_count = num;
>>> +
>>> +       endmntent(fp);
>>> +       return 0;
>>> +}
>>> +
>>> +static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
>>> +                               int ovl_mnt_count)
>>> +{
>>> +       int i,j;
>>> +
>>> +       for (i = 0; i < ovl_mnt_count; i++) {
>>> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
>>> +                       free(ovl_mnt_entries[i].lowerdir[j]);
>>> +               free(ovl_mnt_entries[i].lowerdir);
>>> +               free(ovl_mnt_entries[i].lowers);
>>> +       }
>>> +       free(ovl_mnt_entries);
>>> +}
>>> +
>>> +/*
>>> + * Scan every mounted filesystem, check the overlay directories want
>>> + * to check is already mounted. Check and fix an online overlay is not
>>> + * allowed.
>>> + *
>>> + * Note: fsck may modify lower layers, so even match only one directory
>>> + *       is triggered as mounted.
>>> + */
>>> +int ovl_check_mount(bool *pass)
>>> +{
>>> +       struct ovl_mnt_entry *ovl_mnt_entries;
>>> +       int ovl_mnt_entry_count;
>>> +       char *mounted_path;
>>> +       bool mounted;
>>> +       int i,j,k;
>>> +       int ret;
>>> +
>>> +       ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       /* Only check hard matching */
>>> +       for (i = 0; i < ovl_mnt_entry_count; i++) {
>>> +               /* Check lower */
>>> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
>>> +                       for (k = 0; k < lower_num; k++) {
>>> +                               if (!strcmp(lowerdir[k],
>>> +                                           ovl_mnt_entries[i].lowerdir[j])) {
>>> +                                       mounted_path = lowerdir[k];
>>> +                                       mounted = true;
>>> +                                       goto out;
>>> +                               }
>>> +                       }
>>> +               }
>>> +
>>> +               /* Check upper */
>>> +               if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
>>> +                       mounted_path = upperdir;
>>> +                       mounted = true;
>>> +                       goto out;
>>> +               }
>>> +
>>> +               /* Check worker */
>>> +               if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
>>> +                       mounted_path = workdir;
>>> +                       mounted = true;
>>> +                       goto out;
>>> +               }
>>> +       }
>>> +out:
>>> +       ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
>>> +
>>> +       if (mounted)
>>> +               print_info(_("Dir %s is mounted\n"), mounted_path);
>>> +       *pass = !mounted;
>>> +
>>> +       return 0;
>>> +}
>>> diff --git a/mount.h b/mount.h
>>> new file mode 100644
>>> index 0000000..8a3762d
>>> --- /dev/null
>>> +++ b/mount.h
>>> @@ -0,0 +1,8 @@
>>> +#ifndef OVL_MOUNT_H
>>> +#define OVL_MOUNT_H
>>> +
>>> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
>>> +                          unsigned int *lowernum);
>>> +int ovl_check_mount(bool *mounted);
>>> +
>>> +#endif /* OVL_MOUNT_H */
>>> diff --git a/test/README.md b/test/README.md
>>> new file mode 100644
>>> index 0000000..71dbad8
>>> --- /dev/null
>>> +++ b/test/README.md
>>> @@ -0,0 +1,12 @@
>>> +fsck.overlay test
>>> +=================
>>> +
>>> +This test cases simulate different inconsistency of overlay filesystem
>>> +underlying directories, test fsck.overlay can repair them correctly or not.
>>> +
>>> +USAGE
>>> +=====
>>> +
>>> +1. Check "local.config", modify config value to appropriate one.
>>> +2. ./auto_test.sh
>>> +3. After testing, the result file and log file created in the result directory.
>>
>> This really sounds like a mini fork of xfstests.
>> I'd prefer if those tests went into xfstests and used fsck.overlay either as an
>> external program (like the rest of fsck.*) or as in-house test helper.
>>
>>> diff --git a/test/auto_test.sh b/test/auto_test.sh
>>> new file mode 100755
>>> index 0000000..8248e24
>>> --- /dev/null
>>> +++ b/test/auto_test.sh
>>> @@ -0,0 +1,46 @@
>>> +#!/bin/bash
>>> +
>>> +. ./src/prepare.sh
>>> +
>>> +echo "***************"
>>> +
>>> +# 1. Test whiteout
>>> +echo -e "1.Test Whiteout\n"
>>> +$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
>>> +if [ $? -ne 0 ]; then
>>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>>> +       RESULT="Fail"
>>> +else
>>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>>> +       RESULT="Pass"
>>> +fi
>>> +
>>> +echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
>>> +
>>> +# 2. Test opaque dir
>>> +echo -e "2.Test opaque directory\n"
>>> +$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
>>> +if [ $? -ne 0 ]; then
>>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>>> +       RESULT="Fail"
>>> +else
>>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>>> +       RESULT="Pass"
>>> +fi
>>> +
>>> +echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
>>> +
>>> +# 3. Test redirect dir
>>> +echo -e "3.Test redirect direcotry\n"
>>> +$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
>>> +if [ $? -ne 0 ]; then
>>> +       echo -e "Result:\033[31m Fail\033[0m\n"
>>> +       RESULT="Fail"
>>> +else
>>> +       echo -e "Result:\033[32m Pass\033[0m\n"
>>> +       RESULT="Pass"
>>> +fi
>>> +
>>> +echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
>>> +
>>> +echo "***************"
>>> diff --git a/test/clean.sh b/test/clean.sh
>>> new file mode 100755
>>> index 0000000..80f360b
>>> --- /dev/null
>>> +++ b/test/clean.sh
>>> @@ -0,0 +1,6 @@
>>> +#!/bin/bash
>>> +
>>> +. ./src/prepare.sh
>>> +
>>> +rm -rf $RESULT_DIR
>>> +rm -rf $TEST_DIR
>>> diff --git a/test/local.config b/test/local.config
>>> new file mode 100644
>>> index 0000000..990395f
>>> --- /dev/null
>>> +++ b/test/local.config
>>> @@ -0,0 +1,16 @@
>>> +# Config
>>> +export OVL_FSCK=fsck.overlay
>>> +export TEST_DIR=/mnt/test
>>> +
>>> +# Base environment
>>> +export PWD=`pwd`
>>> +export RESULT_DIR=$PWD/result
>>> +export SOURCE_DIR=$PWD/src
>>> +
>>> +# Overlayfs config
>>> +export LOWER_DIR="lower"
>>> +export UPPER_DIR="upper"
>>> +export WORK_DIR="worker"
>>
>> worker? this is a strange name for work dir location..
>> it does not have the same meaning in English as upp-er and low-er.
>>
>>> +export MERGE_DIR="merge"
>>> +export LOWER_DIR1="lower1"
>>> +export WORK_DIR1="worker1"
>>> diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
>>> new file mode 100755
>>> index 0000000..7cb5030
>>> --- /dev/null
>>> +++ b/test/src/opaque_test.sh
>>> @@ -0,0 +1,144 @@
>>> +#!/bin/bash
>>> +
>>> +. ./local.config
>>> +
>>> +__clean()
>>> +{
>>> +       rm -rf $TEST_DIR/*
>>> +       cd $PWD
>>> +}
>>> +
>>> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
>>> +OVL_OPAQUE_XATTR_VAL="y"
>>> +RET=0
>>> +
>>> +cd $TEST_DIR
>>> +rm -rf *
>>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>>> +
>>> +# 1.Test lower invalid opaque directory
>>> +echo "***** 1.Test lower invalid opaque directory *****"
>>> +mkdir $LOWER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: lower's invalid opaque xattr not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 2.Test upper invalid opaque directory
>>> +echo "***** 2.Test upper invalid opaque directory *****"
>>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: upper's invalid opaque xattr not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +
>>> +# 3.Test upper opaque direcotry in merged directory
>>> +echo "***** 3.Test upper opaque direcotry in merged directory *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +
>>> +# 4.Test upper opaque direcotry cover lower file
>>> +echo "***** 4.Test upper opaque direcotry cover lower file *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +mkdir $LOWER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 5.Test upper opaque direcotry cover lower directory
>>> +echo "***** 5.Test upper opaque direcotry cover lower directory *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +touch $LOWER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 6.Test lower invalid opaque directory in middle layer
>>> +echo "***** 6.Test lower invalid opaque directory in middle layer *****"
>>> +mkdir $LOWER_DIR/testdir0/testdir1
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: middle's opaque xattr not removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 7.Test lower invalid opaque directory in bottom layer
>>> +echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
>>> +mkdir $LOWER_DIR1/testdir0/testdir1
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
>>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: middle's opaque xattr not removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $LOWER_DIR1/*
>>> +
>>> +# 8.Test middle opaque direcotry cover bottom directory
>>> +echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
>>> +mkdir $LOWER_DIR1/testdir
>>> +mkdir $LOWER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: middle's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $LOWER_DIR1/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 9.Test double opaque direcotry in middle and upper layer
>>> +echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
>>> +mkdir $LOWER_DIR1/testdir
>>> +mkdir $LOWER_DIR/testdir
>>> +mkdir $UPPER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
>>> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: middle's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's opaque xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $LOWER_DIR1/*
>>> +rm -rf $LOWER_DIR/*
>>> +rm -rf $UPPER_DIR/*
>>> +
>>> +__clean
>>> +exit $RET
>>> diff --git a/test/src/prepare.sh b/test/src/prepare.sh
>>> new file mode 100755
>>> index 0000000..138539a
>>> --- /dev/null
>>> +++ b/test/src/prepare.sh
>>> @@ -0,0 +1,15 @@
>>> +#!/bin/bash
>>> +
>>> +. ./local.config
>>> +
>>> +NOW=`date +"%Y-%m-%d-%H-%M-%S"`
>>> +LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
>>> +RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
>>> +
>>> +# creat test base dir
>>> +if [ ! -d "$TEST_DIR" ]; then
>>> +       mkdir -p $TEST_DIR
>>> +fi
>>> +
>>> +# creat result dir
>>> +mkdir -p $RESULT_DIR
>>> diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
>>> new file mode 100755
>>> index 0000000..be2ce80
>>> --- /dev/null
>>> +++ b/test/src/redirect_test.sh
>>> @@ -0,0 +1,163 @@
>>> +#!/bin/bash
>>> +
>>> +. ./local.config
>>> +
>>> +__clean()
>>> +{
>>> +       rm -rf $TEST_DIR/*
>>> +       cd $PWD
>>> +}
>>> +
>>> +OVL_REDIRECT_XATTR="trusted.overlay.redirect"
>>> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
>>> +RET=0
>>> +
>>> +cd $TEST_DIR
>>> +rm -rf *
>>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>>> +
>>> +# 1.Test no exist redirect origin
>>> +echo "***** 1.Test no exist redirect origin *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +
>>> +# 2.Test redirect origin is file
>>> +echo "***** 2.Test redirect origin is file *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +touch $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 3.Test valid redirect xattr
>>> +echo "***** 3.Test valid redirect xattr *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +mknod $UPPER_DIR/origin c 0 0
>>> +mkdir $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 4.Test valid redirect xattr start from root
>>> +echo "***** 4.Test valid redirect xattr start from root *****"
>>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>>> +mknod $UPPER_DIR/origin c 0 0
>>> +mkdir $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 5.Test missing whiteout in redirect parent directory
>>> +echo "***** 5.Test missing whiteout in redirect parent directory *****"
>>> +mkdir $UPPER_DIR/testdir
>>> +mkdir $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +if [ ! -e "$UPPER_DIR/origin" ];then
>>> +       echo "ERROR: upper's missing whiteout not create"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $UPPER_DIR/*
>>> +
>>> +# 6.Test missing opaque in redirect parent directory
>>> +echo "***** 6.Test missing opaque in redirect parent directory *****"
>>> +mkdir -p $UPPER_DIR/testdir0/testdir1
>>> +mkdir $UPPER_DIR/origin
>>> +mkdir $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's missing opaque not set"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 7.Test duplicate redirect directory in one layer
>>> +echo "***** 7.Test duplicate redirect directory in one layer *****"
>>> +mkdir $UPPER_DIR/testdir0
>>> +mkdir $UPPER_DIR/testdir1
>>> +mknod $UPPER_DIR/origin c 0 0
>>> +mkdir $LOWER_DIR/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +if [[ $? -eq 0 ]];then
>>> +       echo "ERROR: fsck check incorrect pass"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +
>>> +# 8.Test duplicate redirect directory in different layer
>>> +echo "***** 8.Test duplicate redirect directory in different layer *****"
>>> +mkdir $UPPER_DIR/testdir0
>>> +mkdir $LOWER_DIR/testdir1
>>> +mkdir $LOWER_DIR1/origin
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
>>> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
>>> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
>>> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: upper's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
>>> +if [[ $? -ne 0 ]];then
>>> +       echo "ERROR: lower's redirect xattr incorrect removed"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +
>>> +if [ ! -e "$LOWER_DIR/origin" ];then
>>> +       echo "ERROR: upper's missing whiteout not create"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +if [ ! -e "$LOWER_DIR/origin" ];then
>>> +       echo "ERROR: lower's missing whiteout not create"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +
>>> +rm -rf $UPPER_DIR/*
>>> +rm -rf $LOWER_DIR/*
>>> +rm -rf $LOWER_DIR1/*
>>> +
>>> +__clean
>>> +
>>> +exit $RET
>>> diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
>>> new file mode 100755
>>> index 0000000..c0e1555
>>> --- /dev/null
>>> +++ b/test/src/whiteout_test.sh
>>> @@ -0,0 +1,63 @@
>>> +#!/bin/bash
>>> +
>>> +. ./local.config
>>> +
>>> +__clean()
>>> +{
>>> +       rm -rf $TEST_DIR/*
>>> +       cd $PWD
>>> +}
>>> +
>>> +RET=0
>>> +
>>> +cd $TEST_DIR
>>> +rm -rf *
>>> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
>>> +
>>> +# 1.Test lower orphan whiteout
>>> +echo "***** 1.Test lower orphan whiteout *****"
>>> +mknod $LOWER_DIR/foo c 0 0
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +if [ -e "$LOWER_DIR/foo" ];then
>>> +       echo "lower's whiteout not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -f $LOWER_DIR/*
>>> +
>>> +# 2.Test upper orphan whiteout
>>> +echo "***** 2.Test upper orphan whiteout *****"
>>> +mknod $UPPER_DIR/foo c 0 0
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +if [ -e "$UPPER_DIR/foo" ];then
>>> +       echo "upper's whiteout not remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -f $UPPER_DIR/*
>>> +
>>> +# 3.Test upper inuse whiteout
>>> +echo "***** 3.Test upper inuse whiteout *****"
>>> +touch $LOWER_DIR/foo
>>> +mknod $UPPER_DIR/foo c 0 0
>>> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +if [ ! -e "$UPPER_DIR/foo" ];then
>>> +       echo "upper's whiteout incorrect remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -f $UPPER_DIR/*
>>> +rm -f $LOWER_DIR/*
>>> +
>>> +# 4.Test lower inuse whiteout
>>> +echo "***** 4.Test lower inuse whiteout *****"
>>> +touch $LOWER_DIR/foo
>>> +mknod $LOWER_DIR1/foo c 0 0
>>> +$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
>>> +if [ ! -e "$LOWER_DIR1/foo" ];then
>>> +       echo "lower's whiteout incorrect remove"
>>> +       RET=$(($RET+1))
>>> +fi
>>> +rm -f $LOWER_DIR1/*
>>> +rm -f $LOWER_DIR/*
>>> +
>>> +__clean
>>> +
>>> +exit $RET
>>> --
>>> 2.9.5
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe fstests" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> .
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-20  6:56   ` zhangyi (F)
@ 2017-11-20  7:29     ` Amir Goldstein
  2017-11-20  9:00       ` zhangyi (F)
  2017-11-20  7:42     ` Eryu Guan
  1 sibling, 1 reply; 19+ messages in thread
From: Amir Goldstein @ 2017-11-20  7:29 UTC (permalink / raw)
  To: zhangyi (F); +Cc: overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

On Mon, Nov 20, 2017 at 8:56 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
> On 2017/11/18 1:13, Amir Goldstein Wrote:
[...]
>
>>>
>>> This patch can am to an empty git repository. I have already do basic
>>> test list in 'test' directory. Please do a review give some suggestions.
>>> Thanks a lot.
>>
>> Please consider, instead of writing overlay tests in new repo,
>> to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
>> _check_scratch_fs.
>> See for example very basic index sanity checks I implemented here:
>> https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
>>
>
> I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
> and put in the same place. Now I notice that all xfs test cases put together
> in xfstests, so It's also a good choice, not sure which one is better. How
> about Eryu's opinion ?
>

Looks. It's not a terrible idea to have some amount of duplication with tests
and it's fine you with to keep some independent test within the progs repositiry
as a "self test". But you will benefit a lot more testers on more machines and
configurations if you merge all the interesting overlay tests to "fstests" repo,
which a lot more people are running.
And yes, there are quite a lot of tests in xfstest to verify correctness of
xfs_repair. Self testing the main fs sanity check program is a good idea.


>> It may be useful, if Eryu agrees, to merge the "incubator" version of
>> fsck.overlayfs
>> into xfstests as a helper program until it starts getting packaged and
>> distributed.
>> Please consider this option.
>>
>
> Can we applay for a repository in git.kernel.org like xfsprogs-dev and e2fsprogs ?

"Kernel.org accounts are not given away very often, usually you need
to be making some
reasonable amount of contributions to the Linux kernel and have a good
reason for wanting
/ needing an account"

It doesn't hurt to ask, but I suspect an incubation period for
fsck.overlay in another location
before it gets enough traction might be a good idea.
You may ask Miklos to maintain overlayfs-progs on kernel.org getting
patches from you,
but I guess this was not your intention.

[...]

>>> +Redirect directories
>>> +--------------------
>>> +
>>> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
>>> +valued to the path of the original location from the root of the overlay. It
>>> +is only used when renaming a directory and "redirect dir" feature is enabled.
>>> +If an redirect directory is found, the following must be met:
>>> +
>>> +1) The directory store in redirect xattr should exist in one of lower layers.
>>> +2) The origin directory should be redirected only once in one layer, which mean
>>> +   there is only one redirect xattr point to this origin directory in the
>>> +   specified layer.
>>> +3) A whiteout or an opaque directory with the same name to origin should exist
>>> +   in the same directory as the redirect directory.
>> another redirected upper dir can also be covering the redirect lower target
>>
> Do you mean the following case: upper's dir_rd cover lower1's redirect dir_rd ?
>
> Upper:                    dir_rd(redurected,opaque)   dir2(whiteout)
> Lower1:   dir(whiteout)   dir_rd(redurected)          dir2
> Lower0:   dir
>

That's not what I meant. What I meant is:

Upper: A (whiteout), B (redirect=A), C (redirect=B)
Lower: A (dir),          B (dir)

The redirect target of upper C (B) is not a whiteout in upper, nor an
opaque dir.
It is a redirected dir (whose own target is covered by a whitetout A).
The description in 3) doesn't cover that case. Didn't check how the program
handles this case.

Amir.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-20  6:56   ` zhangyi (F)
  2017-11-20  7:29     ` Amir Goldstein
@ 2017-11-20  7:42     ` Eryu Guan
  2017-11-21  2:30       ` Theodore Ts'o
  1 sibling, 1 reply; 19+ messages in thread
From: Eryu Guan @ 2017-11-20  7:42 UTC (permalink / raw)
  To: zhangyi (F); +Cc: Amir Goldstein, overlayfs, Miklos Szeredi, Miao Xie, fstests

On Mon, Nov 20, 2017 at 02:56:15PM +0800, zhangyi (F) wrote:
> On 2017/11/18 1:13, Amir Goldstein Wrote:
> > [adding fstests in CC with full patch inline to collect wisdom from
> > other fs developers]
> > 
> > On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
> >> Hi,
> >>
> >> Here is the origin version of fsck.overlay utility I mentioned last
> >> week. It scan each underlying directory and check orphan whiteout,
> >> invalid opaque xattr and invalid redirect xattr. We can use it to
> >> check filesystem inconsistency before mount overlayfs to avoid some
> >> unexpected results.
> > 
> > Thanks for working on this!
> > It looks like a great start.
> > 
> Hi Amir,
> 
> Thanks a lot for review and your suggestions.
> 
> >>
> >> This patch can am to an empty git repository. I have already do basic
> >> test list in 'test' directory. Please do a review give some suggestions.
> >> Thanks a lot.
> > 
> > Please consider, instead of writing overlay tests in new repo,
> > to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
> > _check_scratch_fs.
> > See for example very basic index sanity checks I implemented here:
> > https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
> > 
> 
> I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
> and put in the same place. Now I notice that all xfs test cases put together
> in xfstests, so It's also a good choice, not sure which one is better. How
> about Eryu's opinion ?

IMHO, xfstests is a good place for fsck.overlay tests, but I don't think
that's something conflicting with in-repo unit tests, some tests are
just not suitable for xfstests. How about writing tests in xfstests by
default and only keeping them in repository, if there's any, if the
tests are not fitting in xfstests well?

> 
> > It may be useful, if Eryu agrees, to merge the "incubator" version of
> > fsck.overlayfs
> > into xfstests as a helper program until it starts getting packaged and
> > distributed.
> > Please consider this option.

Hmm, we can introduce all necessary infrastructure in xfstests for
fsck.overlay and make that optional and run it only when it's available
in system $PATH, but I don't think xfstests is the right place keep the
source code of fsck.overlay. That means if you care about fsck.overlay
and want to test it just compile & install a local binary from source
code and xfstests will take care of the rest.

Thanks,
Eryu

> > 
> 
> Can we applay for a repository in git.kernel.org like xfsprogs-dev and e2fsprogs ?
> 
> >>
> >> Thanks,
> >> Yi.
> >>
> >> ---------------------------
> >>
> >> fsck.overlay
> >> ============
> >>
> >> fsck.overlay is used to check and optionally repair underlying
> >> directories of overlay-filesystem.
> >>
> >> Check the following points:
> >>
> >> Whiteouts
> >> ---------
> >>
> >> A whiteout is a character device with 0/0 device number. It is used to
> >> record the removed files or directories, When a whiteout is found in a
> >> directory, there should be at least one directory or file with the same
> >> name in any of the corresponding lower layers. If not exist, the whiteout
> >> will be treated as orphan whiteout and remove.
> >>
> >> Opaque directories
> >> ------------------
> >>
> >> An opaque directory is a directory with "trusted.overlay.opaque" xattr
> >> valued "y". There are two legal situations of making opaque directory: 1)
> >> create directory over whiteout 2) creat directory in merged directory. If an
> >> opaque directory is found, corresponding matching name in lower layers might
> >> exist or parent directory might merged, If not, the opaque xattr will be
> >> treated as invalid and remove.
> >>
> >> Redirect directories
> >> --------------------
> >>
> >> An redirect directory is a directory with "trusted.overlay.redirect"
> >> xattr valued to the path of the original location from the root of the
> >> overlay. It is only used when renaming a directory and "redirect dir"
> >> feature is enabled. If an redirect directory is found, the following
> >> must be met:
> >>
> >> 1) The directory store in redirect xattr should exist in one of lower
> >> layers.
> >> 2) The origin directory should be redirected only once in one layer,
> >> which mean there is only one redirect xattr point to this origin directory in
> >> the specified layer.
> >> 3) A whiteout or an opaque directory with the same name to origin should
> >> exist in the same directory as the redirect directory.
> >>
> >> If not, 1) The redirect xattr is invalid and need to remove 2) One of
> >> the redirect xattr is redundant but not sure which one is, ask user 3)
> >> Create a whiteout device or set opaque xattr to an existing directory if the
> >> parent directory was meregd or remove xattr if not.
> >>
> >> Usage
> >> =====
> >>
> >> 1. Ensure overlay filesystem is not mounted based on directories which
> >> need to check.
> >>
> >> 2. Run fsck.overlay program:
> >>    Usage:
> >>    fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
> >>
> >>    Options:
> >>    -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
> >>                              multiple lower use ':' as separator.
> >>    -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
> >>    -w, --workdir=WORKDIR     specify work directory of overlayfs
> >>    -a, --auto                repair automatically (no questions)
> > 
> > Let stick with e2fsck conventions when possible.
> > -a should be -y and we should definitely have -n, because this is
> > how xfstest would run it.
> 
> Yes, will fix it.
> 
> >>    -v, --verbose             print more messages of overlayfs
> >>    -h, --help                display this usage of overlayfs
> >>    -V, --version             display version information
> >>
> >> 3. Exit value:
> >>    0      No errors
> >>    1      Filesystem errors corrected
> >>    2      System should be rebooted
> >>    4      Filesystem errors left uncorrected
> >>    8      Operational error
> >>    16     Usage or syntax error
> >>    32     Checking canceled by user request
> >>    128    Shared-library error
> >>
> >> Todo
> >> ====
> >>
> >> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
> >> online. Now, We cannot distinguish mounted directories if overlayfs was
> >> mounted with relative path.
> > 
> > This should be handled by kernel.
> > We now already grab an advisory exclusive I_OVL_INUSE lock on both
> > upperdir and workdir.
> > fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
> > and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
> > Read the man page section on O_EXCL and block device. This is how
> > e2fsck and friends get exclusive access w.r.t mount.
> >
> 
> Good suggesttion, will consider it later.
> 
> >> 2. Symbolic link check.
> >> 3. Check origin/impure/nlink xattr.
> >> 4. ...
> >>
> >> Signed-off-by: zhangyi (F) <yi.zhang@huawei.com>
> >> ---
> >>  Makefile                  |  31 +++
> >>  README.md                 |  88 +++++++++
> >>  check.c                   | 482 ++++++++++++++++++++++++++++++++++++++++++++++
> >>  check.h                   |   7 +
> >>  common.c                  |  95 +++++++++
> >>  common.h                  |  34 ++++
> >>  config.h                  |  22 +++
> >>  fsck.c                    | 179 +++++++++++++++++
> >>  lib.c                     | 197 +++++++++++++++++++
> >>  lib.h                     |  73 +++++++
> >>  mount.c                   | 319 ++++++++++++++++++++++++++++++
> >>  mount.h                   |   8 +
> >>  test/README.md            |  12 ++
> >>  test/auto_test.sh         |  46 +++++
> >>  test/clean.sh             |   6 +
> >>  test/local.config         |  16 ++
> >>  test/src/opaque_test.sh   | 144 ++++++++++++++
> >>  test/src/prepare.sh       |  15 ++
> >>  test/src/redirect_test.sh | 163 ++++++++++++++++
> >>  test/src/whiteout_test.sh |  63 ++++++
> >>  20 files changed, 2000 insertions(+)
> >>  create mode 100644 Makefile
> >>  create mode 100644 README.md
> >>  create mode 100644 check.c
> >>  create mode 100644 check.h
> >>  create mode 100644 common.c
> >>  create mode 100644 common.h
> >>  create mode 100644 config.h
> >>  create mode 100644 fsck.c
> >>  create mode 100644 lib.c
> >>  create mode 100644 lib.h
> >>  create mode 100644 mount.c
> >>  create mode 100644 mount.h
> >>  create mode 100644 test/README.md
> >>  create mode 100755 test/auto_test.sh
> >>  create mode 100755 test/clean.sh
> >>  create mode 100644 test/local.config
> >>  create mode 100755 test/src/opaque_test.sh
> >>  create mode 100755 test/src/prepare.sh
> >>  create mode 100755 test/src/redirect_test.sh
> >>  create mode 100755 test/src/whiteout_test.sh
> >>
> >> diff --git a/Makefile b/Makefile
> >> new file mode 100644
> >> index 0000000..ced5005
> >> --- /dev/null
> >> +++ b/Makefile
> >> @@ -0,0 +1,31 @@
> >> +CFLAGS = -Wall -g
> >> +LFLAGS = -lm
> >> +CC = gcc
> >> +
> >> +all: overlay
> >> +
> >> +overlay: fsck.o common.o lib.o check.o mount.o
> >> +       $(CC) $(LFLAGS) fsck.o common.o lib.o check.o mount.o -o fsck.overlay
> >> +
> >> +fsck.o:
> >> +       $(CC) $(CFLAGS) -c fsck.c
> >> +
> >> +common.o:
> >> +       $(CC) $(CFLAGS) -c common.c
> >> +
> >> +lib.o:
> >> +       $(CC) $(CFLAGS) -c lib.c
> >> +
> >> +check.o:
> >> +       $(CC) $(CFLAGS) -c check.c
> >> +
> >> +mount.o:
> >> +       $(CC) $(CFLAGS) -c mount.c
> >> +
> >> +clean:
> >> +       rm -f *.o fsck.overlay
> >> +       rm -rf bin
> >> +
> >> +install: all
> >> +       mkdir bin
> >> +       cp fsck.overlay bin
> >> diff --git a/README.md b/README.md
> >> new file mode 100644
> >> index 0000000..8de69cd
> >> --- /dev/null
> >> +++ b/README.md
> >> @@ -0,0 +1,88 @@
> >> +fsck.overlay
> >> +============
> >> +
> >> +fsck.overlay is used to check and optionally repair underlying directories
> >> +of overlay-filesystem.
> >> +
> >> +Check the following points:
> >> +
> >> +Whiteouts
> >> +---------
> >> +
> >> +A whiteout is a character device with 0/0 device number. It is used to record
> >> +the removed files or directories, When a whiteout is found in a directory,
> >> +there should be at least one directory or file with the same name in any of the
> >> +corresponding lower layers. If not exist, the whiteout will be treated as orphan
> >> +whiteout and remove.
> >> +
> >> +
> >> +Opaque directories
> >> +------------------
> >> +
> >> +An opaque directory is a directory with "trusted.overlay.opaque" xattr valued
> >> +"y". There are two legal situations of making opaque directory: 1) create
> >> +directory over whiteout 2) creat directory in merged directory. If an opaque
> >> +directory is found, corresponding matching name in lower layers might exist or
> >> +parent directory might merged, If not, the opaque xattr will be treated as
> >> +invalid and remove.
> >> +
> >> +
> >> +Redirect directories
> >> +--------------------
> >> +
> >> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
> >> +valued to the path of the original location from the root of the overlay. It
> >> +is only used when renaming a directory and "redirect dir" feature is enabled.
> >> +If an redirect directory is found, the following must be met:
> >> +
> >> +1) The directory store in redirect xattr should exist in one of lower layers.
> >> +2) The origin directory should be redirected only once in one layer, which mean
> >> +   there is only one redirect xattr point to this origin directory in the
> >> +   specified layer.
> >> +3) A whiteout or an opaque directory with the same name to origin should exist
> >> +   in the same directory as the redirect directory.
> > another redirected upper dir can also be covering the redirect lower target
> > 
> Do you mean the following case: upper's dir_rd cover lower1's redirect dir_rd ?
> 
> Upper:                    dir_rd(redurected,opaque)   dir2(whiteout)
> Lower1:   dir(whiteout)   dir_rd(redurected)          dir2
> Lower0:   dir
> 
> If so, I think my program handled this case already.
> >> +
> >> +If not, 1) The redirect xattr is invalid and need to remove 2) One of the
> >> +redirect xattr is redundant but not sure which one is, ask user 3) Create a
> >> +whiteout device or set opaque xattr to an existing directory if the parent
> >> +directory was meregd or remove xattr if not.
> > 
> > You can consult origin xattr, decode it and fix redirect accordingly.
> > I have plans to implement this in kernel as well with 'verify_dir' mount option,
> > but maybe is it better to do this with fsck tool.
> > Restoring unset origin xattr, which I also sent a kernel patch for can also be
> > done by fsck as Miklos also suggested.
> > 
> 
> Yes, I tried to use origin xattr and get the ovl_fh. We can put the redirect
> information into ovl_fh to let fsck fix redirect latter.
> 
> > 
> >> +
> >> +Usage
> >> +=====
> >> +
> >> +1. Ensure overlay filesystem is not mounted based on directories which need to
> >> +   check.
> >> +
> >> +2. Run fsck.overlay program:
> >> +   Usage:
> >> +   fsck.overlay [-l lowerdir] [-u upperdir] [-w workdir] [-avhV]
> >> +
> >> +   Options:
> >> +   -l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,
> >> +                             multiple lower use ':' as separator.
> >> +   -u, --upperdir=UPPERDIR   specify upper directory of overlayfs
> >> +   -w, --workdir=WORKDIR     specify work directory of overlayfs
> >> +   -a, --auto                repair automatically (no questions)
> >> +   -v, --verbose             print more messages of overlayfs
> >> +   -h, --help                display this usage of overlayfs
> >> +   -V, --version             display version information
> >> +
> >> +3. Exit value:
> >> +   0      No errors
> >> +   1      Filesystem errors corrected
> >> +   2      System should be rebooted
> >> +   4      Filesystem errors left uncorrected
> >> +   8      Operational error
> >> +   16     Usage or syntax error
> >> +   32     Checking canceled by user request
> >> +   128    Shared-library error
> >> +
> >> +Todo
> >> +====
> >> +
> >> +1. Overlay filesystem mounted check. Prevent fscking when overlay is
> >> +   online. Now, We cannot distinguish mounted directories if overlayfs was
> >> +   mounted with relative path.
> >> +2. Symbolic link check.
> >> +3. Check origin/impure/nlink xattr.
> >> +4. ...
> >> diff --git a/check.c b/check.c
> >> new file mode 100644
> >> index 0000000..1794501
> >> --- /dev/null
> >> +++ b/check.c
> >> @@ -0,0 +1,482 @@
> >> +/*
> >> + *
> >> + *     Check and fix inconsistency for all underlying layers of overlay
> >> + *
> >> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> > 
> > I guess you are not obliged to, but it would probably be better for
> > you to assign
> > copyright either to you or to your employer.
> > And choosing a license explicitly is also recommended.
> > IMO it would be best if you could keep at least part of the code
> > under LGPL (or a like) and start with a mini liboverlay that can grow over time
> > and be used by other projects.
> > 
> 
> will fix it.
> 
> >> + *
> >> + */
> >> +
> >> +#include <stdlib.h>
> >> +#include <stdio.h>
> >> +#include <string.h>
> >> +#include <errno.h>
> >> +#include <unistd.h>
> >> +#include <stdbool.h>
> >> +#include <sys/types.h>
> >> +#include <sys/xattr.h>
> >> +#include <sys/stat.h>
> >> +#include <linux/limits.h>
> >> +
> >> +#include "common.h"
> >> +#include "lib.h"
> >> +#include "check.h"
> >> +
> >> +/* Underlying information */
> >> +struct ovl_lower_check {
> >> +       unsigned int type;      /* check extent type */
> >> +
> >> +       bool exist;
> >> +       char path[PATH_MAX];    /* exist pathname found, only valid if exist */
> >> +       struct stat st;         /* only valid if exist */
> >> +};
> >> +
> >> +/* Redirect information */
> >> +struct ovl_redirect_entry {
> >> +       struct ovl_redirect_entry *next;
> >> +
> >> +       char origin[PATH_MAX];  /* origin directory path */
> >> +
> >> +       char path[PATH_MAX];    /* redirect directory */
> >> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> >> +       int dirnum;             /* only valid when dirtype==OVL_LOWER */
> >> +};
> >> +
> >> +/* Whiteout */
> >> +#define WHITEOUT_DEV   0
> >> +#define WHITEOUT_MOD   0
> >> +
> >> +extern char **lowerdir;
> >> +extern char upperdir[];
> >> +extern char workdir[];
> >> +extern unsigned int lower_num;
> >> +extern int flags;
> >> +extern int status;
> >> +
> >> +static inline mode_t file_type(const struct stat *status)
> >> +{
> >> +       return status->st_mode & S_IFMT;
> >> +}
> >> +
> >> +static inline bool is_whiteout(const struct stat *status)
> >> +{
> >> +       return (file_type(status) == S_IFCHR) && (status->st_rdev == WHITEOUT_DEV);
> >> +}
> >> +
> >> +static inline bool is_dir(const struct stat *status)
> >> +{
> >> +       return file_type(status) == S_IFDIR;
> >> +}
> >> +
> >> +static bool is_dir_xattr(const char *pathname, const char *xattrname)
> >> +{
> >> +       char val;
> >> +       ssize_t ret;
> >> +
> >> +       ret = getxattr(pathname, xattrname, &val, 1);
> >> +       if ((ret < 0) && !(errno == ENODATA || errno == ENOTSUP)) {
> >> +               print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> >> +                           xattrname, strerror(errno));
> >> +               return false;
> >> +       }
> >> +
> >> +       return (ret == 1 && val == 'y') ? true : false;
> >> +}
> >> +
> >> +static inline bool ovl_is_opaque(const char *pathname)
> >> +{
> >> +       return is_dir_xattr(pathname, OVL_OPAQUE_XATTR);
> >> +}
> >> +
> >> +static inline int ovl_remove_opaque(const char *pathname)
> >> +{
> >> +       return remove_xattr(pathname, OVL_OPAQUE_XATTR);
> >> +}
> >> +
> >> +static inline int ovl_set_opaque(const char *pathname)
> >> +{
> >> +       return set_xattr(pathname, OVL_OPAQUE_XATTR, "y", 1);
> >> +}
> >> +
> >> +static int ovl_get_redirect(const char *pathname, size_t dirlen,
> >> +                           size_t filelen, char **redirect)
> >> +{
> >> +       char *buf = NULL;
> >> +       ssize_t ret;
> >> +
> >> +       ret = get_xattr(pathname, OVL_REDIRECT_XATTR, &buf, NULL);
> >> +       if (ret <= 0 || !buf)
> >> +               return ret;
> >> +
> >> +       if (buf[0] != '/') {
> >> +               size_t baselen = strlen(pathname)-filelen-dirlen;
> >> +
> >> +               buf = srealloc(buf, ret + baselen + 1);
> >> +               memmove(buf + baselen, buf, ret);
> >> +               memcpy(buf, pathname+dirlen, baselen);
> >> +               buf[ret + baselen] = '\0';
> >> +       }
> >> +
> >> +       *redirect = buf;
> >> +       return 0;
> >> +}
> >> +
> >> +static inline int ovl_remove_redirect(const char *pathname)
> >> +{
> >> +       return remove_xattr(pathname, OVL_REDIRECT_XATTR);
> >> +}
> >> +
> >> +static inline int ovl_create_whiteout(const char *pathname)
> >> +{
> >> +       int ret;
> >> +
> >> +       ret = mknod(pathname, S_IFCHR | WHITEOUT_MOD, makedev(0, 0));
> >> +       if (ret)
> >> +               print_err(_("Cannot mknod %s:%s\n"),
> >> +                           pathname, strerror(errno));
> >> +       return ret;
> >> +}
> >> +
> >> +/*
> >> + * Scan each lower dir lower than 'start' and check type matching,
> >> + * we stop scan if we found something.
> >> + *
> >> + * skip: skip whiteout.
> >> + *
> >> + */
> >> +static int ovl_check_lower(const char *path, unsigned int start,
> >> +                          struct ovl_lower_check *chk, bool skip)
> >> +{
> >> +       char lower_path[PATH_MAX];
> >> +       struct stat st;
> >> +       unsigned int i;
> >> +
> >> +       for (i = start; i < lower_num; i++) {
> >> +               snprintf(lower_path, sizeof(lower_path), "%s%s", lowerdir[i], path);
> >> +
> >> +               if (lstat(lower_path, &st) != 0) {
> > 
> > IMO it would be better to keep open fd for lower layers root and use fstatat()
> > with layer root fd.
> > 
> 
> It's a good idea, will change it.
> 
> >> +                       if (errno != ENOENT && errno != ENOTDIR) {
> >> +                               print_err(_("Cannot stat %s: %s\n"),
> >> +                                           lower_path, strerror(errno));
> >> +                               return -1;
> >> +                       }
> >> +                       continue;
> >> +               }
> >> +
> >> +               if (skip && is_whiteout(&st))
> >> +                       continue;
> >> +
> >> +               chk->exist = true;
> >> +               chk->st = st;
> >> +               strncpy(chk->path, lower_path, sizeof(chk->path));
> >> +               break;
> >> +       }
> >> +
> >> +       return 0;
> >> +}
> >> +
> >> +/*
> >> + * Scan each underlying dirs under specified dir if a whiteout is
> >> + * found, check it's orphan or not. In auto-mode, orphan whiteouts
> >> + * will be removed directly.
> >> + */
> >> +static int ovl_check_whiteout(struct scan_ctx *sctx)
> >> +{
> >> +       const char *pathname = sctx->pathname;
> >> +       const struct stat *st = sctx->st;
> >> +       struct ovl_lower_check chk = {0};
> >> +       unsigned int start;
> >> +       int ret = 0;
> >> +
> >> +       /* Is a whiteout ? */
> >> +       if (!is_whiteout(st))
> >> +               return 0;
> >> +
> >> +       /* Is Whiteout in the bottom lower dir ? */
> >> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> >> +               goto remove;
> >> +
> >> +       /*
> >> +        * Scan each corresponding lower directroy under this layer,
> >> +        * check is there a file or dir with the same name.
> >> +        */
> >> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> >> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> >> +       if (ret)
> >> +               return ret;
> >> +       if (chk.exist && !is_whiteout(&chk.st))
> >> +               goto out;
> >> +
> >> +remove:
> >> +       /* Remove orphan whiteout directly or ask user */
> >> +       print_info(_("Orphan whiteout: %s "), pathname);
> >> +       if (!ask_question("Remove", 1))
> >> +               return 0;
> >> +
> >> +       ret = unlink(pathname);
> >> +       if (ret) {
> >> +               print_err(_("Cannot unlink %s: %s\n"), pathname,
> >> +                           strerror(errno));
> >> +               return ret;
> >> +       }
> >> +out:
> >> +       return ret;
> >> +}
> >> +
> >> +/*
> >> + * Scan each underlying under specified dir if an opaque dir is found,
> >> + * check the opaque xattr is invalid or not. In auto-mode, invalid
> >> + * opaque xattr will be removed directly.
> >> + * Do the follow checking:
> >> + * 1) Check lower matching name exist or not.
> >> + * 2) Check parent dir is merged or pure.
> >> + * If no lower matching and parent is not merged, remove opaque xattr.
> >> + */
> >> +static int ovl_check_opaque(struct scan_ctx *sctx)
> >> +{
> >> +       const char *pathname = sctx->pathname;
> >> +       char parent_path[PATH_MAX];
> >> +       struct ovl_lower_check chk = {0};
> >> +       unsigned int start;
> >> +       int ret = 0;
> >> +
> >> +       /* Is opaque ? */
> >> +       if (!ovl_is_opaque(pathname))
> >> +               goto out;
> >> +
> >> +       /* Opaque dir in last lower dir ? */
> >> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> >> +               goto remove;
> >> +
> >> +       /*
> >> +        * Scan each corresponding lower directroy under this layer,
> >> +        * check if there is a file or dir with the same name.
> >> +        */
> >> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> >> +       ret = ovl_check_lower(pathname + sctx->dirlen, start, &chk, true);
> >> +       if (ret)
> >> +               return ret;
> >> +       if (chk.exist && !is_whiteout(&chk.st))
> >> +               goto out;
> >> +
> >> +       /* Check parent directory merged or pure */
> >> +       memcpy(parent_path, pathname, sctx->pathlen-sctx->filelen);
> >> +       ret = ovl_check_lower(parent_path + sctx->dirlen, start, &chk, false);
> >> +       if (ret)
> >> +               return ret;
> >> +       if (chk.exist && is_dir(&chk.st))
> >> +               goto out;
> >> +
> >> +remove:
> >> +       /* Remove opaque xattr or ask user */
> >> +       print_info(_("Invalid opaque xattr: %s "), pathname);
> >> +       if (!ask_question("Remove", 1))
> >> +               return 0;
> >> +
> >> +       ret = ovl_remove_opaque(pathname);
> >> +out:
> >> +       return ret;
> >> +}
> >> +
> >> +static struct ovl_redirect_entry *redirect_list = NULL;
> >> +
> >> +static void ovl_redirect_entry_add(const char *path, int dirtype,
> >> +                                  int dirnum, const char *origin)
> >> +{
> >> +       struct ovl_redirect_entry *last, *new;
> >> +
> >> +       new = smalloc(sizeof(*new));
> >> +
> >> +       print_debug(_("Redirect entry add: [%s %s %s %d]\n"),
> >> +                     origin, path, (dirtype == OVL_UPPER) ? "UP" : "LOW",
> >> +                     (dirtype == OVL_UPPER) ? 0 : dirnum);
> >> +
> >> +       if (!redirect_list) {
> >> +               redirect_list = new;
> >> +       } else {
> >> +               for (last = redirect_list; last->next; last = last->next);
> >> +               last->next = new;
> >> +       }
> >> +       new->next = NULL;
> >> +       new->dirtype = dirtype;
> >> +       new->dirnum = dirnum;
> >> +       strncpy(new->origin, origin, sizeof(new->origin));
> >> +       strncpy(new->path, path, sizeof(new->path));
> >> +}
> >> +
> >> +static bool vol_redirect_entry_find(const char *origin, int dirtype,
> >> +                                   int dirnum, char **founded)
> >> +{
> >> +       struct ovl_redirect_entry *entry;
> >> +
> >> +       if (!redirect_list)
> >> +               return false;
> >> +
> >> +       for (entry = redirect_list; entry; entry = entry->next) {
> >> +               bool same_layer;
> >> +
> >> +               print_debug(_("Redirect entry found:[%s %s %s %d]\n"),
> >> +                             entry->origin, entry->path,
> >> +                             (entry->dirtype == OVL_UPPER) ? "UP" : "LOW",
> >> +                             (entry->dirtype == OVL_UPPER) ? 0 : entry->dirnum);
> >> +
> >> +               same_layer = ((entry->dirtype == dirtype) &&
> >> +                             (dirtype == OVL_LOWER ? (entry->dirnum == dirnum) : true));
> >> +
> >> +               if (same_layer && !strcmp(entry->origin, origin)) {
> >> +                       *founded = entry->path;
> >> +                       return true;
> >> +               }
> >> +       }
> >> +
> >> +       return false;
> >> +}
> >> +
> >> +static void vol_redirect_free(void)
> >> +{
> >> +       struct ovl_redirect_entry *entry;
> >> +
> >> +       while (redirect_list) {
> >> +               entry = redirect_list;
> >> +               redirect_list = redirect_list->next;
> >> +               free(entry);
> >> +       }
> >> +}
> >> +
> >> +/*
> >> + * Get redirect origin directory stored in the xattr, check it's invlaid
> >> + * or not, In auto-mode, invalid redirect xattr will be removed directly.
> >> + * Do the follow checking:
> >> + * 1) Check the origin directory exist or not. If not, remove xattr.
> >> + * 2) Count how many directories the origin directory was redirected by.
> >> + *    If more than one in the same layer, there must be some inconsistency
> >> + *    but not sure, just warn.
> >> + * 3) Check and fix the missing whiteout or opaque in redierct parent dir.
> >> + */
> >> +static int ovl_check_redirect(struct scan_ctx *sctx)
> >> +{
> >> +       const char *pathname = sctx->pathname;
> >> +       struct ovl_lower_check chk = {0};
> >> +       char redirect_rpath[PATH_MAX];
> >> +       struct stat rst;
> >> +       char *redirect = NULL;
> >> +       unsigned int start;
> >> +       int ret = 0;
> >> +
> >> +       /* Get redirect */
> >> +       ret = ovl_get_redirect(pathname, sctx->dirlen,
> >> +                              sctx->filelen, &redirect);
> >> +       if (ret || !redirect)
> >> +               return ret;
> >> +
> >> +       print_debug(_("Dir %s has redirect %s\n"), pathname, redirect);
> >> +
> >> +       /* Redirect dir in last lower dir ? */
> >> +       if (sctx->dirtype == OVL_LOWER && sctx->num == lower_num-1)
> >> +               goto remove;
> >> +
> >> +       /* Scan lower directories to check redirect dir exist or not */
> >> +       start = (sctx->dirtype == OVL_LOWER) ? sctx->num + 1 : 0;
> >> +       ret = ovl_check_lower(redirect, start, &chk, false);
> >> +       if (ret)
> >> +               goto out;
> >> +       if (chk.exist && is_dir(&chk.st)) {
> >> +               char *tmp;
> >> +
> >> +               /* Check duplicate in same layer */
> >> +               if (vol_redirect_entry_find(chk.path, sctx->dirtype,
> >> +                                           sctx->num, &tmp)) {
> >> +                       print_info("Duplicate redirect directories found:\n");
> >> +                       print_info("origin:%s current:%s latest:%s\n",
> >> +                                  chk.path, pathname, tmp);
> >> +
> >> +                       set_st_inconsistency(&status);
> >> +               }
> >> +
> >> +               ovl_redirect_entry_add(pathname, sctx->dirtype,
> >> +                                      sctx->num, chk.path);
> >> +
> >> +               /* Check and fix whiteout or opaque dir */
> >> +               snprintf(redirect_rpath, sizeof(redirect_rpath), "%s%s",
> >> +                        sctx->dirname, redirect);
> >> +               if (lstat(redirect_rpath, &rst) != 0) {
> >> +                       if (errno != ENOENT && errno != ENOTDIR) {
> >> +                               print_err(_("Cannot stat %s: %s\n"),
> >> +                                           redirect_rpath, strerror(errno));
> >> +                               goto out;
> >> +                       }
> >> +
> >> +                       /* Found nothing, create a whiteout */
> >> +                       ret = ovl_create_whiteout(redirect_rpath);
> >> +
> >> +               } else if (is_dir(&rst) && !ovl_is_opaque(redirect_rpath)) {
> >> +                       /* Found a directory but not opaqued, fix opaque xattr */
> >> +                       ret = ovl_set_opaque(redirect_rpath);
> >> +               }
> >> +
> >> +               goto out;
> >> +       }
> >> +
> >> +remove:
> >> +       /* Remove redirect xattr or ask user */
> >> +       print_info(_("Invalid redirect xattr: %s "), pathname);
> >> +       if (!ask_question("Remove", 1))
> >> +               goto out;
> >> +
> >> +       ret = ovl_remove_redirect(pathname);
> >> +out:
> >> +       free(redirect);
> >> +       return ret;
> >> +}
> >> +
> >> +static struct scan_operations ovl_scan_ops = {
> >> +       .whiteout = ovl_check_whiteout,
> >> +       .opaque = ovl_check_opaque,
> >> +       .redirect = ovl_check_redirect,
> >> +};
> >> +
> >> +static void ovl_scan_clean(void)
> >> +{
> >> +       /* Clean redirect entry record */
> >> +       vol_redirect_free();
> >> +}
> >> +
> >> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> >> +int ovl_scan_fix(void)
> >> +{
> >> +       struct scan_ctx sctx;
> >> +       unsigned int i;
> >> +       int ret = 0;
> >> +
> >> +       if (flags | FL_VERBOSE)
> >> +               print_info(_("Scan and fix: [whiteouts|opaque|redirectdir]\n"));
> >> +
> >> +       /* Scan upper directory */
> >> +       if (flags | FL_VERBOSE)
> >> +               print_info(_("Scan upper directory: %s\n"), upperdir);
> >> +
> >> +       sctx.dirname = upperdir;
> >> +       sctx.dirlen = strlen(upperdir);
> >> +       sctx.dirtype = OVL_UPPER;
> >> +
> >> +       ret = scan_dir(&sctx, &ovl_scan_ops);
> >> +       if (ret)
> >> +               goto out;
> >> +
> >> +       /* Scan every lower directories */
> >> +       for (i = 0; i < lower_num; i++) {
> >> +               if (flags | FL_VERBOSE)
> >> +                       print_info(_("Scan upper directory: %s\n"), lowerdir[i]);
> >> +
> >> +               sctx.dirname = lowerdir[i];
> >> +               sctx.dirlen = strlen(lowerdir[i]);
> >> +               sctx.dirtype = OVL_LOWER;
> >> +               sctx.num = i;
> >> +
> >> +               ret = scan_dir(&sctx, &ovl_scan_ops);
> >> +               if (ret)
> >> +                       goto out;
> >> +       }
> >> +out:
> >> +       ovl_scan_clean();
> >> +       return ret;
> >> +}
> >> diff --git a/check.h b/check.h
> >> new file mode 100644
> >> index 0000000..373ff3a
> >> --- /dev/null
> >> +++ b/check.h
> >> @@ -0,0 +1,7 @@
> >> +#ifndef OVL_WHITECHECK_H
> >> +#define OVL_WHITECHECK_H
> >> +
> >> +/* Scan upperdir and each lowerdirs, check and fix inconsistency */
> >> +int ovl_scan_fix(void);
> >> +
> >> +#endif /* OVL_WHITECHECK_H */
> >> diff --git a/common.c b/common.c
> >> new file mode 100644
> >> index 0000000..4e77045
> >> --- /dev/null
> >> +++ b/common.c
> >> @@ -0,0 +1,95 @@
> >> +/*
> >> + *
> >> + *     Common things for all utilities
> >> + *
> >> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> >> + *
> >> + */
> >> +
> >> +#include <stdio.h>
> >> +#include <string.h>
> >> +#include <stdlib.h>
> >> +#include <unistd.h>
> >> +#include <stdarg.h>
> >> +#include <errno.h>
> >> +#include <sys/types.h>
> >> +#include <sys/stat.h>
> >> +#include "common.h"
> >> +#include "config.h"
> >> +
> >> +extern char *program_name;
> >> +
> >> +/* #define DEBUG 1 */
> >> +#ifdef DEBUG
> >> +void print_debug(char *fmtstr, ...)
> >> +{
> >> +       va_list args;
> >> +
> >> +       va_start(args, fmtstr);
> >> +       fprintf(stdout, "%s:[Debug]: ", program_name);
> >> +       vfprintf(stdout, fmtstr, args);
> >> +       va_end(args);
> >> +}
> >> +#else
> >> +void print_debug (char *fmtstr, ...) {}
> >> +#endif
> >> +
> >> +void print_info(char *fmtstr, ...)
> >> +{
> >> +       va_list args;
> >> +
> >> +       va_start(args, fmtstr);
> >> +       vfprintf(stdout, fmtstr, args);
> >> +       va_end(args);
> >> +}
> >> +
> >> +void print_err(char *fmtstr, ...)
> >> +{
> >> +       va_list args;
> >> +
> >> +       va_start(args, fmtstr);
> >> +       fprintf(stderr, "%s:[Error]: ", program_name);
> >> +       vfprintf(stderr, fmtstr, args);
> >> +       va_end(args);
> >> +}
> >> +
> >> +void *smalloc(size_t size)
> >> +{
> >> +       void *new = malloc(size);
> >> +
> >> +       if (!new) {
> >> +               print_err(_("malloc error:%s\n"), strerror(errno));
> >> +               exit(1);
> >> +       }
> >> +
> >> +       memset(new, 0, size);
> >> +       return new;
> >> +}
> >> +
> >> +void *srealloc(void *addr, size_t size)
> >> +{
> >> +       void *re = realloc(addr, size);
> >> +
> >> +       if (!re) {
> >> +               print_err(_("malloc error:%s\n"), strerror(errno));
> >> +               exit(1);
> >> +       }
> >> +       return re;
> >> +}
> >> +
> >> +char *sstrdup(const char *src)
> >> +{
> >> +       char *dst = strdup(src);
> >> +
> >> +       if (!dst) {
> >> +               print_err(_("strdup error:%s\n"), strerror(errno));
> >> +               exit(1);
> >> +       }
> >> +
> >> +       return dst;
> >> +}
> >> +
> >> +void version(void)
> >> +{
> >> +       printf(_("Overlay utilities version %s\n"), PACKAGE_VERSION);
> >> +}
> >> diff --git a/common.h b/common.h
> >> new file mode 100644
> >> index 0000000..c4707e7
> >> --- /dev/null
> >> +++ b/common.h
> >> @@ -0,0 +1,34 @@
> >> +#ifndef OVL_COMMON_H
> >> +#define OVL_COMMON_H
> >> +
> >> +#ifndef __attribute__
> >> +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
> >> +#  define __attribute__(x)
> >> +# endif
> >> +#endif
> >> +
> >> +#ifdef USE_GETTEXT
> >> +#include <libintl.h>
> >> +#define _(x)   gettext((x))
> >> +#else
> >> +#define _(x)   (x)
> >> +#endif
> >> +
> >> +/* Print an error message */
> >> +void print_err(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> >> +
> >> +/* Print an info message */
> >> +void print_info(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> >> +
> >> +/* Print an debug message */
> >> +void print_debug(char *, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
> >> +
> >> +/* Safety wrapper */
> >> +void *smalloc(size_t size);
> >> +void *srealloc(void *addr, size_t size);
> >> +char *sstrdup(const char *src);
> >> +
> >> +/* Print program version */
> >> +void version(void);
> >> +
> >> +#endif /* OVL_COMMON_H */
> >> diff --git a/config.h b/config.h
> >> new file mode 100644
> >> index 0000000..deac089
> >> --- /dev/null
> >> +++ b/config.h
> >> @@ -0,0 +1,22 @@
> >> +#ifndef OVL_CONFIG_H
> >> +#define OVL_CONFIG_H
> >> +
> >> +/* program version */
> >> +#define PACKAGE_VERSION        "v1.0.0"
> >> +
> >> +/* overlay max lower stacks (the same to kernel overlayfs driver) */
> >> +#define OVL_MAX_STACK 500
> >> +
> >> +/* File with mounted filesystems */
> >> +#define MOUNT_TAB "/proc/mounts"
> >> +
> >> +/* Name of overlay filesystem type */
> >> +#define OVERLAY_NAME "overlay"
> >> +#define OVERLAY_NAME_OLD "overlayfs"
> >> +
> >> +/* Mount options */
> >> +#define OPT_LOWERDIR "lowerdir="
> >> +#define OPT_UPPERDIR "upperdir="
> >> +#define OPT_WORKDIR "workdir="
> >> +
> >> +#endif
> >> diff --git a/fsck.c b/fsck.c
> >> new file mode 100644
> >> index 0000000..cbcb8e9
> >> --- /dev/null
> >> +++ b/fsck.c
> >> @@ -0,0 +1,179 @@
> >> +/*
> >> + *
> >> + *     Utility to fsck overlay
> >> + *
> >> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> >> + *
> >> + */
> >> +
> >> +#include <stdio.h>
> >> +#include <stdlib.h>
> >> +#include <string.h>
> >> +#include <errno.h>
> >> +#include <getopt.h>
> >> +#include <libgen.h>
> >> +#include <stdbool.h>
> >> +#include <sys/types.h>
> >> +#include <sys/stat.h>
> >> +#include <linux/limits.h>
> >> +
> >> +#include "common.h"
> >> +#include "config.h"
> >> +#include "lib.h"
> >> +#include "check.h"
> >> +#include "mount.h"
> >> +
> >> +char *program_name;
> >> +
> >> +char **lowerdir;
> >> +char upperdir[PATH_MAX] = {0};
> >> +char workdir[PATH_MAX] = {0};
> >> +unsigned int lower_num;
> >> +int flags;             /* user input option flags */
> >> +int status;            /* fsck scan status */
> >> +
> >> +/* Cleanup lower directories buf */
> >> +static void ovl_clean_lowerdirs(void)
> >> +{
> >> +       unsigned int i;
> >> +
> >> +       for (i = 0; i < lower_num; i++) {
> >> +               free(lowerdir[i]);
> >> +               lowerdir[i] = NULL;
> >> +               lower_num = 0;
> >> +       }
> >> +       free(lowerdir);
> >> +       lowerdir = NULL;
> >> +}
> >> +
> >> +static void usage(void)
> >> +{
> >> +       print_info(_("Usage:\n\t%s [-l lowerdir] [-u upperdir] [-w workdir] "
> >> +                   "[-avhV]\n\n"), program_name);
> >> +       print_info(_("Options:\n"
> >> +                   "-l, --lowerdir=LOWERDIR   specify lower directories of overlayfs,\n"
> >> +                   "                          multiple lower use ':' as separator\n"
> >> +                   "-u, --upperdir=UPPERDIR   specify upper directory of overlayfs\n"
> >> +                   "-w, --workdir=WORKDIR     specify work directory of overlayfs\n"
> >> +                   "-a, --auto                repair automatically (no questions)\n"
> >> +                   "-v, --verbose             print more messages of overlayfs\n"
> >> +                   "-h, --help                display this usage of overlayfs\n"
> >> +                   "-V, --version             display version information\n"));
> >> +       exit(1);
> >> +}
> >> +
> >> +static void parse_options(int argc, char *argv[])
> >> +{
> >> +       char *lowertemp;
> >> +       int c;
> >> +       int ret = 0;
> >> +
> >> +       struct option long_options[] = {
> >> +               {"lowerdir", required_argument, NULL, 'l'},
> >> +               {"upperdir", required_argument, NULL, 'u'},
> >> +               {"workdir", required_argument, NULL, 'w'},
> >> +               {"auto", no_argument, NULL, 'a'},
> >> +               {"verbose", no_argument, NULL, 'v'},
> >> +               {"version", no_argument, NULL, 'V'},
> >> +               {"help", no_argument, NULL, 'h'},
> >> +               {NULL, 0, NULL, 0}
> >> +       };
> >> +
> >> +       while ((c = getopt_long(argc, argv, "l:u:w:avVh", long_options, NULL)) != -1) {
> >> +               switch (c) {
> >> +               case 'l':
> >> +                       lowertemp = strdup(optarg);
> >> +                       ret = ovl_resolve_lowerdirs(lowertemp, &lowerdir, &lower_num);
> >> +                       free(lowertemp);
> >> +                       break;
> >> +               case 'u':
> >> +                       if (realpath(optarg, upperdir)) {
> >> +                               print_debug(_("Upperdir:%s\n"), upperdir);
> >> +                               flags |= FL_UPPER;
> >> +                       } else {
> >> +                               print_err(_("Failed to resolve upperdir:%s\n"), optarg);
> >> +                               ret = -1;
> >> +                       }
> >> +                       break;
> >> +               case 'w':
> >> +                       if (realpath(optarg, workdir)) {
> >> +                               print_debug(_("Workdir:%s\n"), workdir);
> >> +                               flags |= FL_WORK;
> >> +                       } else {
> >> +                               print_err(_("Failed to resolve workdir:%s\n"), optarg);
> >> +                               ret = -1;
> >> +                       }
> >> +                       break;
> >> +               case 'a':
> >> +                       flags |= FL_AUTO;
> >> +                       break;
> >> +               case 'v':
> >> +                       flags |= FL_VERBOSE;
> >> +                       break;
> >> +               case 'V':
> >> +                       version();
> >> +                       return;
> >> +               case 'h':
> >> +               default:
> >> +                       usage();
> >> +                       return;
> >> +               }
> >> +
> >> +               if (ret)
> >> +                       exit(1);
> >> +       }
> >> +
> >> +       if (!lower_num || (!(flags & FL_UPPER) && lower_num == 1)) {
> >> +               print_info(_("Please specify correct lowerdirs and upperdir\n"));
> >> +               usage();
> >> +       }
> >> +}
> >> +
> >> +void fsck_status_check(int *val)
> >> +{
> >> +       if (status & OVL_ST_INCONSISTNECY) {
> >> +               *val |= FSCK_UNCORRECTED;
> >> +               print_info(_("Still have unexpected inconsistency!\n"));
> >> +       }
> >> +
> >> +       if (status & OVL_ST_ABORT) {
> >> +               *val |= FSCK_ERROR;
> >> +               print_info(_("Cannot continue, aborting\n"));
> >> +       }
> >> +}
> >> +
> >> +int main(int argc, char *argv[])
> >> +{
> >> +       bool mounted;
> >> +       int err = 0;
> >> +       int exit_value = 0;
> >> +
> >> +       program_name = basename(argv[0]);
> >> +
> >> +       parse_options(argc, argv);
> >> +
> >> +       /* Ensure overlay filesystem not mounted */
> >> +       if ((err = ovl_check_mount(&mounted)))
> >> +               goto out;
> >> +       if (!mounted) {
> >> +               set_st_abort(&status);
> >> +               goto out;
> >> +       }
> >> +
> >> +       /* Scan and fix */
> >> +       if ((err = ovl_scan_fix()))
> >> +               goto out;
> >> +
> >> +out:
> >> +       fsck_status_check(&exit_value);
> >> +       ovl_clean_lowerdirs();
> >> +
> >> +       if (err)
> >> +               exit_value |= FSCK_ERROR;
> >> +       if (exit_value)
> >> +               print_info("WARNING: Filesystem check failed, may not clean\n");
> >> +       else
> >> +               print_info("Filesystem clean\n");
> >> +
> >> +       return exit_value;
> >> +}
> >> diff --git a/lib.c b/lib.c
> >> new file mode 100644
> >> index 0000000..a6832fe
> >> --- /dev/null
> >> +++ b/lib.c
> >> @@ -0,0 +1,197 @@
> >> +/*
> >> + *
> >> + *     Common things for all utilities
> >> + *
> >> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> >> + *
> >> + */
> >> +
> >> +#include <stdlib.h>
> >> +#include <stdio.h>
> >> +#include <unistd.h>
> >> +#include <errno.h>
> >> +#include <string.h>
> >> +#include <stdbool.h>
> >> +#include <sys/types.h>
> >> +#include <sys/stat.h>
> >> +#include <sys/xattr.h>
> >> +#include <fts.h>
> >> +
> >> +#include "common.h"
> >> +#include "lib.h"
> >> +
> >> +extern int flags;
> >> +extern int status;
> >> +
> >> +static int ask_yn(const char *question, int def)
> >> +{
> >> +       char ans[16];
> >> +
> >> +       print_info(_("%s ? [%s]: \n"), question, def ? _("y") : _("n"));
> >> +       fflush(stdout);
> >> +       while (fgets(ans, sizeof(ans)-1, stdin)) {
> >> +               if (ans[0] == '\n')
> >> +                       return def;
> >> +               else if (!strcasecmp(ans, "y\n") || !strcasecmp(ans, "yes\n"))
> >> +                       return 1;
> >> +               else if (!strcasecmp(ans, "n\n") || !strcasecmp(ans, "no\n"))
> >> +                       return 0;
> >> +               else
> >> +                       print_info(_("Illegal answer. Please input y/n or yes/no:"));
> >> +               fflush(stdout);
> >> +       }
> >> +       return def;
> >> +}
> >> +
> >> +/* Ask user */
> >> +int ask_question(const char *question, int def)
> >> +{
> >> +       if (flags & FL_AUTO) {
> >> +               print_info(_("%s? %s\n"), question, def ? _("y") : _("n"));
> >> +               return def;
> >> +       }
> >> +
> >> +       return ask_yn(question, def);
> >> +}
> >> +
> >> +ssize_t get_xattr(const char *pathname, const char *xattrname,
> >> +                 char **value, bool *exist)
> >> +{
> >> +       char *buf = NULL;
> >> +       ssize_t ret;
> >> +
> >> +       ret = getxattr(pathname, xattrname, NULL, 0);
> >> +       if (ret < 0) {
> >> +               if (errno == ENODATA || errno == ENOTSUP) {
> >> +                       if (exist)
> >> +                               *exist = false;
> >> +                       return 0;
> >> +               } else {
> >> +                       goto fail;
> >> +               }
> >> +       }
> >> +
> >> +       /* Zero size value means xattr exist but value unknown */
> >> +       if (exist)
> >> +               *exist = true;
> >> +       if (ret == 0 || !value)
> >> +               return 0;
> >> +
> >> +       buf = smalloc(ret+1);
> >> +       ret = getxattr(pathname, xattrname, buf, ret);
> >> +       if (ret <= 0)
> >> +               goto fail2;
> >> +
> >> +       buf[ret] = '\0';
> >> +       *value = buf;
> >> +       return ret;
> >> +
> >> +fail2:
> >> +       free(buf);
> >> +fail:
> >> +       print_err(_("Cannot getxattr %s %s: %s\n"), pathname,
> >> +                   xattrname, strerror(errno));
> >> +       return -1;
> >> +}
> >> +
> >> +int set_xattr(const char *pathname, const char *xattrname,
> >> +               void *value, size_t size)
> >> +{
> >> +       int ret;
> >> +
> >> +       ret = setxattr(pathname, xattrname, value, size, XATTR_CREATE);
> >> +       if (ret && ret != EEXIST)
> >> +               goto fail;
> >> +
> >> +       if (ret == EEXIST) {
> >> +               ret = setxattr(pathname, xattrname, value, size, XATTR_REPLACE);
> >> +               if (ret)
> >> +                       goto fail;
> >> +       }
> >> +
> >> +       return 0;
> >> +fail:
> >> +       print_err(_("Cannot setxattr %s %s: %s\n"), pathname,
> >> +                   xattrname, strerror(errno));
> >> +       return ret;
> >> +}
> >> +
> >> +int remove_xattr(const char *pathname, const char *xattrname)
> >> +{
> >> +       int ret;
> >> +       if ((ret = removexattr(pathname, xattrname)))
> >> +               print_err(_("Cannot removexattr %s %s: %s\n"), pathname,
> >> +                           xattrname, strerror(errno));
> >> +       return ret;
> >> +}
> >> +
> >> +static inline int __check_entry(struct scan_ctx *sctx,
> >> +                                 int (*do_check)(struct scan_ctx *))
> >> +{
> >> +       return do_check ? do_check(sctx) : 0;
> >> +}
> >> +
> >> +/* Scan specified directories and invoke callback */
> >> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop)
> >> +{
> >> +       char *paths[2] = {(char *)sctx->dirname, NULL};
> >> +       FTS *ftsp;
> >> +       FTSENT *ftsent;
> >> +       int ret = 0;
> >> +
> >> +       ftsp = fts_open(paths, FTS_NOCHDIR | FTS_PHYSICAL, NULL);
> >> +       if (ftsp == NULL) {
> >> +               print_err(_("Failed to fts open %s:%s\n"),
> >> +                           sctx->dirname, strerror(errno));
> >> +               return -1;
> >> +       }
> >> +
> >> +       while ((ftsent = fts_read(ftsp)) != NULL) {
> >> +               int err;
> >> +
> >> +               print_debug(_("Scan:%-3s %2d %7jd   %-40s %s\n"),
> >> +                       (ftsent->fts_info == FTS_D) ? "d" :
> >> +                       (ftsent->fts_info == FTS_DNR) ? "dnr" :
> >> +                       (ftsent->fts_info == FTS_DP) ? "dp" :
> >> +                       (ftsent->fts_info == FTS_F) ? "f" :
> >> +                       (ftsent->fts_info == FTS_NS) ? "ns" :
> >> +                       (ftsent->fts_info == FTS_SL) ? "sl" :
> >> +                       (ftsent->fts_info == FTS_SLNONE) ? "sln" :
> >> +                       (ftsent->fts_info == FTS_DEFAULT) ? "df" : "???",
> >> +                       ftsent->fts_level, ftsent->fts_statp->st_size,
> >> +                       ftsent->fts_path, ftsent->fts_name);
> >> +
> >> +
> >> +               /* Fillup base context */
> >> +               sctx->pathname = ftsent->fts_path;
> >> +               sctx->pathlen = ftsent->fts_pathlen;
> >> +               sctx->filename = ftsent->fts_name;
> >> +               sctx->filelen = ftsent->fts_namelen;
> >> +               sctx->st = ftsent->fts_statp;
> >> +
> >> +               switch (ftsent->fts_info) {
> >> +               case FTS_DEFAULT:
> >> +                       /* Check whiteouts */
> >> +                       err = __check_entry(sctx, sop->whiteout);
> >> +                       ret = (ret || !err) ? ret : err;
> >> +                       break;
> >> +               case FTS_D:
> >> +                       /* Check opaque and redirect */
> >> +                       err = __check_entry(sctx, sop->opaque);
> >> +                       ret = (ret || !err) ? ret : err;
> >> +
> >> +                       err = __check_entry(sctx, sop->redirect);
> >> +                       ret = (ret || !err) ? ret : err;
> >> +                       break;
> >> +               case FTS_NS:
> >> +               case FTS_DNR:
> >> +               case FTS_ERR:
> >> +                       print_err(_("Failed to fts read %s:%s\n"),
> >> +                                   ftsent->fts_path, strerror(ftsent->fts_errno));
> >> +                       goto out;
> >> +               }
> >> +       }
> >> +out:
> >> +       fts_close(ftsp);
> >> +       return ret;
> >> +}
> >> diff --git a/lib.h b/lib.h
> >> new file mode 100644
> >> index 0000000..463b263
> >> --- /dev/null
> >> +++ b/lib.h
> >> @@ -0,0 +1,73 @@
> >> +#ifndef OVL_LIB_H
> >> +#define OVL_LIB_H
> >> +
> >> +/* Common return value */
> >> +#define FSCK_OK          0     /* No errors */
> >> +#define FSCK_NONDESTRUCT 1     /* File system errors corrected */
> >> +#define FSCK_REBOOT      2     /* System should be rebooted */
> >> +#define FSCK_UNCORRECTED 4     /* File system errors left uncorrected */
> >> +#define FSCK_ERROR       8     /* Operational error */
> >> +#define FSCK_USAGE       16    /* Usage or syntax error */
> >> +#define FSCK_CANCELED   32     /* Aborted with a signal or ^C */
> >> +#define FSCK_LIBRARY     128   /* Shared library error */
> >> +
> >> +/* Fsck status */
> >> +#define OVL_ST_INCONSISTNECY   (1 << 0)
> >> +#define OVL_ST_ABORT           (1 << 1)
> >> +
> >> +/* Option flags */
> >> +#define FL_VERBOSE     (1 << 0)        /* verbose */
> >> +#define FL_UPPER       (1 << 1)        /* specify upper directory */
> >> +#define FL_WORK                (1 << 2)        /* specify work directory */
> >> +#define FL_AUTO                (1 << 3)        /* automactically scan dirs and repair */
> >> +
> >> +/* Scan path type */
> >> +#define OVL_UPPER      0
> >> +#define OVL_LOWER      1
> >> +#define OVL_WORKER     2
> >> +#define OVL_PTYPE_MAX  3
> >> +
> >> +/* Xattr */
> >> +#define OVL_OPAQUE_XATTR       "trusted.overlay.opaque"
> >> +#define OVL_REDIRECT_XATTR     "trusted.overlay.redirect"
> >> +
> >> +/* Directories scan data struct */
> >> +struct scan_ctx {
> >> +       const char *dirname;    /* upper/lower root dir */
> >> +       size_t dirlen;          /* strlen(dirlen) */
> >> +       int dirtype;            /* OVL_UPPER or OVL_LOWER */
> >> +       int num;                /* lower dir depth, lower type use only */
> >> +
> >> +       const char *pathname;   /* file path from the root */
> >> +       size_t pathlen;         /* strlen(pathname) */
> >> +       const char *filename;   /* filename */
> >> +       size_t filelen;         /* strlen(filename) */
> >> +       struct stat *st;        /* file stat */
> >> +};
> >> +
> >> +/* Directories scan callback operations struct */
> >> +struct scan_operations {
> >> +       int (*whiteout)(struct scan_ctx *);
> >> +       int (*opaque)(struct scan_ctx *);
> >> +       int (*redirect)(struct scan_ctx *);
> >> +};
> >> +
> >> +static inline void set_st_inconsistency(int *st)
> >> +{
> >> +       *st |= OVL_ST_INCONSISTNECY;
> >> +}
> >> +
> >> +static inline void set_st_abort(int *st)
> >> +{
> >> +       *st |= OVL_ST_ABORT;
> >> +}
> >> +
> >> +int scan_dir(struct scan_ctx *sctx, struct scan_operations *sop);
> >> +int ask_question(const char *question, int def);
> >> +ssize_t get_xattr(const char *pathname, const char *xattrname,
> >> +                 char **value, bool *exist);
> >> +int set_xattr(const char *pathname, const char *xattrname,
> >> +             void *value, size_t size);
> >> +int remove_xattr(const char *pathname, const char *xattrname);
> >> +
> >> +#endif /* OVL_LIB_H */
> >> diff --git a/mount.c b/mount.c
> >> new file mode 100644
> >> index 0000000..28ce8e5
> >> --- /dev/null
> >> +++ b/mount.c
> >> @@ -0,0 +1,319 @@
> >> +/*
> >> + *
> >> + *     Check mounted overlay
> >> + *
> >> + *     zhangyi (F) <yi.zhang@huawei.com> - Sponsored by Huawei CR
> >> + *
> >> + */
> >> +
> >> +#include <stdio.h>
> >> +#include <stdlib.h>
> >> +#include <string.h>
> >> +#include <errno.h>
> >> +#include <getopt.h>
> >> +#include <libgen.h>
> >> +#include <stdbool.h>
> >> +#include <mntent.h>
> >> +#include <sys/types.h>
> >> +#include <sys/stat.h>
> >> +#include <linux/limits.h>
> >> +
> >> +#include "common.h"
> >> +#include "config.h"
> >> +#include "lib.h"
> >> +#include "check.h"
> >> +#include "mount.h"
> >> +
> >> +struct ovl_mnt_entry {
> >> +       char *lowers;
> >> +       char **lowerdir;
> >> +       unsigned int lowernum;
> >> +       char upperdir[PATH_MAX];
> >> +       char workdir[PATH_MAX];
> >> +};
> >> +
> >> +/* Mount buf allocate a time */
> >> +#define ALLOC_NUM      16
> >> +
> >> +extern char **lowerdir;
> >> +extern char upperdir[];
> >> +extern char workdir[];
> >> +extern unsigned int lower_num;
> >> +
> >> +/*
> >> + * Split directories to individual one.
> >> + * (copied from linux kernel, see fs/overlayfs/super.c)
> >> + */
> > 
> > It would be nice if such functions could be maintained in a file/dir
> > that is synced with the kernel, like the xfs/lib files.
> > For the first release we don't really have to sync this file with the
> > kernel, but at least keep them at a specific file/dir and maybe the
> > kernel driver will follow up later.
> > 
> 
> will consider about it, thx.
> 
> >> +static unsigned int ovl_split_lowerdirs(char *lower)
> >> +{
> >> +       unsigned int ctr = 1;
> >> +       char *s, *d;
> >> +
> >> +       for (s = d = lower;; s++, d++) {
> >> +               if (*s == '\\') {
> >> +                       s++;
> >> +               } else if (*s == ':') {
> >> +                       *d = '\0';
> >> +                       ctr++;
> >> +                       continue;
> >> +               }
> >> +               *d = *s;
> >> +               if (!*s)
> >> +                       break;
> >> +       }
> >> +       return ctr;
> >> +}
> >> +
> >> +/* Resolve each lower directories and check the validity */
> >> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> >> +                         unsigned int *lowernum)
> >> +{
> >> +       unsigned int num;
> >> +       char **dirs;
> >> +       char *p;
> >> +       int i;
> >> +
> >> +       num = ovl_split_lowerdirs(loweropt);
> >> +       if (num > OVL_MAX_STACK) {
> >> +               print_err(_("Too many lower directories:%u, max:%u\n"),
> >> +                           num, OVL_MAX_STACK);
> >> +               return -1;
> >> +       }
> >> +
> >> +       dirs = smalloc(sizeof(char *) * num);
> >> +
> >> +       p = loweropt;
> >> +       for (i = 0; i < num; i++) {
> >> +               dirs[i] = smalloc(PATH_MAX);
> >> +               if (!realpath(p, dirs[i])) {
> >> +                       print_err(_("Failed to resolve lowerdir:%s:%s\n"),
> >> +                                   p, strerror(errno));
> >> +                       goto err;
> >> +               }
> >> +               print_debug(_("Lowerdir %u:%s\n"), i, dirs[i]);
> >> +               p = strchr(p, '\0') + 1;
> >> +       }
> >> +
> >> +       *lowerdir = dirs;
> >> +       *lowernum = num;
> >> +
> >> +       return 0;
> >> +err:
> >> +       for (i--; i >= 0; i--)
> >> +               free(dirs[i]);
> >> +       free(dirs);
> >> +       *lowernum = 0;
> >> +       return -1;
> >> +}
> >> +
> >> +/*
> >> + * Split and return next opt.
> >> + * (copied from linux kernel, see fs/overlayfs/super.c)
> >> + */
> >> +static char *ovl_next_opt(char **s)
> >> +{
> >> +       char *sbegin = *s;
> >> +       char *p;
> >> +
> >> +       if (sbegin == NULL)
> >> +               return NULL;
> >> +
> >> +       for (p = sbegin; *p; p++) {
> >> +               if (*p == '\\') {
> >> +                       p++;
> >> +                       if (!*p)
> >> +                               break;
> >> +               } else if (*p == ',') {
> >> +                       *p = '\0';
> >> +                       *s = p + 1;
> >> +                       return sbegin;
> >> +               }
> >> +       }
> >> +       *s = NULL;
> >> +       return sbegin;
> >> +}
> >> +
> >> +/*
> >> + * Split and parse opt to each directories.
> >> + *
> >> + * Note: FIXME: We cannot distinguish mounted directories if overlayfs was
> >> + * mounted use relative path, so there may have misjudgment.
> >> + */
> >> +static int ovl_parse_opt(char *opt, struct ovl_mnt_entry *entry)
> >> +{
> >> +       char tmp[PATH_MAX] = {0};
> >> +       char *p;
> >> +       int len;
> >> +       int ret;
> >> +       int i;
> >> +
> >> +       while ((p = ovl_next_opt(&opt)) != NULL) {
> >> +               if (!*p)
> >> +                       continue;
> >> +
> >> +               if (!strncmp(p, OPT_UPPERDIR, strlen(OPT_UPPERDIR))) {
> >> +                       len = strlen(p) - strlen(OPT_UPPERDIR) + 1;
> >> +                       strncpy(tmp, p+strlen(OPT_UPPERDIR), len);
> >> +
> >> +                       if (!realpath(tmp, entry->upperdir)) {
> >> +                               print_err(_("Faile to resolve path:%s:%s\n"),
> >> +                                           tmp, strerror(errno));
> >> +                               ret = -1;
> >> +                               goto errout;
> >> +                       }
> >> +               } else if (!strncmp(p, OPT_LOWERDIR, strlen(OPT_LOWERDIR))) {
> >> +                       len = strlen(p) - strlen(OPT_LOWERDIR) + 1;
> >> +                       entry->lowers = smalloc(len);
> >> +                       strncpy(entry->lowers, p+strlen(OPT_LOWERDIR), len);
> >> +
> >> +                       if ((ret = ovl_resolve_lowerdirs(entry->lowers,
> >> +                                       &entry->lowerdir, &entry->lowernum)))
> >> +                               goto errout;
> >> +
> >> +               } else if (!strncmp(p, OPT_WORKDIR, strlen(OPT_WORKDIR))) {
> >> +                       len = strlen(p) - strlen(OPT_WORKDIR) + 1;
> >> +                       strncpy(tmp, p+strlen(OPT_WORKDIR), len);
> >> +
> >> +                       if (!realpath(tmp, entry->workdir)) {
> >> +                               print_err(_("Faile to resolve path:%s:%s\n"),
> >> +                                           tmp, strerror(errno));
> >> +                               ret = -1;
> >> +                               goto errout;
> >> +                       }
> >> +               }
> >> +       }
> >> +
> >> +errout:
> >> +       if (entry->lowernum) {
> >> +               for (i = 0; i < entry->lowernum; i++)
> >> +                       free(entry->lowerdir[i]);
> >> +               free(entry->lowerdir);
> >> +               entry->lowerdir = NULL;
> >> +               entry->lowernum = 0;
> >> +       }
> >> +       free(entry->lowers);
> >> +       entry->lowers = NULL;
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +/* Scan current mounted overlayfs and get used underlying directories */
> >> +static int ovl_scan_mount_init(struct ovl_mnt_entry **ovl_mnt_entries,
> >> +                              int *ovl_mnt_count)
> >> +{
> >> +       struct ovl_mnt_entry *mnt_entries;
> >> +       struct mntent *mnt;
> >> +       FILE *fp;
> >> +       char *opt;
> >> +       int allocated, num = 0;
> >> +
> >> +       fp = setmntent(MOUNT_TAB, "r");
> >> +       if (!fp) {
> >> +               print_err(_("Fail to setmntent %s:%s\n"),
> >> +                           MOUNT_TAB, strerror(errno));
> >> +               return -1;
> >> +       }
> >> +
> >> +       allocated = ALLOC_NUM;
> >> +       mnt_entries = smalloc(sizeof(struct ovl_mnt_entry) * allocated);
> >> +
> >> +       while ((mnt = getmntent(fp))) {
> >> +               if (!strcmp(mnt->mnt_type, OVERLAY_NAME) ||
> >> +                   !strcmp(mnt->mnt_type, OVERLAY_NAME_OLD)) {
> >> +
> >> +                       opt = sstrdup(mnt->mnt_opts);
> >> +                       if (ovl_parse_opt(opt, &mnt_entries[num])) {
> >> +                               free(opt);
> >> +                               continue;
> >> +                       }
> >> +
> >> +                       num++;
> >> +                       if (num % ALLOC_NUM == 0) {
> >> +                               allocated += ALLOC_NUM;
> >> +                               mnt_entries = srealloc(mnt_entries,
> >> +                                    sizeof(struct ovl_mnt_entry) * allocated);
> >> +                       }
> >> +
> >> +                       free(opt);
> >> +               }
> >> +       }
> >> +
> >> +       *ovl_mnt_entries = mnt_entries;
> >> +       *ovl_mnt_count = num;
> >> +
> >> +       endmntent(fp);
> >> +       return 0;
> >> +}
> >> +
> >> +static void ovl_scan_mount_exit(struct ovl_mnt_entry *ovl_mnt_entries,
> >> +                               int ovl_mnt_count)
> >> +{
> >> +       int i,j;
> >> +
> >> +       for (i = 0; i < ovl_mnt_count; i++) {
> >> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++)
> >> +                       free(ovl_mnt_entries[i].lowerdir[j]);
> >> +               free(ovl_mnt_entries[i].lowerdir);
> >> +               free(ovl_mnt_entries[i].lowers);
> >> +       }
> >> +       free(ovl_mnt_entries);
> >> +}
> >> +
> >> +/*
> >> + * Scan every mounted filesystem, check the overlay directories want
> >> + * to check is already mounted. Check and fix an online overlay is not
> >> + * allowed.
> >> + *
> >> + * Note: fsck may modify lower layers, so even match only one directory
> >> + *       is triggered as mounted.
> >> + */
> >> +int ovl_check_mount(bool *pass)
> >> +{
> >> +       struct ovl_mnt_entry *ovl_mnt_entries;
> >> +       int ovl_mnt_entry_count;
> >> +       char *mounted_path;
> >> +       bool mounted;
> >> +       int i,j,k;
> >> +       int ret;
> >> +
> >> +       ret = ovl_scan_mount_init(&ovl_mnt_entries, &ovl_mnt_entry_count);
> >> +       if (ret)
> >> +               return ret;
> >> +
> >> +       /* Only check hard matching */
> >> +       for (i = 0; i < ovl_mnt_entry_count; i++) {
> >> +               /* Check lower */
> >> +               for (j = 0; j < ovl_mnt_entries[i].lowernum; j++) {
> >> +                       for (k = 0; k < lower_num; k++) {
> >> +                               if (!strcmp(lowerdir[k],
> >> +                                           ovl_mnt_entries[i].lowerdir[j])) {
> >> +                                       mounted_path = lowerdir[k];
> >> +                                       mounted = true;
> >> +                                       goto out;
> >> +                               }
> >> +                       }
> >> +               }
> >> +
> >> +               /* Check upper */
> >> +               if (!(strcmp(upperdir, ovl_mnt_entries[i].upperdir))) {
> >> +                       mounted_path = upperdir;
> >> +                       mounted = true;
> >> +                       goto out;
> >> +               }
> >> +
> >> +               /* Check worker */
> >> +               if (workdir[0] != '\0' && !(strcmp(workdir, ovl_mnt_entries[i].workdir))) {
> >> +                       mounted_path = workdir;
> >> +                       mounted = true;
> >> +                       goto out;
> >> +               }
> >> +       }
> >> +out:
> >> +       ovl_scan_mount_exit(ovl_mnt_entries, ovl_mnt_entry_count);
> >> +
> >> +       if (mounted)
> >> +               print_info(_("Dir %s is mounted\n"), mounted_path);
> >> +       *pass = !mounted;
> >> +
> >> +       return 0;
> >> +}
> >> diff --git a/mount.h b/mount.h
> >> new file mode 100644
> >> index 0000000..8a3762d
> >> --- /dev/null
> >> +++ b/mount.h
> >> @@ -0,0 +1,8 @@
> >> +#ifndef OVL_MOUNT_H
> >> +#define OVL_MOUNT_H
> >> +
> >> +int ovl_resolve_lowerdirs(char *loweropt, char ***lowerdir,
> >> +                          unsigned int *lowernum);
> >> +int ovl_check_mount(bool *mounted);
> >> +
> >> +#endif /* OVL_MOUNT_H */
> >> diff --git a/test/README.md b/test/README.md
> >> new file mode 100644
> >> index 0000000..71dbad8
> >> --- /dev/null
> >> +++ b/test/README.md
> >> @@ -0,0 +1,12 @@
> >> +fsck.overlay test
> >> +=================
> >> +
> >> +This test cases simulate different inconsistency of overlay filesystem
> >> +underlying directories, test fsck.overlay can repair them correctly or not.
> >> +
> >> +USAGE
> >> +=====
> >> +
> >> +1. Check "local.config", modify config value to appropriate one.
> >> +2. ./auto_test.sh
> >> +3. After testing, the result file and log file created in the result directory.
> > 
> > This really sounds like a mini fork of xfstests.
> > I'd prefer if those tests went into xfstests and used fsck.overlay either as an
> > external program (like the rest of fsck.*) or as in-house test helper.
> > 
> >> diff --git a/test/auto_test.sh b/test/auto_test.sh
> >> new file mode 100755
> >> index 0000000..8248e24
> >> --- /dev/null
> >> +++ b/test/auto_test.sh
> >> @@ -0,0 +1,46 @@
> >> +#!/bin/bash
> >> +
> >> +. ./src/prepare.sh
> >> +
> >> +echo "***************"
> >> +
> >> +# 1. Test whiteout
> >> +echo -e "1.Test Whiteout\n"
> >> +$SOURCE_DIR/whiteout_test.sh >> $LOG_FILE 2>&1
> >> +if [ $? -ne 0 ]; then
> >> +       echo -e "Result:\033[31m Fail\033[0m\n"
> >> +       RESULT="Fail"
> >> +else
> >> +       echo -e "Result:\033[32m Pass\033[0m\n"
> >> +       RESULT="Pass"
> >> +fi
> >> +
> >> +echo -e "Test whiteout: $RESULT" >> $RESOULT_FILE
> >> +
> >> +# 2. Test opaque dir
> >> +echo -e "2.Test opaque directory\n"
> >> +$SOURCE_DIR/opaque_test.sh >> $LOG_FILE 2>&1
> >> +if [ $? -ne 0 ]; then
> >> +       echo -e "Result:\033[31m Fail\033[0m\n"
> >> +       RESULT="Fail"
> >> +else
> >> +       echo -e "Result:\033[32m Pass\033[0m\n"
> >> +       RESULT="Pass"
> >> +fi
> >> +
> >> +echo -e "Test opaque directory: $RESULT" >> $RESOULT_FILE
> >> +
> >> +# 3. Test redirect dir
> >> +echo -e "3.Test redirect direcotry\n"
> >> +$SOURCE_DIR/redirect_test.sh >> $LOG_FILE 2>&1
> >> +if [ $? -ne 0 ]; then
> >> +       echo -e "Result:\033[31m Fail\033[0m\n"
> >> +       RESULT="Fail"
> >> +else
> >> +       echo -e "Result:\033[32m Pass\033[0m\n"
> >> +       RESULT="Pass"
> >> +fi
> >> +
> >> +echo -e "Test redirect direcotry: $RESULT" >> $RESOULT_FILE
> >> +
> >> +echo "***************"
> >> diff --git a/test/clean.sh b/test/clean.sh
> >> new file mode 100755
> >> index 0000000..80f360b
> >> --- /dev/null
> >> +++ b/test/clean.sh
> >> @@ -0,0 +1,6 @@
> >> +#!/bin/bash
> >> +
> >> +. ./src/prepare.sh
> >> +
> >> +rm -rf $RESULT_DIR
> >> +rm -rf $TEST_DIR
> >> diff --git a/test/local.config b/test/local.config
> >> new file mode 100644
> >> index 0000000..990395f
> >> --- /dev/null
> >> +++ b/test/local.config
> >> @@ -0,0 +1,16 @@
> >> +# Config
> >> +export OVL_FSCK=fsck.overlay
> >> +export TEST_DIR=/mnt/test
> >> +
> >> +# Base environment
> >> +export PWD=`pwd`
> >> +export RESULT_DIR=$PWD/result
> >> +export SOURCE_DIR=$PWD/src
> >> +
> >> +# Overlayfs config
> >> +export LOWER_DIR="lower"
> >> +export UPPER_DIR="upper"
> >> +export WORK_DIR="worker"
> > 
> > worker? this is a strange name for work dir location..
> > it does not have the same meaning in English as upp-er and low-er.
> > 
> sorry, clerical error, will fix it.
> 
> >> +export MERGE_DIR="merge"
> >> +export LOWER_DIR1="lower1"
> >> +export WORK_DIR1="worker1"
> >> diff --git a/test/src/opaque_test.sh b/test/src/opaque_test.sh
> >> new file mode 100755
> >> index 0000000..7cb5030
> >> --- /dev/null
> >> +++ b/test/src/opaque_test.sh
> >> @@ -0,0 +1,144 @@
> >> +#!/bin/bash
> >> +
> >> +. ./local.config
> >> +
> >> +__clean()
> >> +{
> >> +       rm -rf $TEST_DIR/*
> >> +       cd $PWD
> >> +}
> >> +
> >> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> >> +OVL_OPAQUE_XATTR_VAL="y"
> >> +RET=0
> >> +
> >> +cd $TEST_DIR
> >> +rm -rf *
> >> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> >> +
> >> +# 1.Test lower invalid opaque directory
> >> +echo "***** 1.Test lower invalid opaque directory *****"
> >> +mkdir $LOWER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: lower's invalid opaque xattr not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 2.Test upper invalid opaque directory
> >> +echo "***** 2.Test upper invalid opaque directory *****"
> >> +mkdir -p $UPPER_DIR/testdir0/testdir1
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir0/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir0/testdir1
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: upper's invalid opaque xattr not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +
> >> +# 3.Test upper opaque direcotry in merged directory
> >> +echo "***** 3.Test upper opaque direcotry in merged directory *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +
> >> +# 4.Test upper opaque direcotry cover lower file
> >> +echo "***** 4.Test upper opaque direcotry cover lower file *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +mkdir $LOWER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 5.Test upper opaque direcotry cover lower directory
> >> +echo "***** 5.Test upper opaque direcotry cover lower directory *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +touch $LOWER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 6.Test lower invalid opaque directory in middle layer
> >> +echo "***** 6.Test lower invalid opaque directory in middle layer *****"
> >> +mkdir $LOWER_DIR/testdir0/testdir1
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir0/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir0/testdir1
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: middle's opaque xattr not removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 7.Test lower invalid opaque directory in bottom layer
> >> +echo "***** 7.Test lower invalid opaque directory in bottom layer *****"
> >> +mkdir $LOWER_DIR1/testdir0/testdir1
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR1/testdir0/
> >> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR1/testdir0/testdir1
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: middle's opaque xattr not removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $LOWER_DIR1/*
> >> +
> >> +# 8.Test middle opaque direcotry cover bottom directory
> >> +echo "***** 8.Test middle opaque direcotry cover bottom directory *****"
> >> +mkdir $LOWER_DIR1/testdir
> >> +mkdir $LOWER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: middle's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $LOWER_DIR1/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 9.Test double opaque direcotry in middle and upper layer
> >> +echo "***** 9.Test double opaque direcotry in middle and upper layer *****"
> >> +mkdir $LOWER_DIR1/testdir
> >> +mkdir $LOWER_DIR/testdir
> >> +mkdir $UPPER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $LOWER_DIR/testdir
> >> +setfattr -n $OVL_OPAQUE_XATTR -v $OVL_OPAQUE_XATTR_VAL $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_OPAQUE_XATTR $LOWER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: middle's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's opaque xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $LOWER_DIR1/*
> >> +rm -rf $LOWER_DIR/*
> >> +rm -rf $UPPER_DIR/*
> >> +
> >> +__clean
> >> +exit $RET
> >> diff --git a/test/src/prepare.sh b/test/src/prepare.sh
> >> new file mode 100755
> >> index 0000000..138539a
> >> --- /dev/null
> >> +++ b/test/src/prepare.sh
> >> @@ -0,0 +1,15 @@
> >> +#!/bin/bash
> >> +
> >> +. ./local.config
> >> +
> >> +NOW=`date +"%Y-%m-%d-%H-%M-%S"`
> >> +LOG_FILE=$RESULT_DIR/LOG_${NOW}.log
> >> +RESOULT_FILE=$RESULT_DIR/RESULT_${NOW}.out
> >> +
> >> +# creat test base dir
> >> +if [ ! -d "$TEST_DIR" ]; then
> >> +       mkdir -p $TEST_DIR
> >> +fi
> >> +
> >> +# creat result dir
> >> +mkdir -p $RESULT_DIR
> >> diff --git a/test/src/redirect_test.sh b/test/src/redirect_test.sh
> >> new file mode 100755
> >> index 0000000..be2ce80
> >> --- /dev/null
> >> +++ b/test/src/redirect_test.sh
> >> @@ -0,0 +1,163 @@
> >> +#!/bin/bash
> >> +
> >> +. ./local.config
> >> +
> >> +__clean()
> >> +{
> >> +       rm -rf $TEST_DIR/*
> >> +       cd $PWD
> >> +}
> >> +
> >> +OVL_REDIRECT_XATTR="trusted.overlay.redirect"
> >> +OVL_OPAQUE_XATTR="trusted.overlay.opaque"
> >> +RET=0
> >> +
> >> +cd $TEST_DIR
> >> +rm -rf *
> >> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> >> +
> >> +# 1.Test no exist redirect origin
> >> +echo "***** 1.Test no exist redirect origin *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "xxx" $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +
> >> +# 2.Test redirect origin is file
> >> +echo "***** 2.Test redirect origin is file *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +touch $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 3.Test valid redirect xattr
> >> +echo "***** 3.Test valid redirect xattr *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +mknod $UPPER_DIR/origin c 0 0
> >> +mkdir $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 4.Test valid redirect xattr start from root
> >> +echo "***** 4.Test valid redirect xattr start from root *****"
> >> +mkdir -p $UPPER_DIR/testdir0/testdir1
> >> +mknod $UPPER_DIR/origin c 0 0
> >> +mkdir $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 5.Test missing whiteout in redirect parent directory
> >> +echo "***** 5.Test missing whiteout in redirect parent directory *****"
> >> +mkdir $UPPER_DIR/testdir
> >> +mkdir $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +if [ ! -e "$UPPER_DIR/origin" ];then
> >> +       echo "ERROR: upper's missing whiteout not create"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $UPPER_DIR/*
> >> +
> >> +# 6.Test missing opaque in redirect parent directory
> >> +echo "***** 6.Test missing opaque in redirect parent directory *****"
> >> +mkdir -p $UPPER_DIR/testdir0/testdir1
> >> +mkdir $UPPER_DIR/origin
> >> +mkdir $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "/origin" $UPPER_DIR/testdir0/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0/testdir1
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +getfattr -n $OVL_OPAQUE_XATTR $UPPER_DIR/origin
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's missing opaque not set"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 7.Test duplicate redirect directory in one layer
> >> +echo "***** 7.Test duplicate redirect directory in one layer *****"
> >> +mkdir $UPPER_DIR/testdir0
> >> +mkdir $UPPER_DIR/testdir1
> >> +mknod $UPPER_DIR/origin c 0 0
> >> +mkdir $LOWER_DIR/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +if [[ $? -eq 0 ]];then
> >> +       echo "ERROR: fsck check incorrect pass"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +
> >> +# 8.Test duplicate redirect directory in different layer
> >> +echo "***** 8.Test duplicate redirect directory in different layer *****"
> >> +mkdir $UPPER_DIR/testdir0
> >> +mkdir $LOWER_DIR/testdir1
> >> +mkdir $LOWER_DIR1/origin
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $UPPER_DIR/testdir0
> >> +setfattr -n $OVL_REDIRECT_XATTR -v "origin" $LOWER_DIR/testdir1
> >> +$OVL_FSCK -a -l $LOWER_DIR:$LOWER_DIR1 -u $UPPER_DIR -w $WORK_DIR
> >> +getfattr -n $OVL_REDIRECT_XATTR $UPPER_DIR/testdir0
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: upper's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +getfattr -n $OVL_REDIRECT_XATTR $LOWER_DIR/testdir1
> >> +if [[ $? -ne 0 ]];then
> >> +       echo "ERROR: lower's redirect xattr incorrect removed"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +
> >> +if [ ! -e "$LOWER_DIR/origin" ];then
> >> +       echo "ERROR: upper's missing whiteout not create"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +if [ ! -e "$LOWER_DIR/origin" ];then
> >> +       echo "ERROR: lower's missing whiteout not create"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +
> >> +rm -rf $UPPER_DIR/*
> >> +rm -rf $LOWER_DIR/*
> >> +rm -rf $LOWER_DIR1/*
> >> +
> >> +__clean
> >> +
> >> +exit $RET
> >> diff --git a/test/src/whiteout_test.sh b/test/src/whiteout_test.sh
> >> new file mode 100755
> >> index 0000000..c0e1555
> >> --- /dev/null
> >> +++ b/test/src/whiteout_test.sh
> >> @@ -0,0 +1,63 @@
> >> +#!/bin/bash
> >> +
> >> +. ./local.config
> >> +
> >> +__clean()
> >> +{
> >> +       rm -rf $TEST_DIR/*
> >> +       cd $PWD
> >> +}
> >> +
> >> +RET=0
> >> +
> >> +cd $TEST_DIR
> >> +rm -rf *
> >> +mkdir $LOWER_DIR $LOWER_DIR1 $UPPER_DIR $WORK_DIR
> >> +
> >> +# 1.Test lower orphan whiteout
> >> +echo "***** 1.Test lower orphan whiteout *****"
> >> +mknod $LOWER_DIR/foo c 0 0
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +if [ -e "$LOWER_DIR/foo" ];then
> >> +       echo "lower's whiteout not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -f $LOWER_DIR/*
> >> +
> >> +# 2.Test upper orphan whiteout
> >> +echo "***** 2.Test upper orphan whiteout *****"
> >> +mknod $UPPER_DIR/foo c 0 0
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +if [ -e "$UPPER_DIR/foo" ];then
> >> +       echo "upper's whiteout not remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -f $UPPER_DIR/*
> >> +
> >> +# 3.Test upper inuse whiteout
> >> +echo "***** 3.Test upper inuse whiteout *****"
> >> +touch $LOWER_DIR/foo
> >> +mknod $UPPER_DIR/foo c 0 0
> >> +$OVL_FSCK -a -l $LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +if [ ! -e "$UPPER_DIR/foo" ];then
> >> +       echo "upper's whiteout incorrect remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -f $UPPER_DIR/*
> >> +rm -f $LOWER_DIR/*
> >> +
> >> +# 4.Test lower inuse whiteout
> >> +echo "***** 4.Test lower inuse whiteout *****"
> >> +touch $LOWER_DIR/foo
> >> +mknod $LOWER_DIR1/foo c 0 0
> >> +$OVL_FSCK -a -l $LOWER_DIR1:$LOWER_DIR -u $UPPER_DIR -w $WORK_DIR
> >> +if [ ! -e "$LOWER_DIR1/foo" ];then
> >> +       echo "lower's whiteout incorrect remove"
> >> +       RET=$(($RET+1))
> >> +fi
> >> +rm -f $LOWER_DIR1/*
> >> +rm -f $LOWER_DIR/*
> >> +
> >> +__clean
> >> +
> >> +exit $RET
> >> --
> >> 2.9.5
> >>
> > 
> > .
> > 
> 
> --
> To unsubscribe from this list: send the line "unsubscribe fstests" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-20  7:29     ` Amir Goldstein
@ 2017-11-20  9:00       ` zhangyi (F)
  0 siblings, 0 replies; 19+ messages in thread
From: zhangyi (F) @ 2017-11-20  9:00 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: overlayfs, Miklos Szeredi, Miao Xie, fstests, Eryu Guan

On 2017/11/20 15:29, Amir Goldstein Wrote:
> On Mon, Nov 20, 2017 at 8:56 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>> On 2017/11/18 1:13, Amir Goldstein Wrote:
> [...]
>>
>>>>
>>>> This patch can am to an empty git repository. I have already do basic
>>>> test list in 'test' directory. Please do a review give some suggestions.
>>>> Thanks a lot.
>>>
>>> Please consider, instead of writing overlay tests in new repo,
>>> to write the tests for xfstests and hook overlay.fsck to _check_test_fs/
>>> _check_scratch_fs.
>>> See for example very basic index sanity checks I implemented here:
>>> https://github.com/amir73il/xfstests/commit/dac4da479e3e85be8f7beec54f384c0a832f74bd
>>>
>>
>> I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
>> and put in the same place. Now I notice that all xfs test cases put together
>> in xfstests, so It's also a good choice, not sure which one is better. How
>> about Eryu's opinion ?
>>
> 
> Looks. It's not a terrible idea to have some amount of duplication with tests
> and it's fine you with to keep some independent test within the progs repositiry
> as a "self test". But you will benefit a lot more testers on more machines and
> configurations if you merge all the interesting overlay tests to "fstests" repo,
> which a lot more people are running.
> And yes, there are quite a lot of tests in xfstest to verify correctness of
> xfs_repair. Self testing the main fs sanity check program is a good idea.
> 
> 
>>> It may be useful, if Eryu agrees, to merge the "incubator" version of
>>> fsck.overlayfs
>>> into xfstests as a helper program until it starts getting packaged and
>>> distributed.
>>> Please consider this option.
>>>
>>
>> Can we applay for a repository in git.kernel.org like xfsprogs-dev and e2fsprogs ?
> 
> "Kernel.org accounts are not given away very often, usually you need
> to be making some
> reasonable amount of contributions to the Linux kernel and have a good
> reason for wanting
> / needing an account"
> 
> It doesn't hurt to ask, but I suspect an incubation period for
> fsck.overlay in another location
> before it gets enough traction might be a good idea.
> You may ask Miklos to maintain overlayfs-progs on kernel.org getting
> patches from you,
> but I guess this was not your intention.
> 
> [...]
> 
>>>> +Redirect directories
>>>> +--------------------
>>>> +
>>>> +An redirect directory is a directory with "trusted.overlay.redirect" xattr
>>>> +valued to the path of the original location from the root of the overlay. It
>>>> +is only used when renaming a directory and "redirect dir" feature is enabled.
>>>> +If an redirect directory is found, the following must be met:
>>>> +
>>>> +1) The directory store in redirect xattr should exist in one of lower layers.
>>>> +2) The origin directory should be redirected only once in one layer, which mean
>>>> +   there is only one redirect xattr point to this origin directory in the
>>>> +   specified layer.
>>>> +3) A whiteout or an opaque directory with the same name to origin should exist
>>>> +   in the same directory as the redirect directory.
>>> another redirected upper dir can also be covering the redirect lower target
>>>
>> Do you mean the following case: upper's dir_rd cover lower1's redirect dir_rd ?
>>
>> Upper:                    dir_rd(redurected,opaque)   dir2(whiteout)
>> Lower1:   dir(whiteout)   dir_rd(redurected)          dir2
>> Lower0:   dir
>>
> 
> That's not what I meant. What I meant is:
> 
> Upper: A (whiteout), B (redirect=A), C (redirect=B)
> Lower: A (dir),          B (dir)
> 
> The redirect target of upper C (B) is not a whiteout in upper, nor an
> opaque dir.
> It is a redirected dir (whose own target is covered by a whitetout A).
> The description in 3) doesn't cover that case. Didn't check how the program
> handles this case.
> 

Indeed, I missed this situation, will add.

Thanks,
Yi.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-20  7:42     ` Eryu Guan
@ 2017-11-21  2:30       ` Theodore Ts'o
  2017-11-21  6:06         ` zhangyi (F)
  0 siblings, 1 reply; 19+ messages in thread
From: Theodore Ts'o @ 2017-11-21  2:30 UTC (permalink / raw)
  To: Eryu Guan
  Cc: zhangyi (F),
	Amir Goldstein, overlayfs, Miklos Szeredi, Miao Xie, fstests

On Mon, Nov 20, 2017 at 03:42:52PM +0800, Eryu Guan wrote:
> > I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
> > and put in the same place. Now I notice that all xfs test cases put together
> > in xfstests, so It's also a good choice, not sure which one is better. How
> > about Eryu's opinion ?
> 
> IMHO, xfstests is a good place for fsck.overlay tests, but I don't think
> that's something conflicting with in-repo unit tests, some tests are
> just not suitable for xfstests. How about writing tests in xfstests by
> default and only keeping them in repository, if there's any, if the
> tests are not fitting in xfstests well?

Zhangyi,

Here's my opinion, for whatever it's worth.  Things which don't
require root and which run fairly quickly (ideally less than 10
seconds; preferably under 5), are better to do in the fsck git
repository.  That's because it's much faster to run "make ; make
check".

Tests are most effective when they are frequently run and used.  You
may be more willing to live an exciting life, but I'm not particularly
fond of running "make install" of a development branch of e2fsprogs
just to run tests.  So that means I have to build e2fsprogs packages,
rebuild the test appliance VM, and then run tests --- which takes
*easily* 10x more time.

The place where it makes a lot of sense to put fsck and resize tests
into xfstests is where it requires root, or if the tests have a long
run-time.  For similar reasons of not wanting to live an exciting life
when it comes to my development environment, I don't like running
"make check" as repository which can be updated by others as root.  So
if a file system needs to be mounted (for example, to set up a test),
or if you want to test on-line resize, then that's an argument for
doing it in xfstests.

For overlayfs, it might be that you need to do a lot of mount and
unmount operations as root in order to run your tests.  So maybe
that's a good argument to run it under xfstests.  Unless you can make
small, pre-prepared file system images that you can untar and then
point at your overlayfsck to check.

As the Perl developers would say, "there's more than one way to do things".  :-)

							- Ted

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-21  2:30       ` Theodore Ts'o
@ 2017-11-21  6:06         ` zhangyi (F)
  0 siblings, 0 replies; 19+ messages in thread
From: zhangyi (F) @ 2017-11-21  6:06 UTC (permalink / raw)
  To: Theodore Ts'o, Eryu Guan
  Cc: Amir Goldstein, overlayfs, Miklos Szeredi, Miao Xie, fstests

On 2017/11/21 10:30, Theodore Ts'o Wrote:
> On Mon, Nov 20, 2017 at 03:42:52PM +0800, Eryu Guan wrote:
>>> I found that e2fsprogs put tests in the e2fsprogs repository, so I write it
>>> and put in the same place. Now I notice that all xfs test cases put together
>>> in xfstests, so It's also a good choice, not sure which one is better. How
>>> about Eryu's opinion ?
>>
>> IMHO, xfstests is a good place for fsck.overlay tests, but I don't think
>> that's something conflicting with in-repo unit tests, some tests are
>> just not suitable for xfstests. How about writing tests in xfstests by
>> default and only keeping them in repository, if there's any, if the
>> tests are not fitting in xfstests well?
> 
> Zhangyi,
> 
> Here's my opinion, for whatever it's worth.  Things which don't
> require root and which run fairly quickly (ideally less than 10
> seconds; preferably under 5), are better to do in the fsck git
> repository.  That's because it's much faster to run "make ; make
> check".
> 
> Tests are most effective when they are frequently run and used.  You
> may be more willing to live an exciting life, but I'm not particularly
> fond of running "make install" of a development branch of e2fsprogs
> just to run tests.  So that means I have to build e2fsprogs packages,
> rebuild the test appliance VM, and then run tests --- which takes
> *easily* 10x more time.
> 
> The place where it makes a lot of sense to put fsck and resize tests
> into xfstests is where it requires root, or if the tests have a long
> run-time.  For similar reasons of not wanting to live an exciting life
> when it comes to my development environment, I don't like running
> "make check" as repository which can be updated by others as root.  So
> if a file system needs to be mounted (for example, to set up a test),
> or if you want to test on-line resize, then that's an argument for
> doing it in xfstests.
> 
> For overlayfs, it might be that you need to do a lot of mount and
> unmount operations as root in order to run your tests.  So maybe
> that's a good argument to run it under xfstests.  Unless you can make
> small, pre-prepared file system images that you can untar and then
> point at your overlayfsck to check.
> 
> As the Perl developers would say, "there's more than one way to do things".  :-)
> 
Hi Ted,

Thanks for your explain and advice, writing tests in xfstests feels better.
I will consider it and split tests from fsck source code to put them(maybe part of)
into xfstests.

Thanks,
Yi.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2017-11-17 17:13 ` Amir Goldstein
  2017-11-17 18:39   ` Darrick J. Wong
  2017-11-20  6:56   ` zhangyi (F)
@ 2018-03-07  9:25   ` yangerkun
  2018-03-07 11:14     ` Amir Goldstein
  2 siblings, 1 reply; 19+ messages in thread
From: yangerkun @ 2018-03-07  9:25 UTC (permalink / raw)
  To: Amir Goldstein, zhangyi (F); +Cc: overlayfs, Miklos Szeredi, Miao Xie



On 11/18/2017 1:13 AM, Amir Goldstein wrote:
> [adding fstests in CC with full patch inline to collect wisdom from
> other fs developers]
> 
> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>> Hi,
[...]
>>
>> Todo
>> ====
>>
>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>> online. Now, We cannot distinguish mounted directories if overlayfs was
>> mounted with relative path.
> 
> This should be handled by kernel.
> We now already grab an advisory exclusive I_OVL_INUSE lock on both
> upperdir and workdir.
> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
> Read the man page section on O_EXCL and block device. This is how
> e2fsck and friends get exclusive access w.r.t mount.
> 

In fsck.overlay, lower layer file/dir may be modified with there is not 
I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted 
with I_OVL_INUSE.

Maybe we need adopt another way? How about store pwd in ofs.config when 
we mount the overlay.

Thanks,
yangerkun.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-07  9:25   ` yangerkun
@ 2018-03-07 11:14     ` Amir Goldstein
  2018-03-07 11:20       ` Amir Goldstein
  2018-03-08  2:32       ` yangerkun
  0 siblings, 2 replies; 19+ messages in thread
From: Amir Goldstein @ 2018-03-07 11:14 UTC (permalink / raw)
  To: yangerkun; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie

On Wed, Mar 7, 2018 at 11:25 AM, yangerkun <yangerkun@huawei.com> wrote:
>
>
> On 11/18/2017 1:13 AM, Amir Goldstein wrote:
>>
>> [adding fstests in CC with full patch inline to collect wisdom from
>> other fs developers]
>>
>> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>>>
>>> Hi,
>
> [...]
>>>
>>>
>>> Todo
>>> ====
>>>
>>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>> online. Now, We cannot distinguish mounted directories if overlayfs was
>>> mounted with relative path.
>>
>>
>> This should be handled by kernel.
>> We now already grab an advisory exclusive I_OVL_INUSE lock on both
>> upperdir and workdir.
>> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
>> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
>> Read the man page section on O_EXCL and block device. This is how
>> e2fsck and friends get exclusive access w.r.t mount.
>>
>
> In fsck.overlay, lower layer file/dir may be modified with there is not
> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
> with I_OVL_INUSE.
>

Can you list the possible modifications the fsck.overlay can do on lower layer?
What exactly are we trying to protect from?

> Maybe we need adopt another way? How about store pwd in ofs.config when we
> mount the overlay.
>

I don't understand what that means.

Instead of relying on the partial information available in /proc/<pid>/mountinfo
we can export more interesting information from kernel per overlayfs mount,
see for example the information available at /proc/fs/ext4/<blockdev>/

For example, we can expose "layers" information, including the file handle of
all layers root dir, so fsck.overlay can figure out if any upper/lower
dir are in-use
without needing to resolve paths.

There is probably other interesting information from ofs.config that can
be exposed this way.

Instead of <blockdev> (in the ext4 case) the key would have to be the overlayfs
anonymous dev, which can be read by stat(2) on any overlay directory.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-07 11:14     ` Amir Goldstein
@ 2018-03-07 11:20       ` Amir Goldstein
  2018-03-08  2:38         ` yangerkun
  2018-03-08  2:32       ` yangerkun
  1 sibling, 1 reply; 19+ messages in thread
From: Amir Goldstein @ 2018-03-07 11:20 UTC (permalink / raw)
  To: yangerkun; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie

On Wed, Mar 7, 2018 at 1:14 PM, Amir Goldstein <amir73il@gmail.com> wrote:
> On Wed, Mar 7, 2018 at 11:25 AM, yangerkun <yangerkun@huawei.com> wrote:
>>
>>
>> On 11/18/2017 1:13 AM, Amir Goldstein wrote:
>>>
>>> [adding fstests in CC with full patch inline to collect wisdom from
>>> other fs developers]
>>>
>>> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>>>>
>>>> Hi,
>>
>> [...]
>>>>
>>>>
>>>> Todo
>>>> ====
>>>>
>>>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>>> online. Now, We cannot distinguish mounted directories if overlayfs was
>>>> mounted with relative path.
>>>
>>>
>>> This should be handled by kernel.
>>> We now already grab an advisory exclusive I_OVL_INUSE lock on both
>>> upperdir and workdir.
>>> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
>>> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
>>> Read the man page section on O_EXCL and block device. This is how
>>> e2fsck and friends get exclusive access w.r.t mount.
>>>
>>
>> In fsck.overlay, lower layer file/dir may be modified with there is not
>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>> with I_OVL_INUSE.
>>
>
> Can you list the possible modifications the fsck.overlay can do on lower layer?
> What exactly are we trying to protect from?
>

Also, if we follow my suggestion above for upperdir/workdir
fsck.overlay may still
try to aquire I_OVL_INUSE on lowerdir with O_EXCL|O_DIRECTORY and kernel
can test I_OVL_INUSE flag on lowerdir without trying to set it on mount.

>> Maybe we need adopt another way? How about store pwd in ofs.config when we
>> mount the overlay.
>>
>
> I don't understand what that means.
>
> Instead of relying on the partial information available in /proc/<pid>/mountinfo
> we can export more interesting information from kernel per overlayfs mount,
> see for example the information available at /proc/fs/ext4/<blockdev>/
>
> For example, we can expose "layers" information, including the file handle of
> all layers root dir, so fsck.overlay can figure out if any upper/lower
> dir are in-use
> without needing to resolve paths.
>
> There is probably other interesting information from ofs.config that can
> be exposed this way.
>
> Instead of <blockdev> (in the ext4 case) the key would have to be the overlayfs
> anonymous dev, which can be read by stat(2) on any overlay directory.
>
> Thanks,
> Amir.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-07 11:14     ` Amir Goldstein
  2018-03-07 11:20       ` Amir Goldstein
@ 2018-03-08  2:32       ` yangerkun
  2018-03-08  7:37         ` Amir Goldstein
  1 sibling, 1 reply; 19+ messages in thread
From: yangerkun @ 2018-03-08  2:32 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie



On 3/7/2018 7:14 PM, Amir Goldstein wrote:
> On Wed, Mar 7, 2018 at 11:25 AM, yangerkun <yangerkun@huawei.com> wrote:
>>
>>
>> On 11/18/2017 1:13 AM, Amir Goldstein wrote:
>>>
>>> [adding fstests in CC with full patch inline to collect wisdom from
>>> other fs developers]
>>>
>>> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>>>>
>>>> Hi,
>>
>> [...]
>>>>
>>>>
>>>> Todo
>>>> ====
>>>>
>>>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>>> online. Now, We cannot distinguish mounted directories if overlayfs was
>>>> mounted with relative path.
>>>
>>>
>>> This should be handled by kernel.
>>> We now already grab an advisory exclusive I_OVL_INUSE lock on both
>>> upperdir and workdir.
>>> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
>>> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
>>> Read the man page section on O_EXCL and block device. This is how
>>> e2fsck and friends get exclusive access w.r.t mount.
>>>
>>
>> In fsck.overlay, lower layer file/dir may be modified with there is not
>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>> with I_OVL_INUSE.
>>
> 
> Can you list the possible modifications the fsck.overlay can do on lower layer?
> What exactly are we trying to protect from?

A orphan whiteout and invalid redirect in lower layer will be removed 
automatic or ask user.
Since we cannot change anything for lower layer when overlay has been 
mounted, so we need run fsck.overlay first before mount to ensure 
consistency.
> 
>> Maybe we need adopt another way? How about store pwd in ofs.config when we
>> mount the overlay.
>>
> 
> I don't understand what that means.

We can use the parameters passed in by fsck.overlay and current working 
directory to construct absolute path for lower layers and upper layer; 
but when we want to get the information about mounted overlayfs through 
mountinfo, we cannot construct the absolute path of lower layer and 
upper layer without the pwd when we mount an overlayfs.
> 
> Instead of relying on the partial information available in /proc/<pid>/mountinfo
> we can export more interesting information from kernel per overlayfs mount,
> see for example the information available at /proc/fs/ext4/<blockdev>/
> 
> For example, we can expose "layers" information, including the file handle of
> all layers root dir, so fsck.overlay can figure out if any upper/lower
> dir are in-use
> without needing to resolve paths.

> 
> There is probably other interesting information from ofs.config that can
> be exposed this way.
> 
> Instead of <blockdev> (in the ext4 case) the key would have to be the overlayfs
> anonymous dev, which can be read by stat(2) on any overlay directory.

Thanks for your advise, i will think how to realize.
> 
> Thanks,
> Amir.
> 
> .
> 
Thanks,
yangerkun.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-07 11:20       ` Amir Goldstein
@ 2018-03-08  2:38         ` yangerkun
  2018-03-08  7:51           ` Amir Goldstein
  0 siblings, 1 reply; 19+ messages in thread
From: yangerkun @ 2018-03-08  2:38 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie



On 3/7/2018 7:20 PM, Amir Goldstein wrote:
> On Wed, Mar 7, 2018 at 1:14 PM, Amir Goldstein <amir73il@gmail.com> wrote:
>> On Wed, Mar 7, 2018 at 11:25 AM, yangerkun <yangerkun@huawei.com> wrote:
>>>
>>>
>>> On 11/18/2017 1:13 AM, Amir Goldstein wrote:
>>>>
>>>> [adding fstests in CC with full patch inline to collect wisdom from
>>>> other fs developers]
>>>>
>>>> On Fri, Nov 17, 2017 at 7:49 AM, zhangyi (F) <yi.zhang@huawei.com> wrote:
>>>>>
>>>>> Hi,
>>>
>>> [...]
>>>>>
>>>>>
>>>>> Todo
>>>>> ====
>>>>>
>>>>> 1. Overlay filesystem mounted check. Prevent fscking when overlay is
>>>>> online. Now, We cannot distinguish mounted directories if overlayfs was
>>>>> mounted with relative path.
>>>>
>>>>
>>>> This should be handled by kernel.
>>>> We now already grab an advisory exclusive I_OVL_INUSE lock on both
>>>> upperdir and workdir.
>>>> fsck.overlay can try to open upperdir/workdir with O_EXCL|O_DIRECTORY
>>>> and kernel should fail this open if overlayfs is holding the  I_OVL_INUSE.
>>>> Read the man page section on O_EXCL and block device. This is how
>>>> e2fsck and friends get exclusive access w.r.t mount.
>>>>
>>>
>>> In fsck.overlay, lower layer file/dir may be modified with there is not
>>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>>> with I_OVL_INUSE.
>>>
>>
>> Can you list the possible modifications the fsck.overlay can do on lower layer?
>> What exactly are we trying to protect from?
>>
> 
> Also, if we follow my suggestion above for upperdir/workdir
> fsck.overlay may still
> try to aquire I_OVL_INUSE on lowerdir with O_EXCL|O_DIRECTORY and kernel
> can test I_OVL_INUSE flag on lowerdir without trying to set it on mount.

It's good when we want to mount overlayfs with fsck.overlay is running; 
But when there is a mounted overlayfs before fsck.overlay, we cannot use 
this way to check if the lower layer has already been used.
> 
>>> Maybe we need adopt another way? How about store pwd in ofs.config when we
>>> mount the overlay.
>>>
>>
>> I don't understand what that means.
>>
>> Instead of relying on the partial information available in /proc/<pid>/mountinfo
>> we can export more interesting information from kernel per overlayfs mount,
>> see for example the information available at /proc/fs/ext4/<blockdev>/
>>
>> For example, we can expose "layers" information, including the file handle of
>> all layers root dir, so fsck.overlay can figure out if any upper/lower
>> dir are in-use
>> without needing to resolve paths.
>>
>> There is probably other interesting information from ofs.config that can
>> be exposed this way.
>>
>> Instead of <blockdev> (in the ext4 case) the key would have to be the overlayfs
>> anonymous dev, which can be read by stat(2) on any overlay directory.
>>
>> Thanks,
>> Amir.
> 
> .
> 
Thanks,
yangerkun.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-08  2:32       ` yangerkun
@ 2018-03-08  7:37         ` Amir Goldstein
  0 siblings, 0 replies; 19+ messages in thread
From: Amir Goldstein @ 2018-03-08  7:37 UTC (permalink / raw)
  To: yangerkun; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie

On Thu, Mar 8, 2018 at 4:32 AM, yangerkun <yangerkun@huawei.com> wrote:
[...]
>>>
>>> In fsck.overlay, lower layer file/dir may be modified with there is not
>>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>>> with I_OVL_INUSE.
>>>
>>
>> Can you list the possible modifications the fsck.overlay can do on lower
>> layer?
>> What exactly are we trying to protect from?
>
>
> A orphan whiteout and invalid redirect in lower layer will be removed
> automatic or ask user.
> Since we cannot change anything for lower layer when overlay has been
> mounted, so we need run fsck.overlay first before mount to ensure
> consistency.

FWIW, I don't think that removing orphan whiteouts and invalid redirects
from lower layer can cause any damage when overlayfs is mounted
(famous last words), but I agree that in principle we should avoid fsck
on layers while overlayfs is mounted.

How about this - by default, lower layers are considered read-only and
fsck -a won't modify them, only report errors. fsck -y can be used to force
fixing lower layers. Maybe another flag could be used to indicate that
lower layers could be modified.

>>
>>
>>> Maybe we need adopt another way? How about store pwd in ofs.config when
>>> we
>>> mount the overlay.
>>>
>>
>> I don't understand what that means.
>
>
> We can use the parameters passed in by fsck.overlay and current working
> directory to construct absolute path for lower layers and upper layer; but
> when we want to get the information about mounted overlayfs through
> mountinfo, we cannot construct the absolute path of lower layer and upper
> layer without the pwd when we mount an overlayfs.

I see. I think that dealing with paths is wrong. pwd is not enough,
you also need
to know the mount ns root path. It's just not good to use path as an identifier.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-08  2:38         ` yangerkun
@ 2018-03-08  7:51           ` Amir Goldstein
  2018-03-20 16:44             ` Miklos Szeredi
  0 siblings, 1 reply; 19+ messages in thread
From: Amir Goldstein @ 2018-03-08  7:51 UTC (permalink / raw)
  To: yangerkun; +Cc: zhangyi (F), overlayfs, Miklos Szeredi, Miao Xie, Jeff Layton

On Thu, Mar 8, 2018 at 4:38 AM, yangerkun <yangerkun@huawei.com> wrote:
>
[...]
>>>>
>>>> In fsck.overlay, lower layer file/dir may be modified with there is not
>>>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>>>> with I_OVL_INUSE.
>>>>
>>>
[...]
>> Also, if we follow my suggestion above for upperdir/workdir
>> fsck.overlay may still
>> try to aquire I_OVL_INUSE on lowerdir with O_EXCL|O_DIRECTORY and kernel
>> can test I_OVL_INUSE flag on lowerdir without trying to set it on mount.
>
>
> It's good when we want to mount overlayfs with fsck.overlay is running; But
> when there is a mounted overlayfs before fsck.overlay, we cannot use this
> way to check if the lower layer has already been used.

Perhaps we should acquire a shared POSIX lock from kernel on lower/upper/work
dirs and fsck.overlay should acquire an exclusive POSIX lock.

I'm not sure how acquiring a POSIX lock from kernel should work and which
task should be the owner of the lock, but generally overlayfs could either have
a single owner task in the kernel for all super blocks or one owner
per sb, in which
case, we could acquire an exclusive lock on work/upper dirs instead of using
the custom  I_OVL_INUSE lock.

Just a thought - not sure if this makes sense. CC'ing Jeff for reality check.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-08  7:51           ` Amir Goldstein
@ 2018-03-20 16:44             ` Miklos Szeredi
  2018-03-20 16:55               ` Jeff Layton
  0 siblings, 1 reply; 19+ messages in thread
From: Miklos Szeredi @ 2018-03-20 16:44 UTC (permalink / raw)
  To: Amir Goldstein; +Cc: yangerkun, zhangyi (F), overlayfs, Miao Xie, Jeff Layton

On Thu, Mar 8, 2018 at 8:51 AM, Amir Goldstein <amir73il@gmail.com> wrote:
> On Thu, Mar 8, 2018 at 4:38 AM, yangerkun <yangerkun@huawei.com> wrote:
>>
> [...]
>>>>>
>>>>> In fsck.overlay, lower layer file/dir may be modified with there is not
>>>>> I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
>>>>> with I_OVL_INUSE.
>>>>>
>>>>
> [...]
>>> Also, if we follow my suggestion above for upperdir/workdir
>>> fsck.overlay may still
>>> try to aquire I_OVL_INUSE on lowerdir with O_EXCL|O_DIRECTORY and kernel
>>> can test I_OVL_INUSE flag on lowerdir without trying to set it on mount.
>>
>>
>> It's good when we want to mount overlayfs with fsck.overlay is running; But
>> when there is a mounted overlayfs before fsck.overlay, we cannot use this
>> way to check if the lower layer has already been used.
>
> Perhaps we should acquire a shared POSIX lock from kernel on lower/upper/work
> dirs and fsck.overlay should acquire an exclusive POSIX lock.
>
> I'm not sure how acquiring a POSIX lock from kernel should work and which
> task should be the owner of the lock, but generally overlayfs could either have
> a single owner task in the kernel for all super blocks or one owner
> per sb, in which
> case, we could acquire an exclusive lock on work/upper dirs instead of using
> the custom  I_OVL_INUSE lock.
>
> Just a thought - not sure if this makes sense. CC'ing Jeff for reality check.

Much better to use flock(2) locks which, unlike POSIX locks, have sane
semantics.   The owner is not a task but a file, so just need to keep
an file open referring to each layer's root in overlayfs.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [fsck.overlay RFC PATCH] overlay: add fsck utility
  2018-03-20 16:44             ` Miklos Szeredi
@ 2018-03-20 16:55               ` Jeff Layton
  0 siblings, 0 replies; 19+ messages in thread
From: Jeff Layton @ 2018-03-20 16:55 UTC (permalink / raw)
  To: Miklos Szeredi, Amir Goldstein
  Cc: yangerkun, zhangyi (F), overlayfs, Miao Xie

On Tue, 2018-03-20 at 17:44 +0100, Miklos Szeredi wrote:
> On Thu, Mar 8, 2018 at 8:51 AM, Amir Goldstein <amir73il@gmail.com> wrote:
> > On Thu, Mar 8, 2018 at 4:38 AM, yangerkun <yangerkun@huawei.com> wrote:
> > > 
> > 
> > [...]
> > > > > > 
> > > > > > In fsck.overlay, lower layer file/dir may be modified with there is not
> > > > > > I_OVL_INUSE in lower layer, but we cannot check does lower layer mounted
> > > > > > with I_OVL_INUSE.
> > > > > > 
> > 
> > [...]
> > > > Also, if we follow my suggestion above for upperdir/workdir
> > > > fsck.overlay may still
> > > > try to aquire I_OVL_INUSE on lowerdir with O_EXCL|O_DIRECTORY and kernel
> > > > can test I_OVL_INUSE flag on lowerdir without trying to set it on mount.
> > > 
> > > 
> > > It's good when we want to mount overlayfs with fsck.overlay is running; But
> > > when there is a mounted overlayfs before fsck.overlay, we cannot use this
> > > way to check if the lower layer has already been used.
> > 
> > Perhaps we should acquire a shared POSIX lock from kernel on lower/upper/work
> > dirs and fsck.overlay should acquire an exclusive POSIX lock.
> > 
> > I'm not sure how acquiring a POSIX lock from kernel should work and which
> > task should be the owner of the lock, but generally overlayfs could either have
> > a single owner task in the kernel for all super blocks or one owner
> > per sb, in which
> > case, we could acquire an exclusive lock on work/upper dirs instead of using
> > the custom  I_OVL_INUSE lock.
> > 
> > Just a thought - not sure if this makes sense. CC'ing Jeff for reality check.
> 
> Much better to use flock(2) locks which, unlike POSIX locks, have sane
> semantics.   The owner is not a task but a file, so just need to keep
> an file open referring to each layer's root in overlayfs.
> 

Oops, missed this thread before.

Yes, a flock lock should be fine as long as you're locking the whole
file (and I assume you would be here). If you need byte ranges, consider
OFD locks.
-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2018-03-20 16:55 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-17  5:49 [fsck.overlay RFC PATCH] overlay: add fsck utility zhangyi (F)
2017-11-17 17:13 ` Amir Goldstein
2017-11-17 18:39   ` Darrick J. Wong
2017-11-20  7:12     ` zhangyi (F)
2017-11-20  6:56   ` zhangyi (F)
2017-11-20  7:29     ` Amir Goldstein
2017-11-20  9:00       ` zhangyi (F)
2017-11-20  7:42     ` Eryu Guan
2017-11-21  2:30       ` Theodore Ts'o
2017-11-21  6:06         ` zhangyi (F)
2018-03-07  9:25   ` yangerkun
2018-03-07 11:14     ` Amir Goldstein
2018-03-07 11:20       ` Amir Goldstein
2018-03-08  2:38         ` yangerkun
2018-03-08  7:51           ` Amir Goldstein
2018-03-20 16:44             ` Miklos Szeredi
2018-03-20 16:55               ` Jeff Layton
2018-03-08  2:32       ` yangerkun
2018-03-08  7:37         ` Amir Goldstein

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.