[PATCH 1/5] apfsck: add cli options to pick the checks to run

1 view
Skip to first unread message

Ernesto A. Fernández

unread,
Mar 7, 2019, 2:38:01 PM3/7/19
to linux...@googlegroups.com
Often enough, there is no clear way to decide if something should be
reported as corruption.

Some filesystems created by Apple software have been found to violate
the specification in small ways (e.g., the volume file count). This is
most likely not corruption, but I have no idea what can be done to
improve the check and I don't want to drop it.

Filesystems that have recently crashed will have some features that
should never be seen under normal use. It may be useful to report when
they appear, to check if the module is unmounting cleanly.

Not all features of APFS will be supported in the near future, by either
the module or the fsck tool. If any of them appear to show up after the
filesystem was used on linux, that's clearly something that should be
reported, even if it's technically not corruption.

To deal with this consistently, distinguish these three types of checks
and add command line options to let the user decide which ones to run.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/apfsck.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
apfsck/apfsck.h | 9 ++++++++
apfsck/inode.c | 2 +-
apfsck/super.c | 4 ++--
4 files changed, 76 insertions(+), 6 deletions(-)

diff --git a/apfsck/apfsck.c b/apfsck/apfsck.c
index 7f1ecf2..bfc767b 100644
--- a/apfsck/apfsck.c
+++ b/apfsck/apfsck.c
@@ -10,10 +10,12 @@
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
+#include <unistd.h>
#include "apfsck.h"
#include "super.h"

int fd;
+unsigned int options;

/**
* usage - Print usage information and exit
@@ -21,7 +23,7 @@ int fd;
*/
static void usage(char *path)
{
- fprintf(stderr, "usage: %s device\n", path);
+ fprintf(stderr, "usage: %s [-cuw] device\n", path);
exit(1);
}

@@ -50,6 +52,44 @@ __attribute__((noreturn, format(printf, 2, 3))) void report(const char *context,
}

/**
+ * report_crash - Report that a crash was discovered and exit
+ * @context: structure with signs of a crash
+ *
+ * Does nothing unless the -c cli option was used.
+ */
+void report_crash(const char *context)
+{
+ if (options & OPT_REPORT_CRASH)
+ report(context, "the filesystem was not unmounted cleanly.");
+}
+
+/**
+ * report_unknown - Report the presence of unknown features and exit
+ * @feature: the unsupported feature
+ *
+ * Does nothing unless the -u cli option was used.
+ */
+void report_unknown(const char *feature)
+{
+ if (options & OPT_REPORT_UNKNOWN)
+ report(feature, "not supported.");
+}
+
+/**
+ * report_weird - Report unexplained inconsistencies and exit
+ * @context: structure where the inconsistency was found
+ *
+ * Does nothing unless the -w cli option was used. This function should
+ * be called when the specification, and common sense, appear to be in
+ * contradiction with the behaviour of actual filesystems.
+ */
+void report_weird(const char *context)
+{
+ if (options & OPT_REPORT_WEIRD)
+ report(context, "odd inconsistency (may not be corruption).");
+}
+
+/**
* parse_filesystem - Parse the filesystem looking for corruption
*/
static void parse_filesystem(void)
@@ -61,9 +101,30 @@ int main(int argc, char *argv[])
{
char *filename;

- if (argc != 2)
+ while (1) {
+ int opt = getopt(argc, argv, "cuw");
+
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 'c':
+ options |= OPT_REPORT_CRASH;
+ break;
+ case 'u':
+ options |= OPT_REPORT_UNKNOWN;
+ break;
+ case 'w':
+ options |= OPT_REPORT_WEIRD;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (optind >= argc)
usage(argv[0]);
- filename = argv[1];
+ filename = argv[optind];

fd = open(filename, O_RDONLY);
if (fd == -1) {
diff --git a/apfsck/apfsck.h b/apfsck/apfsck.h
index 8baf66a..b1b37f8 100644
--- a/apfsck/apfsck.h
+++ b/apfsck/apfsck.h
@@ -8,11 +8,20 @@
#define _APFSCK_H

/* Declarations for global variables */
+extern unsigned int options; /* Command line options */
extern struct super_block *sb; /* Filesystem superblock */
extern struct volume_superblock *vsb; /* Volume superblock */
extern int fd; /* File descriptor for the device */

+/* Option flags */
+#define OPT_REPORT_CRASH 1 /* Report on-disk signs of a past crash */
+#define OPT_REPORT_UNKNOWN 2 /* Report unknown or unsupported features */
+#define OPT_REPORT_WEIRD 4 /* Report issues that may not be corruption */
+
extern __attribute__((noreturn, format(printf, 2, 3)))
void report(const char *context, const char *message, ...);
+extern void report_crash(const char *context);
+extern void report_unknown(const char *feature);
+extern void report_weird(const char *context);

#endif /* _APFSCK_H */
diff --git a/apfsck/inode.c b/apfsck/inode.c
index cce1e87..cbbb393 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -365,7 +365,7 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
xlen = 16;
break;
case APFS_INO_EXT_TYPE_PREV_FSIZE:
- printf("Inode xfield: filesystem has crashed.\n");
+ report_crash("Inode xfield");
if (xflags != 0)
report("Previous size xfield", "wrong flags.");
case APFS_INO_EXT_TYPE_SNAP_XID:
diff --git a/apfsck/super.c b/apfsck/super.c
index e7063ec..6bfdb5a 100644
--- a/apfsck/super.c
+++ b/apfsck/super.c
@@ -123,7 +123,7 @@ static void map_main_super(void)
report("Checkpoint descriptors", "latest is missing.");
/* TODO: the latest checkpoint and block zero are somehow different? */
if (sb->s_xid != le64_to_cpu(msb_raw->nx_o.o_xid))
- report("Block zero", "filesystem was not unmounted cleanly.");
+ report_crash("Block zero");
munmap(msb_raw, sb->s_blocksize);
}

@@ -208,7 +208,7 @@ void parse_super(void)
if (le64_to_cpu(vsb_raw->apfs_num_files) !=
vsb->v_file_count)
/* Sometimes this is off by one. TODO: why? */
- printf("Bad file count, may not be corruption.\n");
+ report_weird("File count in volume superblock");
if (le64_to_cpu(vsb_raw->apfs_num_directories) !=
vsb->v_dir_count)
report("Volume superblock", "bad directory count.");
--
2.11.0

Ernesto A. Fernández

unread,
Mar 7, 2019, 2:38:18 PM3/7/19
to linux...@googlegroups.com
The apfsck cli has become complex enough to need some documentation, so
write a man page.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/apfsck.8 | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 apfsck/apfsck.8

diff --git a/apfsck/apfsck.8 b/apfsck/apfsck.8
new file mode 100644
index 0000000..65e9b77
--- /dev/null
+++ b/apfsck/apfsck.8
@@ -0,0 +1,35 @@
+.\" apfsck.8 - manpage for apfsck
+.\"
+.\" Copyright (C) 2019 Ernesto A. Fernández <ernesto.mn...@gmail.com>
+.\"
+.TH apfsck 8 "March 2019" "apfsprogs 0.1"
+.SH NAME
+apfsck \- check an APFS filesystem
+.SH SYNOPSIS
+.B apfsck
+[\-cuw]
+.I device
+.SH DESCRIPTION
+.B apfsck
+is an experimental tool that checks an APFS filesystem for corruption. When an
+issue is found it is reported, but no repair is attempted. For that purpose,
+you should use the official tools provided by Apple.
+.SH OPTIONS
+.TP
+.B \-c
+Report if the filesystem shows signs of a recent crash.
+.TP
+.B \-u
+Report the presence of unknown/unsupported features.
+.TP
+.B \-w
+Report unexplained inconsistencies that may or may not be corruption.
+.SH EXIT STATUS
+The exit status is 0 if there was nothing to report, 1 otherwise.
+.SH REPORTING BUGS
+Please submit any issues that you find to the project's mailing list at
+<linux...@googlegroups.com>.
+.SH AUTHOR
+Written by Ernesto A. Fernández <ernesto.mn...@gmail.com>.
+.SH SEE ALSO
+.BR mkapfs (8)
--
2.11.0

Ernesto A. Fernández

unread,
Mar 7, 2019, 2:38:40 PM3/7/19
to linux...@googlegroups.com
Add an install target to the Makefile that puts the apfsck executable
and its man page in the appropriate directories for the current user.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/Makefile | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/apfsck/Makefile b/apfsck/Makefile
index ddb38f7..cef4a1e 100644
--- a/apfsck/Makefile
+++ b/apfsck/Makefile
@@ -3,6 +3,9 @@ SRCS = apfsck.c btree.c crc32c.c dir.c extents.c \
OBJS = $(SRCS:.c=.o)
DEPS = $(SRCS:.c=.d)

+BINDIR = ~/bin
+MANDIR = ~/share/man/man8
+
apfsck: $(OBJS)
gcc $(CFLAGS) -o apfsck $(OBJS)

@@ -13,3 +16,10 @@ apfsck: $(OBJS)

clean:
rm -f $(OBJS) $(DEPS) apfsck
+install:
+ install -d $(BINDIR)
+ install -t $(BINDIR) apfsck
+ ln -fs -T $(BINDIR)/apfsck $(BINDIR)/fsck.apfs
+ install -d $(MANDIR)
+ install -m 644 -t $(MANDIR) apfsck.8
+ ln -fs -T $(MANDIR)/apfsck.8 $(MANDIR)/fsck.apfs.8
--
2.11.0

Ernesto A. Fernández

unread,
Mar 7, 2019, 2:38:53 PM3/7/19
to linux...@googlegroups.com
The module does not currently support encryption, and this is unlikely
to change in the near future. Report the presence of encrypted objects
as an unsupported feature.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/object.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/apfsck/object.c b/apfsck/object.c
index 845db21..21d14d5 100644
--- a/apfsck/object.c
+++ b/apfsck/object.c
@@ -100,6 +100,8 @@ void *read_object(u64 oid, struct node *omap, struct object *obj)
report("Object header", "undefined flag in use.");
if (obj->flags & APFS_OBJ_NONPERSISTENT)
report("Object header", "nonpersistent flag is set.");
+ if (obj->flags & APFS_OBJ_ENCRYPTED)
+ report_unknown("Encrypted object");

/* TODO: ephemeral objects? */
storage_type = obj->flags & APFS_OBJ_STORAGETYPE_MASK;
--
2.11.0

Ernesto A. Fernández

unread,
Mar 7, 2019, 2:39:04 PM3/7/19
to linux...@googlegroups.com
There are several types of extended fields that I have never encountered
so far. Some checks can be run based on the specification, but getting
the flags right would be tricky. Just report them as unsupported.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/inode.c | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/apfsck/inode.c b/apfsck/inode.c
index cbbb393..54a5274 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -363,14 +363,21 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
switch (xfield[i].x_type) {
case APFS_INO_EXT_TYPE_FS_UUID:
xlen = 16;
+ report_unknown("UUID xfield");
break;
case APFS_INO_EXT_TYPE_PREV_FSIZE:
+ xlen = 8;
report_crash("Inode xfield");
if (xflags != 0)
report("Previous size xfield", "wrong flags.");
+ break;
case APFS_INO_EXT_TYPE_SNAP_XID:
+ xlen = 8;
+ report_unknown("Snapshot id xfield");
+ break;
case APFS_INO_EXT_TYPE_DELTA_TREE_OID:
xlen = 8;
+ report_unknown("Snapshot's extent delta list xfield");
break;
case APFS_INO_EXT_TYPE_SPARSE_BYTES:
xlen = read_sparse_bytes_xfield(xval, len, inode);
@@ -380,12 +387,15 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
break;
case APFS_INO_EXT_TYPE_DOCUMENT_ID:
xlen = read_document_id_xfield(xval, len, inode);
+ report_unknown("Document id xfield");
break;
case APFS_INO_EXT_TYPE_FINDER_INFO:
xlen = 4;
+ report_unknown("Finder info xfield");
break;
case APFS_INO_EXT_TYPE_RDEV:
xlen = read_rdev_xfield(xval, len, inode);
+ report_unknown("Device identifier xfield");
break;
case APFS_INO_EXT_TYPE_NAME:
xlen = read_name_xfield(xval, len, inode);
@@ -399,6 +409,7 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
break;
case APFS_INO_EXT_TYPE_DIR_STATS_KEY:
xlen = sizeof(struct apfs_dir_stats_val);
+ report_unknown("Directory statistics xfield");
break;
case APFS_INO_EXT_TYPE_RESERVED_6:
case APFS_INO_EXT_TYPE_RESERVED_9:
--
2.11.0

Reply all
Reply to author
Forward
0 new messages