Mksquashfs: add new -exit-on-error option

Mksquashfs recognises two kinds of error:

	- Fatal errors which cause it to abort, and
	- Non fatal errors which it considers can be safely ignored

These "non-fatal" errors are generally failure to read files or
directories, when this happens Mksquashfs skips or stores an empty
file and continues (flagging up what it has done to stderr).

From early feedback from users, this is generally considered a better
option than aborting.  Often-times users are aware that there are files
which cannot be read by Mksquashfs, and want Mksquashfs to automatically
ignore them without having to explicitly exclude them on the command line
(for instance it may be known that there are files owned by other users).
Or for instance archiving a filesystem which is known to be partially
corrupted, and it is expected it will generate I/O errors on one or more
files (which are obviously unknown beforehand and cannot be explicitly
excluded).  Additionally, often-times users do not know that there are
files which cannot be read, but would rather Mksquashfs flag these
errors and continue, rather than aborting what may be many hours of
compression.

But I have got feedback which shows in some use-cases this
behaviour is undesireable, and would rather Mksquashfs abort at
the failure to read any files.

So add a new option -exit-on-error which makes Mksquashfs abort on
these errors.

Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
diff --git a/squashfs-tools/error.h b/squashfs-tools/error.h
index 57b17cd..f227b15 100644
--- a/squashfs-tools/error.h
+++ b/squashfs-tools/error.h
@@ -2,7 +2,7 @@
  * Create a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2012, 2013
+ * Copyright (c) 2012, 2013, 2014
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -22,6 +22,8 @@
  * error.h
  */
 
+extern int exit_on_error;
+
 extern void prep_exit();
 extern void progressbar_error(char *fmt, ...);
 extern void progressbar_info(char *fmt, ...);
@@ -46,6 +48,23 @@
 			progressbar_error(s, ## args); \
 		} while(0)
 
+#define ERROR_START(s, args...) \
+		do { \
+			disable_progress_bar(); \
+			fprintf(stderr, s, ## args); \
+		} while(0)
+
+#define ERROR_EXIT(s, args...) \
+		do {\
+			if (exit_on_error) { \
+				fprintf(stderr, "\n"); \
+				EXIT_MKSQUASHFS(); \
+			} else { \
+				fprintf(stderr, s, ## args); \
+				enable_progress_bar(); \
+			} \
+		} while(0)
+
 #define EXIT_MKSQUASHFS() \
 		do {\
 			prep_exit();\
diff --git a/squashfs-tools/mksquashfs.c b/squashfs-tools/mksquashfs.c
index 956671f..493e668 100644
--- a/squashfs-tools/mksquashfs.c
+++ b/squashfs-tools/mksquashfs.c
@@ -96,6 +96,7 @@
 int old_exclude = TRUE;
 int use_regex = FALSE;
 int nopad = FALSE;
+int exit_on_error = FALSE;
 
 long long global_uid = -1, global_gid = -1;
 
@@ -1058,14 +1059,15 @@
 
 		byte = readlink(filename, buff, 65536);
 		if(byte == -1) {
-			ERROR("Failed to read symlink %s, creating empty "
-				"symlink\n", filename);
+			ERROR_START("Failed to read symlink %s", filename);
+			ERROR_EXIT(", creating empty symlink\n");
 			byte = 0;
 		}
 
 		if(byte == 65536) {
-			ERROR("Symlink %s is greater than 65536 bytes! "
-				"Creating empty symlink\n", filename);
+			ERROR_START("Symlink %s is greater than 65536 bytes!",
+				filename);
+			ERROR_EXIT("  Creating empty symlink\n");
 			byte = 0;
 		}
 
@@ -1085,14 +1087,15 @@
 
 		byte = readlink(filename, buff, 65536);
 		if(byte == -1) {
-			ERROR("Failed to read symlink %s, creating empty "
-				"symlink\n", filename);
+			ERROR_START("Failed to read symlink %s", filename);
+			ERROR_EXIT(", creating empty symlink\n");
 			byte = 0;
 		}
 
 		if(byte == 65536) {
-			ERROR("Symlink %s is greater than 65536 bytes! "
-				"Creating empty symlink\n", filename);
+			ERROR_START("Symlink %s is greater than 65536 bytes!",
+				filename);
+			ERROR_EXIT("  Creating empty symlink\n");
 			byte = 0;
 		}
 
@@ -2143,7 +2146,7 @@
 restat:
 	res = fstat(file, &buf2);
 	if(res == -1) {
-		ERROR("Cannot stat dir/file %s because %s, ignoring\n",
+		ERROR("Cannot stat dir/file %s because %s\n",
 			pathname_reader(dir_ent), strerror(errno));
 		goto read_err;
 	}
@@ -2814,8 +2817,8 @@
 			"attempting to re-read\n", pathname(dir_ent));
 		goto again;
 	} else if(status == 1) {
-		ERROR("Failed to read file %s, creating empty file\n",
-			pathname(dir_ent));
+		ERROR_START("Failed to read file %s", pathname(dir_ent));
+		ERROR_EXIT(", creating empty file\n");
 		write_file_empty(inode, dir_ent, duplicate_file);
 	}
 }
@@ -3161,8 +3164,9 @@
 		int pass = 1, res;
 
 		if(dir_name == NULL) {
-			ERROR("Bad source directory %s - skipping ...\n",
+			ERROR_START("Bad source directory %s",
 				source_path[index]);
+			ERROR_EXIT(" - skipping ...\n");
 			index ++;
 			continue;
 		}
@@ -3263,7 +3267,8 @@
 	struct dir_ent *dir_ent;
 
 	if(dir == NULL) {
-		ERROR("Could not open %s, skipping...\n", filename);
+		ERROR_START("Could not open %s", filename);
+		ERROR_EXIT(", skipping...\n");
 		return NULL;
 	}
 
@@ -3281,8 +3286,9 @@
 		}
 
 		if(lstat(filename, &buf) == -1) {
-			ERROR("Cannot stat dir/file %s because %s, ignoring\n",
+			ERROR_START("Cannot stat dir/file %s because %s",
 				filename, strerror(errno));
+			ERROR_EXIT(", ignoring\n");
 			free_dir_entry(dir_ent);
 			continue;
 		}
@@ -3294,8 +3300,9 @@
 					(buf.st_mode & S_IFMT) != S_IFBLK &&
 					(buf.st_mode & S_IFMT) != S_IFIFO &&
 					(buf.st_mode & S_IFMT) != S_IFSOCK) {
-			ERROR("File %s has unrecognised filetype %d, ignoring"
-				"\n", filename, buf.st_mode & S_IFMT);
+			ERROR_START("File %s has unrecognised filetype %d",
+				filename, buf.st_mode & S_IFMT);
+			ERROR_EXIT(", ignoring\n");
 			free_dir_entry(dir_ent);
 			continue;
 		}
@@ -3395,16 +3402,18 @@
 		if(pseudo_ent->dev->type == 'm') {
 			struct stat *buf;
 			if(dir_ent == NULL) {
-				ERROR("Pseudo modify file \"%s\" does not exist "
-					"in source filesystem.  Ignoring.\n",
+				ERROR_START("Pseudo modify file \"%s\" does "
+					"not exist in source filesystem.",
 					pseudo_ent->pathname);
+				ERROR_EXIT("  Ignoring.\n");
 				continue;
 			}
 			if(dir_ent->inode->root_entry) {
-				ERROR("Pseudo modify file \"%s\" is a pre-existing"
-					" file in the filesystem being appended"
-					"  to.  It cannot be modified. "
-					"Ignoring.\n", pseudo_ent->pathname);
+				ERROR_START("Pseudo modify file \"%s\" is a "
+					"pre-existing file in the filesystem "
+					"being appended to.  It cannot be "\
+					"modified.", pseudo_ent->pathname);
+				ERROR_EXIT("  Ignoring.\n");
 				continue;
 			}
 			buf = &dir_ent->inode->buf;
@@ -3416,17 +3425,20 @@
 		}
 
 		if(dir_ent) {
-			if(dir_ent->inode->root_entry)
-				ERROR("Pseudo file \"%s\" is a pre-existing"
-					" file in the filesystem being appended"
-					"  to.  Ignoring.\n",
+			if(dir_ent->inode->root_entry) {
+				ERROR_START("Pseudo file \"%s\" is a "
+					"pre-existing file in the filesystem "
+					"being appended to.",
 					pseudo_ent->pathname);
-			else
-				ERROR("Pseudo file \"%s\" exists in source "
-					"filesystem \"%s\".\nIgnoring, "
-					"exclude it (-e/-ef) to override.\n",
+				ERROR_EXIT("  Ignoring.\n");
+			} else {
+				ERROR_START("Pseudo file \"%s\" exists in "
+					"source filesystem \"%s\".",
 					pseudo_ent->pathname,
 					pathname(dir_ent));
+				ERROR_EXIT("\nIgnoring, exclude it (-e/-ef) to "
+					"override.\n");
+			}
 			continue;
 		}
 
@@ -3447,9 +3459,9 @@
 			struct dir_info *sub_dir = scan1_opendir("", subpath,
 						dir->depth + 1);
 			if(sub_dir == NULL) {
-				ERROR("Could not create pseudo directory \"%s\""
-					", skipping...\n",
-					pseudo_ent->pathname);
+				ERROR_START("Could not create pseudo directory "
+					"\"%s\"", pseudo_ent->pathname);
+				ERROR_EXIT(", skipping...\n");
 				free(subpath);
 				pseudo_ino --;
 				continue;
@@ -3463,8 +3475,9 @@
 			struct stat buf2;
 			int res = stat(pseudo_ent->dev->filename, &buf2);
 			if(res == -1) {
-				ERROR("Stat on pseudo file \"%s\" failed, "
-					"skipping...\n", pseudo_ent->pathname);
+				ERROR_START("Stat on pseudo file \"%s\" failed"
+					pseudo_ent->pathname);
+				ERROR_EXIT(", skipping...\n");
 				pseudo_ino --;
 				continue;
 			}
@@ -3902,8 +3915,9 @@
 	if(path[0] == '/' || strncmp(path, "./", 2) == 0 ||
 			strncmp(path, "../", 3) == 0) {
 		if(lstat(path, &buf) == -1) {
-			ERROR("Cannot stat exclude dir/file %s because %s, "
-				"ignoring\n", path, strerror(errno));
+			ERROR_START("Cannot stat exclude dir/file %s because "
+				"%s", path, strerror(errno));
+			ERROR_EXIT(", ignoring\n");
 			return TRUE;
 		}
 		ADD_ENTRY(buf);
@@ -3915,10 +3929,11 @@
 		if(res == -1)
 			BAD_ERROR("asprintf failed in old_add_exclude\n");
 		if(lstat(filename, &buf) == -1) {
-			if(!(errno == ENOENT || errno == ENOTDIR))
-				ERROR("Cannot stat exclude dir/file %s because "
-					"%s, ignoring\n", filename,
-					strerror(errno));
+			if(!(errno == ENOENT || errno == ENOTDIR)) {
+				ERROR_START("Cannot stat exclude dir/file %s "
+					"because %s", filename, strerror(errno));
+				ERROR_EXIT(", ignoring\n");
+			}
 			free(filename);
 			continue;
 		}
@@ -4025,8 +4040,9 @@
 #endif
 
 		if(sysctl(mib, 2, &processors, &len, NULL, 0) == -1) {
-			ERROR("Failed to get number of available processors.  "
-				"Defaulting to 1\n");
+			ERROR_START("Failed to get number of available "
+				"processors.");
+			ERROR_EXIT("  Defaulting to 1\n");
 			processors = 1;
 		}
 #else
@@ -4734,7 +4750,7 @@
 
 
 #define VERSION() \
-	printf("mksquashfs version 4.2-git (2014/01/25)\n");\
+	printf("mksquashfs version 4.2-git (2014/01/29)\n");\
 	printf("copyright (C) 2014 Phillip Lougher "\
 		"<phillip@squashfs.org.uk>\n\n"); \
 	printf("This program is free software; you can redistribute it and/or"\
@@ -5084,6 +5100,9 @@
 		else if(strcmp(argv[i], "-keep-as-directory") == 0)
 			keep_as_directory = TRUE;
 
+		else if(strcmp(argv[i], "-exit-on-error") == 0)
+			exit_on_error = TRUE;
+
 		else if(strcmp(argv[i], "-root-becomes") == 0) {
 			if(++i == argc) {
 				ERROR("%s: -root-becomes: missing name\n",
@@ -5162,6 +5181,8 @@
 			ERROR("\nMksquashfs runtime options:\n");
 			ERROR("-version\t\tprint version, licence and "
 				"copyright message\n");
+			ERROR("-exit-on-error\t\ttreat normally ignored errors "
+				"as fatal\n");
 			ERROR("-recover <name>\t\trecover filesystem data "
 				"using recovery file <name>\n");
 			ERROR("-no-recovery\t\tdon't generate a recovery "
diff --git a/squashfs-tools/pseudo.c b/squashfs-tools/pseudo.c
index 58890d0..382537a 100644
--- a/squashfs-tools/pseudo.c
+++ b/squashfs-tools/pseudo.c
@@ -2,7 +2,7 @@
  * Create a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2009, 2010, 2012
+ * Copyright (c) 2009, 2010, 2012, 2014
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -36,6 +36,7 @@
 
 #include "pseudo.h"
 #include "error.h"
+#include "progressbar.h"
 
 #define TRUE 1
 #define FALSE 0
@@ -154,16 +155,22 @@
 					pseudo->name[i].pseudo =
 						add_pseudo(NULL, pseudo_dev,
 						target, alltarget);
-				else
-					ERROR("%s already exists as a non "
-						"directory.  Ignoring %s!\n",
-						 targname, alltarget);
+				else {
+					ERROR_START("%s already exists as a "
+						"non directory.", targname);
+					ERROR_EXIT(".  Ignoring %s!\n",
+						alltarget);
+				}
 			} else if(memcmp(pseudo_dev, pseudo->name[i].dev,
-					sizeof(struct pseudo_dev)) != 0)
-				ERROR("%s already exists as a different pseudo "
-					"definition.  Ignoring!\n", alltarget);
-			else ERROR("%s already exists as an identical "
-					"pseudo definition!\n", alltarget);
+					sizeof(struct pseudo_dev)) != 0) {
+				ERROR_START("%s already exists as a different "
+					"pseudo definition.", alltarget);
+				ERROR_EXIT("  Ignoring!\n");
+			} else {
+				ERROR_START("%s already exists as an identical "
+					"pseudo definition!", alltarget);
+				ERROR_EXIT("  Ignoring!\n");
+			}
 		} else {
 			if(target[0] == '\0') {
 				/*
@@ -176,10 +183,13 @@
 					pseudo->name[i].pathname =
 						strdup(alltarget);
 					pseudo->name[i].dev = pseudo_dev;
-				} else
-					ERROR("%s already exists as a different"
-						" pseudo definition.  Ignoring"
-						" %s!\n", targname, alltarget);
+				} else {
+					ERROR_START("%s already exists as a "
+						"different pseudo definition.",
+						targname);
+					ERROR_EXIT("  Ignoring %s!\n",
+						alltarget);
+				}
 			} else
 				/* recurse adding child components */
 				add_pseudo(pseudo->name[i].pseudo, pseudo_dev,
diff --git a/squashfs-tools/sort.c b/squashfs-tools/sort.c
index f0dadac..89df9e4 100644
--- a/squashfs-tools/sort.c
+++ b/squashfs-tools/sort.c
@@ -3,7 +3,7 @@
  * filesystem.
  *
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
- * 2013
+ * 2013, 2014
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -42,6 +42,7 @@
 #include "mksquashfs.h"
 #include "sort.h"
 #include "error.h"
+#include "progressbar.h"
 
 int mkisofs_style = -1;
 
@@ -170,9 +171,10 @@
 	}
 
 error:
-        ERROR("Cannot stat sortlist entry \"%s\"\n", path);
+        ERROR_START("Cannot stat sortlist entry \"%s\"\n", path);
         ERROR("This is probably because you're using the wrong file\n");
-        ERROR("path relative to the source directories\n");
+        ERROR("path relative to the source directories.");
+	ERROR_EXIT("  Ignoring");
 	/*
 	 * Historical note
 	 * Failure to stat a sortlist entry is deliberately ignored, even
diff --git a/squashfs-tools/xattr.c b/squashfs-tools/xattr.c
index bb433e3..ae07e7e 100644
--- a/squashfs-tools/xattr.c
+++ b/squashfs-tools/xattr.c
@@ -2,7 +2,7 @@
  * Create a squashfs filesystem.  This is a highly compressed read only
  * filesystem.
  *
- * Copyright (c) 2008, 2009, 2010, 2012
+ * Copyright (c) 2008, 2009, 2010, 2012, 2014
  * Phillip Lougher <phillip@squashfs.org.uk>
  *
  * This program is free software; you can redistribute it and/or
@@ -121,8 +121,8 @@
 		size = llistxattr(filename, NULL, 0);
 		if(size <= 0) {
 			if(size < 0 && errno != ENOTSUP)
-				ERROR("llistxattr for %s failed in read_attrs,"
-					" because %s\n", filename,
+				ERROR_START("llistxattr for %s failed in "
+					"read_attrs, because %s", filename,
 					strerror(errno));
 			return 0;
 		}
@@ -138,8 +138,8 @@
 				/* xattr list grew?  Try again */
 				continue;
 			else {
-				ERROR("llistxattr for %s failed in read_attrs,"
-					" because %s\n", filename,
+				ERROR_START("llistxattr for %s failed in "
+					"read_attrs, because %s", filename,
 					strerror(errno));
 				return 0;
 			}
@@ -169,8 +169,8 @@
 			vsize = lgetxattr(filename, xattr_list[i].full_name,
 								NULL, 0);
 			if(vsize < 0) {
-				ERROR("lgetxattr failed for %s in read_attrs,"
-					" because %s\n", filename,
+				ERROR_START("lgetxattr failed for %s in "
+					"read_attrs, because %s", filename,
 					strerror(errno));
 				free(xattr_list[i].full_name);
 				goto failed;
@@ -188,8 +188,8 @@
 					/* xattr grew?  Try again */
 					continue;
 				else {
-					ERROR("lgetxattr failed for %s in "
-						"read_attrs, because %s\n",
+					ERROR_START("lgetxattr failed for %s "
+						"in read_attrs, because %s",
 						filename, strerror(errno));
 					free(xattr_list[i].full_name);
 					goto failed;
@@ -609,8 +609,10 @@
 		return SQUASHFS_INVALID_XATTR;
 
 	xattrs = read_xattrs_from_system(filename, &xattr_list);
-	if(xattrs == 0)
+	if(xattrs == 0) {
+		ERROR_EXIT(".  Ignoring");
 		return SQUASHFS_INVALID_XATTR;
+	}
 
 	return generate_xattrs(xattrs, xattr_list);
 }