[PATCH 1/7] apfsck: improve checks for inode ids

2 views
Skip to first unread message

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:31:19 PM2/26/19
to linux...@googlegroups.com
Move the checks for special inode numbers from parse_inode_record() into
a new function, which can be used by parse_dentry_record() as well.
Also add checks for the parent id of a user inode.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/dir.c | 1 +
apfsck/inode.c | 59 ++++++++++++++++++++++++++++++++++++++++++----------------
apfsck/inode.h | 1 +
3 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/apfsck/dir.c b/apfsck/dir.c
index 6488f4a..a252327 100644
--- a/apfsck/dir.c
+++ b/apfsck/dir.c
@@ -98,6 +98,7 @@ void parse_dentry_record(struct apfs_drec_hashed_key *key,
inode->i_link_count++;

parent_ino = cat_cnid(&key->hdr);
+ check_inode_ids(ino, parent_ino);
if (parent_ino != APFS_ROOT_DIR_PARENT) {
parent = get_inode(parent_ino, vsb->v_inode_table);
if (!parent->i_seen) /* The b-tree keys are in order */
diff --git a/apfsck/inode.c b/apfsck/inode.c
index 0337ea3..6bee492 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -201,6 +201,48 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len)
}

/**
+ * check_inode_ids - Check that an inode id is consistent with its parent id
+ * @ino: inode number
+ * @parent_ino: parent inode number
+ */
+void check_inode_ids(u64 ino, u64 parent_ino)
+{
+ if (ino < APFS_MIN_USER_INO_NUM) {
+ switch (ino) {
+ case APFS_INVALID_INO_NUM:
+ case APFS_ROOT_DIR_PARENT:
+ report("Inode record", "invalid inode number.");
+ case APFS_ROOT_DIR_INO_NUM:
+ case APFS_PRIV_DIR_INO_NUM:
+ case APFS_SNAP_DIR_INO_NUM:
+ /* All children of this fake parent? TODO: check this */
+ if (parent_ino != APFS_ROOT_DIR_PARENT)
+ report("Root inode record", "bad parent id");
+ break;
+ default:
+ report("Inode record", "reserved inode number.");
+ }
+ return;
+ }
+
+ if (parent_ino < APFS_MIN_USER_INO_NUM) {
+ switch (parent_ino) {
+ case APFS_INVALID_INO_NUM:
+ report("Inode record", "invalid parent inode number.");
+ case APFS_ROOT_DIR_PARENT:
+ report("Inode record", "root parent id for nonroot.");
+ case APFS_ROOT_DIR_INO_NUM:
+ case APFS_PRIV_DIR_INO_NUM:
+ case APFS_SNAP_DIR_INO_NUM:
+ /* These are fine */
+ break;
+ default:
+ report("Inode record", "reserved parent inode number.");
+ }
+ }
+}
+
+/**
* parse_inode_record - Parse an inode record value and check for corruption
* @key: pointer to the raw key
* @val: pointer to the raw value
@@ -222,22 +264,7 @@ void parse_inode_record(struct apfs_inode_key *key,
report("Catalog", "inode numbers are repeated.");
inode->i_seen = true;

- if (inode->i_ino < APFS_MIN_USER_INO_NUM) {
- switch (inode->i_ino) {
- case APFS_INVALID_INO_NUM:
- case APFS_ROOT_DIR_PARENT:
- report("Inode record", "invalid inode number.");
- case APFS_ROOT_DIR_INO_NUM:
- case APFS_PRIV_DIR_INO_NUM:
- case APFS_SNAP_DIR_INO_NUM:
- /* All children of this fake parent? TODO: check this */
- if (le64_to_cpu(val->parent_id) != APFS_ROOT_DIR_PARENT)
- report("Root inode record", "bad parent id");
- break;
- default:
- report("Inode record", "reserved inode number.");
- }
- }
+ check_inode_ids(inode->i_ino, le64_to_cpu(val->parent_id));

mode = le16_to_cpu(val->mode);
filetype = mode & S_IFMT;
diff --git a/apfsck/inode.h b/apfsck/inode.h
index 6b9eca4..4063966 100644
--- a/apfsck/inode.h
+++ b/apfsck/inode.h
@@ -149,6 +149,7 @@ struct inode {
extern struct inode **alloc_inode_table();
extern void free_inode_table(struct inode **table);
extern struct inode *get_inode(u64 ino, struct inode **table);
+extern void check_inode_ids(u64 ino, u64 parent_ino);
extern void parse_inode_record(struct apfs_inode_key *key,
struct apfs_inode_val *val, int len);

--
2.11.0

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:31:35 PM2/26/19
to linux...@googlegroups.com
Check that the size of the value is correct, that the length of the
extent is a whole number of blocks, and that no flags are set.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/Makefile | 3 ++-
apfsck/btree.c | 4 ++++
apfsck/extents.c | 35 +++++++++++++++++++++++++++++++++++
apfsck/extents.h | 31 +++++++++++++++++++++++++++++++
4 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 apfsck/extents.c
create mode 100644 apfsck/extents.h

diff --git a/apfsck/Makefile b/apfsck/Makefile
index ce051b8..ddb38f7 100644
--- a/apfsck/Makefile
+++ b/apfsck/Makefile
@@ -1,4 +1,5 @@
-SRCS = apfsck.c btree.c crc32c.c dir.c inode.c key.c object.c super.c unicode.c
+SRCS = apfsck.c btree.c crc32c.c dir.c extents.c \
+ inode.c key.c object.c super.c unicode.c
OBJS = $(SRCS:.c=.o)
DEPS = $(SRCS:.c=.d)

diff --git a/apfsck/btree.c b/apfsck/btree.c
index 7b6cfa1..6724a40 100644
--- a/apfsck/btree.c
+++ b/apfsck/btree.c
@@ -12,6 +12,7 @@
#include "apfsck.h"
#include "btree.h"
#include "dir.h"
+#include "extents.h"
#include "inode.h"
#include "key.h"
#include "object.h"
@@ -505,6 +506,9 @@ static void parse_cat_record(void *key, void *val, int len)
case APFS_TYPE_DIR_REC:
parse_dentry_record(key, val, len);
break;
+ case APFS_TYPE_FILE_EXTENT:
+ parse_extent_record(key, val, len);
+ break;
default:
break;
}
diff --git a/apfsck/extents.c b/apfsck/extents.c
new file mode 100644
index 0000000..184dbab
--- /dev/null
+++ b/apfsck/extents.c
@@ -0,0 +1,35 @@
+/*
+ * apfsprogs/apfsck/extents.c
+ *
+ * Copyright (C) 2018 Ernesto A. Fernández <ernesto.mn...@gmail.com>
+ */
+
+#include "apfsck.h"
+#include "extents.h"
+#include "key.h"
+#include "super.h"
+
+/**
+ * parse_extent_record - Parse an extent record value and check for corruption
+ * @key: pointer to the raw key
+ * @val: pointer to the raw value
+ * @len: length of the raw value
+ *
+ * Internal consistency of @key must be checked before calling this function.
+ */
+void parse_extent_record(struct apfs_file_extent_key *key,
+ struct apfs_file_extent_val *val, int len)
+{
+ u64 length, flags;
+
+ if (len != sizeof(*val))
+ report("Extent record", "wrong size of value.");
+
+ /* TODO: checks for crypto_id */
+ length = le64_to_cpu(val->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
+ if (length & (sb->s_blocksize - 1))
+ report("Extent record", "length isn't multiple of block size.");
+ flags = le64_to_cpu(val->len_and_flags) & APFS_FILE_EXTENT_FLAG_MASK;
+ if (flags)
+ report("Extent record", "no flags should be set.");
+}
diff --git a/apfsck/extents.h b/apfsck/extents.h
new file mode 100644
index 0000000..ec9c0fb
--- /dev/null
+++ b/apfsck/extents.h
@@ -0,0 +1,31 @@
+/*
+ * apfsprogs/apfsck/extents.h
+ *
+ * Copyright (C) 2018 Ernesto A. Fernández <ernesto.mn...@gmail.com>
+ */
+
+#ifndef _EXTENTS_H
+#define _EXTENTS_H
+
+#include "types.h"
+
+struct apfs_file_extent_key;
+
+/* File extent records */
+#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
+#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
+#define APFS_FILE_EXTENT_FLAG_SHIFT 56
+
+/*
+ * Structure of a file extent record
+ */
+struct apfs_file_extent_val {
+ __le64 len_and_flags;
+ __le64 phys_block_num;
+ __le64 crypto_id;
+} __packed;
+
+extern void parse_extent_record(struct apfs_file_extent_key *key,
+ struct apfs_file_extent_val *val, int len);
+
+#endif /* _EXTENTS_H */
--
2.11.0

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:31:50 PM2/26/19
to linux...@googlegroups.com
When reading an extent key, check that its offset is a multiple of the
block size.

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

diff --git a/apfsck/key.c b/apfsck/key.c
index 4dd97b9..20d394e 100644
--- a/apfsck/key.c
+++ b/apfsck/key.c
@@ -202,6 +202,9 @@ static void read_file_extent_key(void *raw, int size, struct key *key)

key->number = le64_to_cpu(raw_key->logical_addr);
key->name = NULL;
+
+ if (key->number & (sb->s_blocksize - 1))
+ report("Extent record", "offset isn't multiple of block size.");
}

/**
--
2.11.0

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:32:03 PM2/26/19
to linux...@googlegroups.com
While checking the catalog tree, add an entry to a hash table for every
file extent record found. For now its only purpose is to verify that
extents are consecutive, i.e., that each extent begins where the last
one ended. More checks will be added in the future.

The hash table code is essentially the same for extents and inodes, so
I intend to refactor this soon.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/extents.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
apfsck/extents.h | 15 +++++++++++
apfsck/super.c | 4 +++
apfsck/super.h | 3 ++-
4 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/apfsck/extents.c b/apfsck/extents.c
index 184dbab..a1cf4e8 100644
--- a/apfsck/extents.c
+++ b/apfsck/extents.c
@@ -4,12 +4,87 @@
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mn...@gmail.com>
*/

+#include <stdlib.h>
+#include <stdio.h>
#include "apfsck.h"
#include "extents.h"
#include "key.h"
#include "super.h"

/**
+ * alloc_dstream_table - Allocates and returns an empty dstream hash table
+ */
+struct dstream **alloc_dstream_table(void)
+{
+ struct dstream **table;
+
+ table = calloc(DSTREAM_TABLE_BUCKETS, sizeof(*table));
+ if (!table) {
+ perror(NULL);
+ exit(1);
+ }
+ return table;
+}
+
+/**
+ * free_dstream_table - Free the dstream hash table and all its dstreams
+ * @table: table to free
+ */
+void free_dstream_table(struct dstream **table)
+{
+ struct dstream *current;
+ struct dstream *next;
+ int i;
+
+ for (i = 0; i < DSTREAM_TABLE_BUCKETS; ++i) {
+ current = table[i];
+ while (current) {
+ next = current->d_next;
+ free(current);
+ current = next;
+ }
+ }
+ free(table);
+}
+
+/**
+ * get_dstream - Find or create a dstream structure in a hash table
+ * @id: id of the dstream
+ * @table: the hash table
+ *
+ * Returns the dstream structure, after creating it if necessary.
+ */
+struct dstream *get_dstream(u64 id, struct dstream **table)
+{
+ int index = id % DSTREAM_TABLE_BUCKETS; /* Trivial hash function */
+ struct dstream **entry_p = table + index;
+ struct dstream *entry = *entry_p;
+ struct dstream *new;
+
+ /* Dstreams are ordered by id in each linked list */
+ while (entry) {
+ if (id == entry->d_id)
+ return entry;
+ if (id < entry->d_id)
+ break;
+
+ entry_p = &entry->d_next;
+ entry = *entry_p;
+ }
+
+ new = calloc(1, sizeof(*new));
+ if (!new) {
+ perror(NULL);
+ exit(1);
+ }
+
+ new->d_id = id;
+ new->d_next = entry;
+ *entry_p = new;
+ return new;
+}
+
+/**
* parse_extent_record - Parse an extent record value and check for corruption
* @key: pointer to the raw key
* @val: pointer to the raw value
@@ -20,6 +95,7 @@
void parse_extent_record(struct apfs_file_extent_key *key,
struct apfs_file_extent_val *val, int len)
{
+ struct dstream *dstream;
u64 length, flags;

if (len != sizeof(*val))
@@ -32,4 +108,9 @@ void parse_extent_record(struct apfs_file_extent_key *key,
flags = le64_to_cpu(val->len_and_flags) & APFS_FILE_EXTENT_FLAG_MASK;
if (flags)
report("Extent record", "no flags should be set.");
+
+ dstream = get_dstream(cat_cnid(&key->hdr), vsb->v_dstream_table);
+ if (dstream->d_size != le64_to_cpu(key->logical_addr))
+ report("Data stream", "extents are not consecutive.");
+ dstream->d_size += length;
}
diff --git a/apfsck/extents.h b/apfsck/extents.h
index ec9c0fb..1b838de 100644
--- a/apfsck/extents.h
+++ b/apfsck/extents.h
@@ -25,6 +25,21 @@ struct apfs_file_extent_val {
__le64 crypto_id;
} __packed;

+#define DSTREAM_TABLE_BUCKETS 512 /* So the hash table array fits in 4k */
+
+/*
+ * Dstream data in memory
+ */
+struct dstream {
+ u64 d_id; /* Id of the dstream */
+ u64 d_size; /* Size of the extents read so far */
+
+ struct dstream *d_next; /* Next dstream in linked list */
+};
+
+extern struct dstream **alloc_dstream_table();
+extern void free_dstream_table(struct dstream **table);
+extern struct dstream *get_dstream(u64 ino, struct dstream **table);
extern void parse_extent_record(struct apfs_file_extent_key *key,
struct apfs_file_extent_val *val, int len);

diff --git a/apfsck/super.c b/apfsck/super.c
index 15dfed7..e270fd8 100644
--- a/apfsck/super.c
+++ b/apfsck/super.c
@@ -10,6 +10,7 @@
#include <sys/mman.h>
#include "apfsck.h"
#include "btree.h"
+#include "extents.h"
#include "inode.h"
#include "object.h"
#include "types.h"
@@ -180,6 +181,7 @@ void parse_super(void)
perror(NULL);
exit(1);
}
+ vsb->v_dstream_table = alloc_dstream_table();
vsb->v_inode_table = alloc_inode_table();

vsb_raw = map_volume_super(vol, vsb);
@@ -198,6 +200,8 @@ void parse_super(void)

free_inode_table(vsb->v_inode_table);
vsb->v_inode_table = NULL;
+ free_dstream_table(vsb->v_dstream_table);
+ vsb->v_dstream_table = NULL;

if (le64_to_cpu(vsb_raw->apfs_num_files) !=
vsb->v_file_count)
diff --git a/apfsck/super.h b/apfsck/super.h
index 7e606c8..133d7c2 100644
--- a/apfsck/super.h
+++ b/apfsck/super.h
@@ -258,7 +258,8 @@ struct volume_superblock {
struct apfs_superblock *v_raw;
struct btree *v_omap;
struct btree *v_cat;
- struct inode **v_inode_table; /* Hash table listing the inodes */
+ struct inode **v_inode_table; /* Hash table of all inodes */
+ struct dstream **v_dstream_table; /* Hash table of all dstreams */

/* Volume stats as measured by the fsck */
u64 v_file_count; /* Number of files */
--
2.11.0

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:32:16 PM2/26/19
to linux...@googlegroups.com
An inode's content should fit inside its extents; add a check for this.
Since the inode size is kept in an xfield, some modifications to
parse_inode_xfields() are required.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/inode.c | 41 ++++++++++++++++++++++++++++++++++++++---
apfsck/inode.h | 2 ++
2 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/apfsck/inode.c b/apfsck/inode.c
index 6bee492..2aba698 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -4,10 +4,12 @@
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mn...@gmail.com>
*/

+#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "apfsck.h"
+#include "extents.h"
#include "inode.h"
#include "key.h"
#include "super.h"
@@ -19,6 +21,11 @@
*/
static void check_inode_stats(struct inode *inode)
{
+ struct dstream *dstream;
+
+ /* The inodes must be freed before the dstreams */
+ assert(vsb->v_dstream_table);
+
if ((inode->i_mode & S_IFMT) == S_IFDIR) {
if (inode->i_link_count != 1)
report("Inode record", "directory has hard links.");
@@ -28,6 +35,10 @@ static void check_inode_stats(struct inode *inode)
if (inode->i_nlink != inode->i_link_count)
report("Inode record", "wrong link count.");
}
+
+ dstream = get_dstream(inode->i_private_id, vsb->v_dstream_table);
+ if (dstream->d_size < inode->i_size)
+ report("Inode record", "some extents are missing.");
}

/**
@@ -110,13 +121,36 @@ struct inode *get_inode(u64 ino, struct inode **table)
}

/**
+ * read_dstream_xfield - Parse a dstream xfield and check its consistency
+ * @xval: pointer to the xfield value
+ * @len: remaining length of the inode value
+ * @inode: struct to receive the results
+ *
+ * Returns the length of the xfield value.
+ */
+static int read_dstream_xfield(char *xval, int len, struct inode *inode)
+{
+ struct apfs_dstream *dstream;
+
+ if (len < sizeof(*dstream))
+ report("Dstream xfield", "doesn't fit in inode record.");
+ dstream = (struct apfs_dstream *)xval;
+
+ inode->i_size = le64_to_cpu(dstream->size);
+
+ return sizeof(*dstream);
+}
+
+/**
* parse_inode_xfields - Parse and check an inode extended fields
* @xblob: pointer to the beginning of the xfields in the inode value
* @len: length of the xfields
+ * @inode: struct to receive the results
*
* Internal consistency of @key must be checked before calling this function.
*/
-static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len)
+static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
+ struct inode *inode)
{
struct apfs_x_field *xfield;
char *xval;
@@ -166,7 +200,7 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len)
"name with no null termination");
break;
case APFS_INO_EXT_TYPE_DSTREAM:
- xlen = sizeof(struct apfs_dstream);
+ xlen = read_dstream_xfield(xval, len, inode);
break;
case APFS_INO_EXT_TYPE_DIR_STATS_KEY:
xlen = sizeof(struct apfs_dir_stats_val);
@@ -263,6 +297,7 @@ void parse_inode_record(struct apfs_inode_key *key,
if (inode->i_seen)
report("Catalog", "inode numbers are repeated.");
inode->i_seen = true;
+ inode->i_private_id = le64_to_cpu(val->private_id);

check_inode_ids(inode->i_ino, le64_to_cpu(val->parent_id));

@@ -301,5 +336,5 @@ void parse_inode_record(struct apfs_inode_key *key,
report("Inode record", "padding should be zeroes.");

parse_inode_xfields((struct apfs_xf_blob *)val->xfields,
- len - sizeof(*val));
+ len - sizeof(*val), inode);
}
diff --git a/apfsck/inode.h b/apfsck/inode.h
index 4063966..2097b58 100644
--- a/apfsck/inode.h
+++ b/apfsck/inode.h
@@ -130,6 +130,7 @@ struct apfs_dir_stats_val {
*/
struct inode {
u64 i_ino; /* Inode number */
+ u64 i_private_id; /* Id of the inode's data stream */
bool i_seen; /* Has this inode been seen? */

/* Inode information read from its record (or from its dentries) */
@@ -138,6 +139,7 @@ struct inode {
u32 i_nchildren; /* Number of children of directory */
u32 i_nlink; /* Number of hard links to file */
};
+ u64 i_size; /* Inode size */

/* Inode stats measured by the fsck */
u32 i_child_count; /* Number of children of directory */
--
2.11.0

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:32:30 PM2/26/19
to linux...@googlegroups.com
The alloced_size field of the inode dstream xfield should report the
total space allocated for the data stream, even if unused. This matches
the sum of the length of the extents; surprisingly, extents representing
holes in sparse files are also counted.

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

diff --git a/apfsck/inode.c b/apfsck/inode.c
index 2aba698..513af6a 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -39,6 +39,8 @@ static void check_inode_stats(struct inode *inode)
dstream = get_dstream(inode->i_private_id, vsb->v_dstream_table);
if (dstream->d_size < inode->i_size)
report("Inode record", "some extents are missing.");
+ if (dstream->d_size != inode->i_alloced_size)
+ report("Inode record", "wrong allocated space for dstream.");
}

/**
@@ -137,6 +139,7 @@ static int read_dstream_xfield(char *xval, int len, struct inode *inode)
dstream = (struct apfs_dstream *)xval;

inode->i_size = le64_to_cpu(dstream->size);
+ inode->i_alloced_size = le64_to_cpu(dstream->alloced_size);

return sizeof(*dstream);
}
diff --git a/apfsck/inode.h b/apfsck/inode.h
index 2097b58..8fa3696 100644
--- a/apfsck/inode.h
+++ b/apfsck/inode.h
@@ -140,6 +140,7 @@ struct inode {
u32 i_nlink; /* Number of hard links to file */
};
u64 i_size; /* Inode size */
+ u64 i_alloced_size; /* Inode size, including unused */

Ernesto A. Fernández

unread,
Feb 26, 2019, 2:32:46 PM2/26/19
to linux...@googlegroups.com
A sparse inode reports the total length of its holes in an extended
field. Check that it's consistent with the actual hole extents.

Signed-off-by: Ernesto A. Fernández <ernesto.mn...@gmail.com>
---
apfsck/extents.c | 3 +++
apfsck/extents.h | 1 +
apfsck/inode.c | 27 ++++++++++++++++++++++++++-
apfsck/inode.h | 1 +
4 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/apfsck/extents.c b/apfsck/extents.c
index a1cf4e8..32fbbbc 100644
--- a/apfsck/extents.c
+++ b/apfsck/extents.c
@@ -113,4 +113,7 @@ void parse_extent_record(struct apfs_file_extent_key *key,
if (dstream->d_size != le64_to_cpu(key->logical_addr))
report("Data stream", "extents are not consecutive.");
dstream->d_size += length;
+
+ if (!le64_to_cpu(val->phys_block_num)) /* This is a hole */
+ dstream->d_sparse_bytes += length;
}
diff --git a/apfsck/extents.h b/apfsck/extents.h
index 1b838de..d774e54 100644
--- a/apfsck/extents.h
+++ b/apfsck/extents.h
@@ -33,6 +33,7 @@ struct apfs_file_extent_val {
struct dstream {
u64 d_id; /* Id of the dstream */
u64 d_size; /* Size of the extents read so far */
+ u64 d_sparse_bytes; /* Size of the holes read so far */

struct dstream *d_next; /* Next dstream in linked list */
};
diff --git a/apfsck/inode.c b/apfsck/inode.c
index 513af6a..ac9bcf6 100644
--- a/apfsck/inode.c
+++ b/apfsck/inode.c
@@ -41,6 +41,8 @@ static void check_inode_stats(struct inode *inode)
report("Inode record", "some extents are missing.");
if (dstream->d_size != inode->i_alloced_size)
report("Inode record", "wrong allocated space for dstream.");
+ if (dstream->d_sparse_bytes != inode->i_sparse_bytes)
+ report("Inode record", "wrong count of sparse bytes.");
}

/**
@@ -123,6 +125,27 @@ struct inode *get_inode(u64 ino, struct inode **table)
}

/**
+ * read_sparse_bytes_xfield - Parse and check an xfield that counts sparse bytes
+ * @xval: pointer to the xfield value
+ * @len: remaining length of the inode value
+ * @inode: struct to receive the results
+ *
+ * Returns the length of the xfield value.
+ */
+static int read_sparse_bytes_xfield(char *xval, int len, struct inode *inode)
+{
+ __le64 *sbytes;
+
+ if (len < 8)
+ report("Sparse bytes xfield", "doesn't fit in inode record.");
+ sbytes = (__le64 *)xval;
+
+ inode->i_sparse_bytes = le64_to_cpu(*sbytes);
+
+ return sizeof(*sbytes);
+}
+
+/**
* read_dstream_xfield - Parse a dstream xfield and check its consistency
* @xval: pointer to the xfield value
* @len: remaining length of the inode value
@@ -188,9 +211,11 @@ static void parse_inode_xfields(struct apfs_xf_blob *xblob, int len,
case APFS_INO_EXT_TYPE_SNAP_XID:
case APFS_INO_EXT_TYPE_DELTA_TREE_OID:
case APFS_INO_EXT_TYPE_PREV_FSIZE:
- case APFS_INO_EXT_TYPE_SPARSE_BYTES:
xlen = 8;
break;
+ case APFS_INO_EXT_TYPE_SPARSE_BYTES:
+ xlen = read_sparse_bytes_xfield(xval, len, inode);
+ break;
case APFS_INO_EXT_TYPE_DOCUMENT_ID:
case APFS_INO_EXT_TYPE_FINDER_INFO:
case APFS_INO_EXT_TYPE_RDEV:
diff --git a/apfsck/inode.h b/apfsck/inode.h
index 8fa3696..1cf1979 100644
--- a/apfsck/inode.h
+++ b/apfsck/inode.h
@@ -141,6 +141,7 @@ struct inode {
};
u64 i_size; /* Inode size */
u64 i_alloced_size; /* Inode size, including unused */
+ u64 i_sparse_bytes; /* Number of sparse bytes */
Reply all
Reply to author
Forward
0 new messages