mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-08-18 02:19:59 -07:00
Compare commits
No files matched your search
@@ -6,7 +6,6 @@
|
||||
KERNELRELEASE ?= $(shell uname -r)
|
||||
KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build
|
||||
PWD := $(shell pwd)
|
||||
GIT_COMMIT = $(shell git describe HEAD | tail -c 9)
|
||||
|
||||
obj-m = apfs.o
|
||||
apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
|
||||
@@ -14,8 +13,13 @@ apfs-y := btree.o compress.o dir.o extents.o file.o inode.o key.o libzbitmap.o \
|
||||
lzfse/lzvn_decode_base.o message.o namei.o node.o object.o snapshot.o \
|
||||
spaceman.o super.o symlink.o transaction.o unicode.o xattr.o xfield.o
|
||||
|
||||
# If you want mounts to be writable by default, run the build as:
|
||||
# make CONFIG=-DCONFIG_APFS_RW_ALWAYS
|
||||
# This is risky and not generally recommended.
|
||||
ccflags-y += $(CONFIG)
|
||||
|
||||
default:
|
||||
@printf '#define GIT_COMMIT\t"%s"\n' $(GIT_COMMIT) > version.h
|
||||
./genver.sh
|
||||
make -C $(KERNEL_DIR) M=$(PWD)
|
||||
install:
|
||||
make -C $(KERNEL_DIR) M=$(PWD) modules_install
|
||||
|
||||
+26
-11
@@ -4,13 +4,8 @@ Apple File System for Linux
|
||||
|
||||
The Apple File System (APFS) is the copy-on-write filesystem currently used on
|
||||
all Apple devices. This module provides a degree of experimental support on
|
||||
Linux.
|
||||
|
||||
It's supposed to work with a range of kernel versions starting at 4.9 or before,
|
||||
but only a few of those have actually been tested. Also, kernel versions below
|
||||
4.12 will be slower for some operations. If you run into any problem, please
|
||||
send a report to <linux-apfs@googlegroups.com> or file a github issue at
|
||||
https://github.com/eafer/linux-apfs-rw/issues.
|
||||
Linux. It's intended to work with all kernel versions since 4.12, but testing
|
||||
is focused on 5.18 and above.
|
||||
|
||||
To help test write support, a set of userland tools is also under development.
|
||||
The git tree can be retrieved from https://github.com/eafer/apfsprogs.git.
|
||||
@@ -19,14 +14,32 @@ Known limitations
|
||||
=================
|
||||
|
||||
This module is the result of reverse engineering and testing has been limited.
|
||||
If you make use of the write support, expect data corruption. Please report any
|
||||
issues that you find, but I can't promise a quick resolution at this stage.
|
||||
If you make use of the write support, there is a real risk of data corruption,
|
||||
so mounts are read-only by default. Please report any issues that you find.
|
||||
|
||||
Encryption is not yet implemented even in read-only mode.
|
||||
Writes to fusion drives will likely never be supported, though I might
|
||||
reconsider this if people actually need it. Encryption is not yet implemented
|
||||
even in read-only mode.
|
||||
|
||||
Reporting bugs
|
||||
==============
|
||||
|
||||
If you encounter any problem, the first thing you should do is run (as root)::
|
||||
|
||||
dmesg | grep -i apfs
|
||||
|
||||
to see all the error messages. If that doesn't help you, please file a github
|
||||
issue at https://github.com/eafer/linux-apfs-rw/issues. Or send me an email if
|
||||
you prefer.
|
||||
|
||||
Build
|
||||
=====
|
||||
|
||||
Note that at this point the driver has been packaged for several distros, so
|
||||
it may be more convenient to install it from there instead of building it
|
||||
yourself. The distro packages are often outdated though, so be sure to check
|
||||
the version.
|
||||
|
||||
In order to build a module out-of-tree, you will first need the Linux kernel
|
||||
headers. Depending on your distro, you can get them by running (as root)::
|
||||
|
||||
@@ -64,12 +77,14 @@ vol=n Volume number to mount. The default is volume 0.
|
||||
|
||||
snap=label Volume snapshot to mount (in read-only mode).
|
||||
|
||||
tier2=path Path to the tier 2 device. For fusion drives only.
|
||||
|
||||
uid=n, gid=n Override on-disk inode ownership data with given uid/gid.
|
||||
|
||||
cknodes Verify the checksum on all metadata nodes. Right now this has a
|
||||
severe performance cost, so it's not recommended.
|
||||
|
||||
readwrite Enable the experimental write support. This **will** corrupt your
|
||||
readwrite Enable the experimental write support. This may corrupt your
|
||||
container.
|
||||
============ =================================================================
|
||||
|
||||
|
||||
@@ -11,6 +11,21 @@
|
||||
#include <linux/list.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
/*
|
||||
* Redhat kernels regularly break the api, so they need their own version
|
||||
* checks. Make sure they always fail for non-rhel kernels.
|
||||
*
|
||||
* By the way, if rhel keeps picking up breaking patches for old majors after
|
||||
* a new one is out, this check could break down and things could get more
|
||||
* complicated. I don't think they do that though...
|
||||
*/
|
||||
#ifdef RHEL_RELEASE
|
||||
#define RHEL_VERSION_GE(a, b) (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(a, b))
|
||||
#else
|
||||
#define RHEL_VERSION_GE(a, b) 0
|
||||
#endif
|
||||
|
||||
#include "apfs_raw.h"
|
||||
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
@@ -29,10 +44,10 @@ static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags
|
||||
#endif
|
||||
|
||||
/* Compatibility wrapper around submit_bh() */
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
|
||||
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op, op_flags, bh)
|
||||
#else
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) || RHEL_VERSION_GE(9, 2)
|
||||
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op | op_flags, bh)
|
||||
#else
|
||||
#define apfs_submit_bh(op, op_flags, bh) submit_bh(op, op_flags, bh)
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -64,6 +79,7 @@ struct apfs_object {
|
||||
*/
|
||||
struct buffer_head *o_bh;
|
||||
char *data; /* The raw object */
|
||||
bool ephemeral; /* Is this an ephemeral object? */
|
||||
};
|
||||
|
||||
/* Constants used in managing the size of a node's table of contents */
|
||||
@@ -80,7 +96,7 @@ struct apfs_node {
|
||||
|
||||
int key; /* Offset of the key area in the block */
|
||||
int free; /* Offset of the free area in the block */
|
||||
int data; /* Offset of the data area in the block */
|
||||
int val; /* Offset of the value area in the block */
|
||||
|
||||
int key_free_list_len; /* Length of the fragmented free key space */
|
||||
int val_free_list_len; /* Length of the fragmented free value space */
|
||||
@@ -116,13 +132,18 @@ static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
|
||||
return (node->flags & APFS_BTNODE_FIXED_KV_SIZE) != 0;
|
||||
}
|
||||
|
||||
struct apfs_ip_bitmap_block_info {
|
||||
bool dirty; /* Do we need to commit this block to disk? */
|
||||
void *block; /* In-memory address of the bitmap block */
|
||||
};
|
||||
|
||||
/*
|
||||
* Space manager data in memory.
|
||||
*/
|
||||
struct apfs_spaceman {
|
||||
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
|
||||
struct buffer_head *sm_bh; /* Buffer head for @sm_raw */
|
||||
struct apfs_nxsb_info *sm_nxi; /* Container superblock */
|
||||
u32 sm_size; /* Size of @sm_raw in bytes */
|
||||
|
||||
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
|
||||
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
|
||||
@@ -131,6 +152,7 @@ struct apfs_spaceman {
|
||||
u32 sm_cib_count; /* Number of chunk-info blocks */
|
||||
u64 sm_free_count; /* Number of free blocks */
|
||||
u32 sm_addr_offset; /* Offset of cib addresses in @sm_raw */
|
||||
u64 sm_main_fq_nodes; /* Number of nodes in the main fq */
|
||||
|
||||
/*
|
||||
* A range of freed blocks not yet put in the free queue. Extend this as
|
||||
@@ -146,12 +168,12 @@ struct apfs_spaceman {
|
||||
/* Number of ip bitmaps */
|
||||
u32 sm_ip_bmaps_count;
|
||||
/* List of ip bitmaps, in order */
|
||||
struct buffer_head *sm_ip_bmaps[];
|
||||
struct apfs_ip_bitmap_block_info sm_ip_bmaps[];
|
||||
};
|
||||
|
||||
#define TRANSACTION_MAIN_QUEUE_MAX 4096
|
||||
#define TRANSACTION_BUFFERS_MAX 65536
|
||||
#define TRANSACTION_STARTS_MAX 65536
|
||||
#define APFS_TRANS_MAIN_QUEUE_MAX 10000
|
||||
#define APFS_TRANS_BUFFERS_MAX 65536
|
||||
#define APFS_TRANS_STARTS_MAX 65536
|
||||
|
||||
/* Possible states for the container transaction structure */
|
||||
#define APFS_NX_TRANS_FORCE_COMMIT 1 /* Commit guaranteed */
|
||||
@@ -159,29 +181,35 @@ struct apfs_spaceman {
|
||||
#define APFS_NX_TRANS_COMMITTING 4 /* Commit ongoing */
|
||||
#define APFS_NX_TRANS_INCOMPLETE_BLOCK 8 /* A data block is not written in full */
|
||||
|
||||
/*
|
||||
* ENOSPC checks before transactions are more or less coarse depending on this.
|
||||
*/
|
||||
enum apfs_trans_kind {
|
||||
APFS_TRANS_REG, /* Most transactions */
|
||||
APFS_TRANS_DEL, /* Transactions that usually free space */
|
||||
APFS_TRANS_SYNC, /* Sync (or unmount) transaction */
|
||||
};
|
||||
|
||||
/* The space requirements for each kind of transaction, in blocks */
|
||||
#define APFS_REG_ROOM 128
|
||||
#define APFS_DEL_ROOM 20
|
||||
#define APFS_SYNC_ROOM 6
|
||||
|
||||
/*
|
||||
* Structure that keeps track of a container transaction.
|
||||
*/
|
||||
struct apfs_nx_transaction {
|
||||
struct buffer_head *t_old_msb; /* Main superblock being replaced */
|
||||
unsigned int t_state;
|
||||
|
||||
struct delayed_work t_work; /* Work task for transaction commits */
|
||||
struct super_block *t_work_sb; /* sb that last queued a commit */
|
||||
|
||||
struct list_head t_inodes; /* List of inodes in the transaction */
|
||||
struct list_head t_buffers; /* List of buffers in the transaction */
|
||||
size_t t_buffers_count; /* Count of items on the list */
|
||||
int t_starts_count; /* Count of starts for transaction */
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure that keeps track of a volume transaction.
|
||||
*/
|
||||
struct apfs_vol_transaction {
|
||||
struct buffer_head *t_old_vsb; /* Volume superblock being replaced */
|
||||
|
||||
struct apfs_node t_old_omap_root; /* Omap root node being replaced */
|
||||
struct apfs_node t_old_cat_root; /* Catalog root node being replaced */
|
||||
};
|
||||
|
||||
/* State bits for buffer heads in a transaction */
|
||||
#define BH_TRANS BH_PrivateStart /* Attached to a transaction */
|
||||
#define BH_CSUM (BH_PrivateStart + 1) /* Requires checksum update */
|
||||
@@ -197,26 +225,71 @@ struct apfs_bh_info {
|
||||
};
|
||||
|
||||
/*
|
||||
* Used to report how many operations may be needed for a transaction
|
||||
* List entry for an in-memory ephemeral object
|
||||
*/
|
||||
struct apfs_max_ops {
|
||||
int cat; /* Maximum catalog records that may need changing */
|
||||
int blks; /* Maximum extent blocks that may need changing */
|
||||
struct apfs_ephemeral_object_info {
|
||||
u64 oid; /* Ephemeral object id */
|
||||
u32 size; /* Size of the object in bytes */
|
||||
void *object; /* In-memory address of the object */
|
||||
};
|
||||
|
||||
/*
|
||||
* We allocate a fixed space for the list of ephemeral objects. I don't
|
||||
* actually know how big this should be allowed to get, but all the objects
|
||||
* must be written down with each transaction commit, so probably not too big.
|
||||
*/
|
||||
#define APFS_EPHEMERAL_LIST_SIZE 32768
|
||||
#define APFS_EPHEMERAL_LIST_LIMIT (APFS_EPHEMERAL_LIST_SIZE / sizeof(struct apfs_ephemeral_object_info))
|
||||
|
||||
/* Mount option flags for a container */
|
||||
#define APFS_CHECK_NODES 1
|
||||
#define APFS_READWRITE 2
|
||||
/*
|
||||
* Mount options for a container are decided on its first mount. This flag lets
|
||||
* future mounts know that has happened already.
|
||||
*/
|
||||
#define APFS_FLAGS_SET 4
|
||||
|
||||
/*
|
||||
* Wrapper around block devices for portability.
|
||||
*/
|
||||
struct apfs_blkdev_info {
|
||||
struct block_device *blki_bdev;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0) || RHEL_VERSION_GE(9, 5)
|
||||
struct file *blki_bdev_file;
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
struct bdev_handle *blki_bdev_handle;
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0) && !RHEL_VERSION_GE(9, 4)
|
||||
fmode_t blki_mode;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* For tier 2, we need to remember the mount path for show_options().
|
||||
* We don't need this for the main device of course, but better to
|
||||
* keep things consistent.
|
||||
*/
|
||||
char *blki_path;
|
||||
};
|
||||
|
||||
/*
|
||||
* Container superblock data in memory
|
||||
*/
|
||||
struct apfs_nxsb_info {
|
||||
struct block_device *nx_bdev; /* Device for the container */
|
||||
/* Block device info for the container */
|
||||
struct apfs_blkdev_info *nx_blkdev_info;
|
||||
struct apfs_blkdev_info *nx_tier2_info;
|
||||
u64 nx_tier2_bno; /* Offset for tier 2 block numbers */
|
||||
|
||||
struct apfs_nx_superblock *nx_raw; /* On-disk main sb */
|
||||
struct apfs_object nx_object; /* Main superblock object */
|
||||
u64 nx_bno; /* Current block number for the checkpoint superblock */
|
||||
u64 nx_xid; /* Latest transaction id */
|
||||
|
||||
/* List of ephemeral objects in memory (except the superblock) */
|
||||
struct apfs_ephemeral_object_info *nx_eph_list;
|
||||
int nx_eph_count;
|
||||
|
||||
struct list_head vol_list; /* List of mounted volumes in container */
|
||||
|
||||
unsigned int nx_flags; /* Mount options shared by all volumes */
|
||||
@@ -228,6 +301,7 @@ struct apfs_nxsb_info {
|
||||
|
||||
struct apfs_spaceman *nx_spaceman;
|
||||
struct apfs_nx_transaction nx_transaction;
|
||||
int nx_trans_buffers_max;
|
||||
|
||||
/* For now, a single semaphore for every operation */
|
||||
struct rw_semaphore nx_big_sem;
|
||||
@@ -292,6 +366,8 @@ struct apfs_sb_info {
|
||||
|
||||
dev_t s_anon_dev; /* Anonymous device for this volume-snapshot */
|
||||
|
||||
char *s_tier2_path; /* Path to the tier 2 device */
|
||||
|
||||
char *s_snap_name; /* Label for the mounted snapshot */
|
||||
u64 s_snap_xid; /* Transaction id for mounted snapshot */
|
||||
|
||||
@@ -307,10 +383,9 @@ struct apfs_sb_info {
|
||||
|
||||
struct apfs_crypto_state_val *s_dflt_pfk; /* default per-file key */
|
||||
|
||||
struct apfs_vol_transaction s_transaction;
|
||||
int s_trans_buffers_max;
|
||||
|
||||
struct inode *s_private_dir; /* Inode for the private directory */
|
||||
struct work_struct s_orphan_cleanup_work;
|
||||
atomic_t s_orphan_cleanup_err; /* Error from last orphan cleanup */
|
||||
};
|
||||
|
||||
static inline struct apfs_sb_info *APFS_SB(struct super_block *sb)
|
||||
@@ -602,24 +677,24 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
|
||||
}
|
||||
|
||||
/* Flags for the query structure */
|
||||
#define APFS_QUERY_TREE_MASK 00177 /* Which b-tree we query */
|
||||
#define APFS_QUERY_OMAP 00001 /* This is a b-tree object map query */
|
||||
#define APFS_QUERY_CAT 00002 /* This is a catalog tree query */
|
||||
#define APFS_QUERY_FREE_QUEUE 00004 /* This is a free queue query */
|
||||
#define APFS_QUERY_EXTENTREF 00010 /* This is an extent reference query */
|
||||
#define APFS_QUERY_FEXT 00020 /* This is a fext tree query */
|
||||
#define APFS_QUERY_SNAP_META 00040 /* This is a snapshot meta query */
|
||||
#define APFS_QUERY_OMAP_SNAP 00100 /* This is an omap snapshots query */
|
||||
#define APFS_QUERY_NEXT 00200 /* Find next of multiple matches */
|
||||
#define APFS_QUERY_EXACT 00400 /* Search for an exact match */
|
||||
#define APFS_QUERY_DONE 01000 /* The search at this level is over */
|
||||
#define APFS_QUERY_ANY_NAME 02000 /* Multiple search for any name */
|
||||
#define APFS_QUERY_ANY_NUMBER 04000 /* Multiple search for any number */
|
||||
#define APFS_QUERY_TREE_MASK 000177 /* Which b-tree we query */
|
||||
#define APFS_QUERY_OMAP 000001 /* This is a b-tree object map query */
|
||||
#define APFS_QUERY_CAT 000002 /* This is a catalog tree query */
|
||||
#define APFS_QUERY_FREE_QUEUE 000004 /* This is a free queue query */
|
||||
#define APFS_QUERY_EXTENTREF 000010 /* This is an extent reference query */
|
||||
#define APFS_QUERY_FEXT 000020 /* This is a fext tree query */
|
||||
#define APFS_QUERY_SNAP_META 000040 /* This is a snapshot meta query */
|
||||
#define APFS_QUERY_OMAP_SNAP 000100 /* This is an omap snapshots query */
|
||||
#define APFS_QUERY_NEXT 000200 /* Find next of multiple matches */
|
||||
#define APFS_QUERY_EXACT 000400 /* Search for an exact match */
|
||||
#define APFS_QUERY_DONE 001000 /* The search at this level is over */
|
||||
#define APFS_QUERY_ANY_NAME 002000 /* Multiple search for any name */
|
||||
#define APFS_QUERY_ANY_NUMBER 004000 /* Multiple search for any number */
|
||||
#define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER)
|
||||
#define APFS_QUERY_PREV 010000 /* Find previous record */
|
||||
|
||||
/*
|
||||
* Structure used to retrieve data from an APFS B-Tree. For now only used
|
||||
* on the calalog and the object map.
|
||||
* Structure used to retrieve data from an APFS B-Tree.
|
||||
*/
|
||||
struct apfs_query {
|
||||
struct apfs_node *node; /* Node being searched */
|
||||
@@ -632,8 +707,8 @@ struct apfs_query {
|
||||
int index; /* Index of the entry in the node */
|
||||
int key_off; /* Offset of the key in the node */
|
||||
int key_len; /* Length of the key */
|
||||
int off; /* Offset of the data in the node */
|
||||
int len; /* Length of the data */
|
||||
int off; /* Offset of the value in the node */
|
||||
int len; /* Length of the value */
|
||||
|
||||
int depth; /* Put a limit on recursion */
|
||||
};
|
||||
@@ -731,6 +806,8 @@ struct apfs_inode_info {
|
||||
bool i_has_dstream; /* Is there a dstream record? */
|
||||
struct apfs_dstream_info i_dstream; /* Dstream data, if any */
|
||||
|
||||
bool i_cleaned; /* Orphan data already deleted */
|
||||
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
@@ -848,6 +925,7 @@ extern int apfs_btree_remove(struct apfs_query *query);
|
||||
extern void apfs_btree_change_node_count(struct apfs_query *query, int change);
|
||||
extern int apfs_btree_replace(struct apfs_query *query, void *key, int key_len,
|
||||
void *val, int val_len);
|
||||
extern void apfs_query_direct_forward(struct apfs_query *query);
|
||||
|
||||
/* compress.c */
|
||||
extern int apfs_compress_get_size(struct inode *inode, loff_t *size);
|
||||
@@ -894,7 +972,7 @@ extern int apfs_link(struct dentry *old_dentry, struct inode *dir,
|
||||
extern int apfs_unlink(struct inode *dir, struct dentry *dentry);
|
||||
extern int apfs_rmdir(struct inode *dir, struct dentry *dentry);
|
||||
extern int apfs_delete_orphan_link(struct inode *inode);
|
||||
extern int APFS_DELETE_ORPHAN_LINK_MAXOPS(void);
|
||||
extern u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p);
|
||||
|
||||
/* extents.c */
|
||||
extern int apfs_extent_from_query(struct apfs_query *query,
|
||||
@@ -908,8 +986,8 @@ extern int apfs_flush_extent_cache(struct apfs_dstream_info *dstream);
|
||||
extern int apfs_dstream_get_new_bno(struct apfs_dstream_info *dstream, u64 dsblock, u64 *bno);
|
||||
extern int apfs_get_new_block(struct inode *inode, sector_t iblock,
|
||||
struct buffer_head *bh_result, int create);
|
||||
extern int APFS_GET_NEW_BLOCK_MAXOPS(void);
|
||||
extern int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size);
|
||||
extern int apfs_inode_delete_front(struct inode *inode);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
||||
extern loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags);
|
||||
#else
|
||||
@@ -926,14 +1004,14 @@ extern int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
/* inode.c */
|
||||
extern struct inode *apfs_iget(struct super_block *sb, u64 cnid);
|
||||
extern int apfs_update_inode(struct inode *inode, char *new_name);
|
||||
extern int APFS_UPDATE_INODE_MAXOPS(void);
|
||||
extern void apfs_schedule_orphan_cleanup(struct super_block *sb);
|
||||
extern void apfs_orphan_cleanup_work(struct work_struct *work);
|
||||
extern void apfs_evict_inode(struct inode *inode);
|
||||
extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
|
||||
dev_t rdev);
|
||||
extern int apfs_create_inode_rec(struct super_block *sb, struct inode *inode,
|
||||
struct dentry *dentry);
|
||||
extern int apfs_inode_create_exclusive_dstream(struct inode *inode);
|
||||
extern int APFS_CREATE_INODE_REC_MAXOPS(void);
|
||||
extern int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata);
|
||||
extern int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int copied, struct page *page, void *fsdata);
|
||||
extern int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta);
|
||||
@@ -973,7 +1051,6 @@ extern int apfs_getattr(struct mnt_idmap *idmap,
|
||||
#endif
|
||||
|
||||
extern int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta);
|
||||
extern int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void);
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
|
||||
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
|
||||
@@ -1017,12 +1094,12 @@ extern int apfs_make_empty_btree_root(struct super_block *sb, u32 subtype, u64 *
|
||||
|
||||
/* object.c */
|
||||
extern int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh);
|
||||
extern void apfs_obj_set_csum(struct super_block *sb,
|
||||
struct apfs_obj_phys *obj);
|
||||
extern int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno);
|
||||
extern int apfs_remove_cpoint_map(struct super_block *sb, u64 bno);
|
||||
extern struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb,
|
||||
u64 oid);
|
||||
extern void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj);
|
||||
extern int apfs_multiblock_verify_csum(char *object, u32 size);
|
||||
extern void apfs_multiblock_set_csum(char *object, u32 size);
|
||||
extern int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p);
|
||||
extern int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size);
|
||||
extern struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid);
|
||||
extern struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve);
|
||||
extern u32 apfs_index_in_data_area(struct super_block *sb, u64 bno);
|
||||
extern u64 apfs_data_index_to_bno(struct super_block *sb, u32 index);
|
||||
@@ -1036,6 +1113,8 @@ extern int apfs_read_spaceman(struct super_block *sb);
|
||||
extern int apfs_free_queue_insert_nocache(struct super_block *sb, u64 bno, u64 count);
|
||||
extern int apfs_free_queue_insert(struct super_block *sb, u64 bno, u64 count);
|
||||
extern int apfs_spaceman_allocate_block(struct super_block *sb, u64 *bno, bool backwards);
|
||||
extern int apfs_write_ip_bitmaps(struct super_block *sb);
|
||||
extern int apfs_spaceman_get_free_blkcnt(struct super_block *sb, u64 *blkcnt);
|
||||
|
||||
/* super.c */
|
||||
extern int apfs_map_volume_super_bno(struct super_block *sb, u64 bno, bool check);
|
||||
@@ -1046,15 +1125,16 @@ extern int apfs_read_catalog(struct super_block *sb, bool write);
|
||||
extern int apfs_sync_fs(struct super_block *sb, int wait);
|
||||
|
||||
/* transaction.c */
|
||||
extern void apfs_cpoint_data_allocate(struct super_block *sb, u64 *bno);
|
||||
extern int apfs_cpoint_data_free(struct super_block *sb, u64 bno);
|
||||
extern int apfs_transaction_start(struct super_block *sb, struct apfs_max_ops maxops);
|
||||
extern void apfs_transaction_init(struct apfs_nx_transaction *trans);
|
||||
extern int apfs_transaction_start(struct super_block *sb, enum apfs_trans_kind kind);
|
||||
extern int apfs_transaction_commit(struct super_block *sb);
|
||||
extern void apfs_inode_join_transaction(struct super_block *sb, struct inode *inode);
|
||||
extern int apfs_transaction_join(struct super_block *sb,
|
||||
struct buffer_head *bh);
|
||||
void apfs_transaction_abort(struct super_block *sb);
|
||||
extern int apfs_transaction_flush_all_inodes(struct super_block *sb);
|
||||
extern int apfs_read_ephemeral_objects(struct super_block *sb);
|
||||
|
||||
/* xattr.c */
|
||||
extern int ____apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
|
||||
@@ -1064,7 +1144,6 @@ extern int __apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
|
||||
extern int apfs_delete_all_xattrs(struct inode *inode);
|
||||
extern int apfs_xattr_set(struct inode *inode, const char *name, const void *value,
|
||||
size_t size, int flags);
|
||||
extern int APFS_XATTR_SET_MAXOPS(void);
|
||||
extern ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
|
||||
extern int apfs_xattr_get_compressed_data(struct inode *inode, const char *name, struct apfs_compressed_data *cdata);
|
||||
extern void apfs_release_compressed_data(struct apfs_compressed_data *cdata);
|
||||
@@ -1124,8 +1203,29 @@ static inline void apfs_assert_query_is_valid(const struct apfs_query *query)
|
||||
static inline void
|
||||
apfs_map_bh(struct buffer_head *bh, struct super_block *sb, sector_t block)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = NULL;
|
||||
struct apfs_blkdev_info *info = NULL;
|
||||
|
||||
nxi = APFS_NXI(sb);
|
||||
info = nxi->nx_blkdev_info;
|
||||
|
||||
/*
|
||||
* Block numbers above s_tier2_bno are for the tier 2 device of a
|
||||
* fusion drive. Don't bother returning an error code if a regular
|
||||
* drive gets corrupted and reports a tier 2 block number: just treat
|
||||
* it the same as any other out-of-range block.
|
||||
*/
|
||||
if (block >= nxi->nx_tier2_bno) {
|
||||
if (nxi->nx_tier2_info) {
|
||||
info = nxi->nx_tier2_info;
|
||||
block -= nxi->nx_tier2_bno;
|
||||
} else {
|
||||
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
|
||||
}
|
||||
}
|
||||
|
||||
set_buffer_mapped(bh);
|
||||
bh->b_bdev = APFS_NXI(sb)->nx_bdev;
|
||||
bh->b_bdev = info->blki_bdev;
|
||||
bh->b_blocknr = block;
|
||||
bh->b_size = sb->s_blocksize;
|
||||
}
|
||||
@@ -1133,20 +1233,59 @@ apfs_map_bh(struct buffer_head *bh, struct super_block *sb, sector_t block)
|
||||
static inline struct buffer_head *
|
||||
apfs_sb_bread(struct super_block *sb, sector_t block)
|
||||
{
|
||||
return __bread_gfp(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
struct apfs_nxsb_info *nxi = NULL;
|
||||
struct apfs_blkdev_info *info = NULL;
|
||||
|
||||
nxi = APFS_NXI(sb);
|
||||
info = nxi->nx_blkdev_info;
|
||||
|
||||
if (block >= nxi->nx_tier2_bno) {
|
||||
if (!nxi->nx_tier2_info) {
|
||||
/* Not really a fusion drive, so it's corrupted */
|
||||
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
|
||||
return NULL;
|
||||
}
|
||||
info = nxi->nx_tier2_info;
|
||||
block -= nxi->nx_tier2_bno;
|
||||
}
|
||||
|
||||
return __bread_gfp(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
}
|
||||
|
||||
/* Like apfs_getblk(), but doesn't mark the buffer uptodate */
|
||||
static inline struct buffer_head *
|
||||
__apfs_getblk(struct super_block *sb, sector_t block)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = NULL;
|
||||
struct apfs_blkdev_info *info = NULL;
|
||||
|
||||
nxi = APFS_NXI(sb);
|
||||
info = nxi->nx_blkdev_info;
|
||||
|
||||
if (block >= nxi->nx_tier2_bno) {
|
||||
if (!nxi->nx_tier2_info) {
|
||||
/* Not really a fusion drive, so it's corrupted */
|
||||
apfs_err(sb, "block number in tier 2 range (0x%llx)", (unsigned long long)block);
|
||||
return NULL;
|
||||
}
|
||||
info = nxi->nx_tier2_info;
|
||||
block -= nxi->nx_tier2_bno;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
|
||||
return __getblk_gfp(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#else
|
||||
return bdev_getblk(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Use instead of apfs_sb_bread() for blocks that will just be overwritten */
|
||||
static inline struct buffer_head *
|
||||
apfs_getblk(struct super_block *sb, sector_t block)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct buffer_head *bh = NULL;
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
|
||||
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#else
|
||||
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#endif
|
||||
bh = __apfs_getblk(sb, block);
|
||||
if (bh)
|
||||
set_buffer_uptodate(bh);
|
||||
return bh;
|
||||
|
||||
+56
-12
@@ -268,6 +268,10 @@ struct apfs_phys_ext_val {
|
||||
#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
|
||||
#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
|
||||
#define APFS_FILE_EXTENT_FLAG_SHIFT 56
|
||||
#define APFS_FILE_EXTENT_CRYPTO_FLAG 0x01 /* Made-up name */
|
||||
#define APFS_FILE_EXTENT_PREALLOCATED 0x02 /* Made-up name */
|
||||
#define APFS_VALID_FILE_EXTENT_FLAGS (APFS_FILE_EXTENT_CRYPTO_FLAG \
|
||||
| APFS_FILE_EXTENT_PREALLOCATED)
|
||||
|
||||
/*
|
||||
* Put a bound on maximum file size so that a growing truncation will always
|
||||
@@ -1051,13 +1055,19 @@ struct apfs_checkpoint_map_phys {
|
||||
#define APFS_FS_SPILLEDOVER 0x00000010LL
|
||||
#define APFS_FS_RUN_SPILLOVER_CLEANER 0x00000020LL
|
||||
#define APFS_FS_ALWAYS_CHECK_EXTENTREF 0x00000040LL
|
||||
#define APFS_FS_PREVIOUSLY_SEALED 0x00000080LL /* Made-up name */
|
||||
#define APFS_FS_PFK 0x00000100LL /* Made-up name */
|
||||
#define APFS_FS_UNKNOWN_200 0x00000200LL
|
||||
#define APFS_FS_FLAGS_VALID_MASK (APFS_FS_UNENCRYPTED \
|
||||
| APFS_FS_EFFACEABLE \
|
||||
| APFS_FS_RESERVED_4 \
|
||||
| APFS_FS_ONEKEY \
|
||||
| APFS_FS_SPILLEDOVER \
|
||||
| APFS_FS_RUN_SPILLOVER_CLEANER \
|
||||
| APFS_FS_ALWAYS_CHECK_EXTENTREF)
|
||||
| APFS_FS_EFFACEABLE \
|
||||
| APFS_FS_RESERVED_4 \
|
||||
| APFS_FS_ONEKEY \
|
||||
| APFS_FS_SPILLEDOVER \
|
||||
| APFS_FS_RUN_SPILLOVER_CLEANER \
|
||||
| APFS_FS_ALWAYS_CHECK_EXTENTREF \
|
||||
| APFS_FS_PREVIOUSLY_SEALED \
|
||||
| APFS_FS_PFK \
|
||||
| APFS_FS_UNKNOWN_200)
|
||||
|
||||
#define APFS_FS_CRYPTOFLAGS (APFS_FS_UNENCRYPTED \
|
||||
| APFS_FS_EFFACEABLE \
|
||||
@@ -1124,7 +1134,9 @@ struct apfs_checkpoint_map_phys {
|
||||
#define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE 0x00000008LL
|
||||
#define APFS_INCOMPAT_INCOMPLETE_RESTORE 0x00000010LL
|
||||
#define APFS_INCOMPAT_SEALED_VOLUME 0x00000020LL
|
||||
#define APFS_INCOMPAT_RESERVED_40 0x00000040LL
|
||||
#define APFS_INCOMPAT_PFK 0x00000040LL /* Made-up name */
|
||||
#define APFS_INCOMPAT_EXTENT_PREALLOC_FLAG 0x00000080LL /* Made-up name */
|
||||
#define APFS_INCOMPAT_SECONDARY_FSROOT 0x00000100LL /* Made-up name */
|
||||
|
||||
#define APFS_SUPPORTED_INCOMPAT_MASK (APFS_INCOMPAT_CASE_INSENSITIVE \
|
||||
| APFS_INCOMPAT_DATALESS_SNAPS \
|
||||
@@ -1132,7 +1144,9 @@ struct apfs_checkpoint_map_phys {
|
||||
| APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \
|
||||
| APFS_INCOMPAT_INCOMPLETE_RESTORE \
|
||||
| APFS_INCOMPAT_SEALED_VOLUME \
|
||||
| APFS_INCOMPAT_RESERVED_40)
|
||||
| APFS_INCOMPAT_PFK \
|
||||
| APFS_INCOMPAT_EXTENT_PREALLOC_FLAG \
|
||||
| APFS_INCOMPAT_SECONDARY_FSROOT)
|
||||
|
||||
#define APFS_MODIFIED_NAMELEN 32
|
||||
|
||||
@@ -1162,6 +1176,18 @@ struct apfs_modified_by {
|
||||
#define APFS_CRYPTO_RESERVED_5 5
|
||||
#define APFS_UNASSIGNED_CRYPTO_ID (~0ULL)
|
||||
|
||||
/* Doc id index flags. I'm making up the names for now. */
|
||||
#define APFS_DOC_ID_HAS_PREV_TREE 0x00000001
|
||||
#define APFS_DOC_ID_UNKNOWN_02 0x00000002
|
||||
#define APFS_DOC_ID_UNKNOWN_04 0x00000004
|
||||
#define APFS_DOC_ID_UNKNOWN_08 0x00000008
|
||||
#define APFS_DOC_ID_UNKNOWN_10 0x00000010
|
||||
#define APFS_DOC_ID_VALID_FLAGS (APFS_DOC_ID_HAS_PREV_TREE \
|
||||
| APFS_DOC_ID_UNKNOWN_02 \
|
||||
| APFS_DOC_ID_UNKNOWN_04 \
|
||||
| APFS_DOC_ID_UNKNOWN_08 \
|
||||
| APFS_DOC_ID_UNKNOWN_10)
|
||||
|
||||
/*
|
||||
* Structure used to store the encryption state
|
||||
*/
|
||||
@@ -1241,15 +1267,24 @@ struct apfs_superblock {
|
||||
|
||||
__le64 apfs_snap_meta_ext_oid;
|
||||
|
||||
char apfs_volume_group_id[UUID_SIZE];
|
||||
/*3F0*/ char apfs_volume_group_id[UUID_SIZE];
|
||||
|
||||
__le64 apfs_integrity_meta_oid;
|
||||
/*400*/ __le64 apfs_integrity_meta_oid;
|
||||
|
||||
__le64 apfs_fext_tree_oid;
|
||||
__le32 apfs_fext_tree_type;
|
||||
/*410*/ __le32 apfs_fext_tree_type;
|
||||
|
||||
__le32 reserved_type;
|
||||
__le64 reserved_oid;
|
||||
|
||||
/*420*/ __le64 apfs_doc_id_index_xid;
|
||||
__le32 apfs_doc_id_index_flags;
|
||||
__le32 apfs_doc_id_tree_type;
|
||||
/*430*/ __le64 apfs_doc_id_tree_oid; /* Made-up name */
|
||||
__le64 apfs_prev_doc_id_tree_oid;
|
||||
__le64 apfs_doc_id_fixup_cursor;
|
||||
__le64 apfs_sec_root_tree_oid;
|
||||
/*450*/ __le32 apfs_sec_root_tree_type;
|
||||
} __packed;
|
||||
|
||||
/* Extended attributes constants */
|
||||
@@ -1268,9 +1303,10 @@ enum {
|
||||
APFS_XATTR_DATA_EMBEDDED = 0x00000002,
|
||||
APFS_XATTR_FILE_SYSTEM_OWNED = 0x00000004,
|
||||
APFS_XATTR_RESERVED_8 = 0x00000008,
|
||||
APFS_XATTR_UNKNOWN_10 = 0x00000010,
|
||||
};
|
||||
|
||||
#define APFS_XATTR_VALID_FLAGS 0x0000000f
|
||||
#define APFS_XATTR_VALID_FLAGS 0x0000001f
|
||||
|
||||
/*
|
||||
* Structure of the value of an extended attributes record
|
||||
@@ -1438,6 +1474,8 @@ struct apfs_compress_rsrc_data {
|
||||
} __packed block[];
|
||||
} __packed;
|
||||
|
||||
#define APFS_FUSION_TIER2_DEVICE_BYTE_ADDR 0x4000000000000000ULL
|
||||
|
||||
/*
|
||||
* TODO: this is a placeholder, create some snapshots with the official
|
||||
* implementation to find the actual limit.
|
||||
@@ -1459,6 +1497,12 @@ struct apfs_snap_metadata_val {
|
||||
u8 name[];
|
||||
} __packed;
|
||||
|
||||
/* Snapshot metadata record flags */
|
||||
enum {
|
||||
APFS_SNAP_META_PENDING_DATALESS = 0x00000001,
|
||||
APFS_SNAP_MERGE_IN_PROGRESS = 0x00000002,
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure of the value of a snapshot name record
|
||||
*/
|
||||
|
||||
@@ -50,7 +50,7 @@ static int apfs_child_from_query(struct apfs_query *query, u64 *child)
|
||||
index_val = (struct apfs_btn_index_node_val *)(raw + query->off);
|
||||
*child = le64_to_cpu(index_val->binv_child_oid) + apfs_catalog_base_oid(query);
|
||||
} else {
|
||||
if (query->len != 8) { /* The data on a nonleaf node is the child id */
|
||||
if (query->len != 8) { /* The value on a nonleaf node is the child id */
|
||||
apfs_err(sb, "bad index value length (%d)", query->len);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
@@ -398,8 +398,6 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||
|
||||
/* To be released by free_query. */
|
||||
query->node = node;
|
||||
/* Start the search with the last record and go backwards */
|
||||
query->index = node->records;
|
||||
|
||||
if (parent) {
|
||||
query->key = parent->key;
|
||||
@@ -408,6 +406,15 @@ struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||
query->depth = parent->depth + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* We start the search with the last record and go backwards, but
|
||||
* some queries later use the PREV flag later to list them in order.
|
||||
*/
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
query->index = -1;
|
||||
else
|
||||
query->index = node->records;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -514,7 +521,8 @@ next_node:
|
||||
* than enough to map every block.
|
||||
*/
|
||||
apfs_err(sb, "btree is too high");
|
||||
return -EFSCORRUPTED;
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = apfs_node_query(sb, *query);
|
||||
@@ -526,12 +534,16 @@ next_node:
|
||||
err = apfs_query_set_before_first(sb, query);
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to set before the first record");
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
return -ENODATA;
|
||||
err = -ENODATA;
|
||||
goto fail;
|
||||
} else if (err == -EAGAIN) {
|
||||
if (!(*query)->parent) /* We are at the root of the tree */
|
||||
return -ENODATA;
|
||||
if (!(*query)->parent) {
|
||||
/* We are at the root of the tree */
|
||||
err = -ENODATA;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Move back up one level and continue the query */
|
||||
parent = (*query)->parent;
|
||||
@@ -540,7 +552,7 @@ next_node:
|
||||
*query = parent;
|
||||
goto next_node;
|
||||
} else if (err) {
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
if (apfs_node_is_leaf((*query)->node)) /* All done */
|
||||
return 0;
|
||||
@@ -549,14 +561,15 @@ next_node:
|
||||
if (err) {
|
||||
apfs_alert(sb, "bad index block: 0x%llx",
|
||||
(*query)->node->object.block_nr);
|
||||
return err;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Now go a level deeper and search the child */
|
||||
node = apfs_read_node(sb, child_id, storage, false /* write */);
|
||||
if (IS_ERR(node)) {
|
||||
apfs_err(sb, "failed to read node 0x%llx", child_id);
|
||||
return PTR_ERR(node);
|
||||
err = PTR_ERR(node);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (node->object.oid != child_id)
|
||||
@@ -571,10 +584,16 @@ next_node:
|
||||
if (!*query) {
|
||||
apfs_node_free(node);
|
||||
*query = parent;
|
||||
return -ENOMEM;
|
||||
err = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
node = NULL;
|
||||
goto next_node;
|
||||
|
||||
fail:
|
||||
/* Don't leave stale record info here or some callers will use it */
|
||||
(*query)->key_len = (*query)->len = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
||||
@@ -589,11 +608,22 @@ int apfs_query_join_transaction(struct apfs_query *query)
|
||||
struct super_block *sb = node->object.sb;
|
||||
u64 oid = node->object.oid;
|
||||
u32 storage = apfs_query_storage(query);
|
||||
struct apfs_obj_phys *raw = NULL;
|
||||
|
||||
/*
|
||||
* Ephemeral objects are checkpoint data, and all of their xids get
|
||||
* updated on commit. There is no real need to do it here as well, but
|
||||
* it's better for consistency with the other object types.
|
||||
*/
|
||||
if (storage == APFS_OBJ_EPHEMERAL) {
|
||||
ASSERT(node->object.ephemeral);
|
||||
raw = (void *)node->object.data;
|
||||
raw->o_xid = cpu_to_le64(APFS_NXI(sb)->nx_xid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (buffer_trans(node->object.o_bh)) /* Already in the transaction */
|
||||
return 0;
|
||||
/* Ephemeral objects are always checkpoint data */
|
||||
ASSERT(storage != APFS_OBJ_EPHEMERAL);
|
||||
/* Root nodes should join the transaction before the query is created */
|
||||
ASSERT(!apfs_node_is_root(node));
|
||||
|
||||
@@ -1121,3 +1151,24 @@ int apfs_btree_replace(struct apfs_query *query, void *key, int key_len, void *v
|
||||
apfs_btree_change_rec_count(query, 0 /* change */, key_len, val_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_query_direct_forward - Set a query to start listing records forwards
|
||||
* @query: a successfully executed query
|
||||
*
|
||||
* Multiple queries list records backwards, but queries marked with this
|
||||
* function after execution will go in the opposite direction.
|
||||
*/
|
||||
void apfs_query_direct_forward(struct apfs_query *query)
|
||||
{
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
return;
|
||||
|
||||
apfs_assert_query_is_valid(query);
|
||||
ASSERT(apfs_node_is_leaf(query->node));
|
||||
|
||||
while (query) {
|
||||
query->flags |= APFS_QUERY_PREV;
|
||||
query = query->parent;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -409,7 +409,7 @@ static ssize_t apfs_compress_file_read_page(struct file *filp, char *buf, loff_t
|
||||
return step;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
|
||||
{
|
||||
struct page *page = &folio->page;
|
||||
@@ -417,16 +417,16 @@ static int apfs_compress_read_folio(struct file *filp, struct folio *folio)
|
||||
static int apfs_compress_readpage(struct file *filp, struct page *page)
|
||||
{
|
||||
#endif
|
||||
void *addr = NULL;
|
||||
char *addr = NULL;
|
||||
ssize_t ret;
|
||||
loff_t off;
|
||||
|
||||
/* Mostly copied from ext4_read_inline_page() */
|
||||
off = page->index << PAGE_SHIFT;
|
||||
addr = kmap_atomic(page);
|
||||
addr = kmap(page);
|
||||
ret = apfs_compress_file_read_page(filp, addr, off);
|
||||
flush_dcache_page(page);
|
||||
kunmap_atomic(addr);
|
||||
kunmap(page);
|
||||
if (ret >= 0) {
|
||||
zero_user_segment(page, ret, PAGE_SIZE);
|
||||
SetPageUptodate(page);
|
||||
@@ -438,7 +438,7 @@ static int apfs_compress_readpage(struct file *filp, struct page *page)
|
||||
}
|
||||
|
||||
const struct address_space_operations apfs_compress_aops = {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
.read_folio = apfs_compress_read_folio,
|
||||
#else
|
||||
.readpage = apfs_compress_readpage,
|
||||
|
||||
@@ -414,7 +414,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CREATE_DENTRY_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_build_sibling_val - Allocate and initialize a sibling link's value
|
||||
@@ -491,7 +490,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CREATE_SIBLING_LINK_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_create_sibling_map_rec - Create a sibling map record for a dentry
|
||||
@@ -534,7 +532,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CREATE_SIBLING_MAP_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_create_sibling_recs - Create sibling link and map records for a dentry
|
||||
@@ -567,8 +564,6 @@ static int apfs_create_sibling_recs(struct dentry *dentry,
|
||||
*sibling_id = cnid;
|
||||
return 0;
|
||||
}
|
||||
#define APFS_CREATE_SIBLING_RECS_MAXOPS (APFS_CREATE_SIBLING_LINK_REC_MAXOPS + \
|
||||
APFS_CREATE_SIBLING_MAP_REC_MAXOPS)
|
||||
|
||||
/**
|
||||
* apfs_create_dentry - Create all records for a new dentry
|
||||
@@ -613,9 +608,6 @@ static int apfs_create_dentry(struct dentry *dentry, struct inode *inode)
|
||||
apfs_inode_join_transaction(parent->i_sb, parent);
|
||||
return 0;
|
||||
}
|
||||
#define APFS_CREATE_DENTRY_MAXOPS (APFS_CREATE_SIBLING_RECS_MAXOPS + \
|
||||
APFS_CREATE_DENTRY_REC_MAXOPS + \
|
||||
APFS_UPDATE_INODE_MAXOPS())
|
||||
|
||||
/**
|
||||
* apfs_undo_create_dentry - Clean up apfs_create_dentry()
|
||||
@@ -643,15 +635,9 @@ int apfs_mkany(struct inode *dir, struct dentry *dentry, umode_t mode,
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct inode *inode;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
maxops.cat = APFS_CREATE_INODE_REC_MAXOPS() + APFS_CREATE_DENTRY_MAXOPS;
|
||||
if (symname)
|
||||
maxops.cat += APFS_XATTR_SET_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -818,8 +804,6 @@ static int apfs_prepare_dentry_for_link(struct dentry *dentry)
|
||||
return apfs_create_dentry_rec(d_inode(dentry), &dentry->d_name,
|
||||
apfs_ino(parent), sibling_id);
|
||||
}
|
||||
#define APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS (1 + APFS_CREATE_SIBLING_RECS_MAXOPS + \
|
||||
APFS_CREATE_DENTRY_REC_MAXOPS)
|
||||
|
||||
/**
|
||||
* __apfs_undo_link - Clean up __apfs_link()
|
||||
@@ -875,22 +859,15 @@ fail:
|
||||
drop_nlink(inode);
|
||||
return err;
|
||||
}
|
||||
#define __APFS_LINK_MAXOPS (APFS_UPDATE_INODE_MAXOPS() + \
|
||||
APFS_PREPARE_DENTRY_FOR_LINK_MAXOPS + \
|
||||
APFS_CREATE_DENTRY_MAXOPS)
|
||||
|
||||
int apfs_link(struct dentry *old_dentry, struct inode *dir,
|
||||
struct dentry *dentry)
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct inode *inode = d_inode(old_dentry);
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
maxops.cat = __APFS_LINK_MAXOPS;
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -954,7 +931,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_DELETE_SIBLING_LINK_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_delete_sibling_map_rec - Delete the sibling map record for a dentry
|
||||
@@ -995,7 +971,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_DELETE_SIBLING_MAP_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_delete_dentry - Delete all records for a dentry
|
||||
@@ -1048,9 +1023,6 @@ static int apfs_delete_dentry(struct dentry *dentry)
|
||||
apfs_inode_join_transaction(sb, parent);
|
||||
return err;
|
||||
}
|
||||
#define APFS_DELETE_DENTRY_MAXOPS (1 + APFS_DELETE_SIBLING_LINK_REC_MAXOPS + \
|
||||
APFS_DELETE_SIBLING_MAP_REC_MAXOPS + \
|
||||
APFS_UPDATE_INODE_MAXOPS())
|
||||
|
||||
/**
|
||||
* apfs_undo_delete_dentry - Clean up apfs_delete_dentry()
|
||||
@@ -1158,13 +1130,13 @@ fail:
|
||||
|
||||
/**
|
||||
* apfs_orphan_name - Get the name for an orphan inode's invisible link
|
||||
* @inode: the vfs inode
|
||||
* @qname: on return, the name assigned to the link
|
||||
* @ino: the inode number
|
||||
* @qname: on return, the name assigned to the link
|
||||
*
|
||||
* Returns 0 on success; the caller must remember to free @qname->name after
|
||||
* use. Returns a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
|
||||
static int apfs_orphan_name(u64 ino, struct qstr *qname)
|
||||
{
|
||||
int max_len;
|
||||
char *name;
|
||||
@@ -1174,7 +1146,7 @@ static int apfs_orphan_name(struct inode *inode, struct qstr *qname)
|
||||
name = kmalloc(max_len, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
qname->len = snprintf(name, max_len, "0x%llx-dead", apfs_ino(inode));
|
||||
qname->len = snprintf(name, max_len, "0x%llx-dead", ino);
|
||||
qname->name = name;
|
||||
return 0;
|
||||
}
|
||||
@@ -1193,7 +1165,7 @@ static int apfs_create_orphan_link(struct inode *inode)
|
||||
struct qstr qname;
|
||||
int err = 0;
|
||||
|
||||
err = apfs_orphan_name(inode, &qname);
|
||||
err = apfs_orphan_name(apfs_ino(inode), &qname);
|
||||
if (err)
|
||||
return err;
|
||||
err = apfs_create_dentry_rec(inode, &qname, apfs_ino(priv_dir), 0 /* sibling_id */);
|
||||
@@ -1217,8 +1189,6 @@ fail:
|
||||
kfree(qname.name);
|
||||
return err;
|
||||
}
|
||||
#define APFS_CREATE_ORPHAN_LINK_MAXOPS (APFS_CREATE_DENTRY_REC_MAXOPS + \
|
||||
APFS_UPDATE_INODE_MAXOPS())
|
||||
|
||||
/**
|
||||
* apfs_delete_orphan_link - Delete the link for an orphan inode
|
||||
@@ -1236,7 +1206,7 @@ int apfs_delete_orphan_link(struct inode *inode)
|
||||
struct apfs_drec drec;
|
||||
int err;
|
||||
|
||||
err = apfs_orphan_name(inode, &qname);
|
||||
err = apfs_orphan_name(apfs_ino(inode), &qname);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -1269,10 +1239,6 @@ fail:
|
||||
kfree(qname.name);
|
||||
return err;
|
||||
}
|
||||
int APFS_DELETE_ORPHAN_LINK_MAXOPS(void)
|
||||
{
|
||||
return 1 + APFS_UPDATE_INODE_MAXOPS();
|
||||
}
|
||||
|
||||
/**
|
||||
* __apfs_undo_unlink - Clean up __apfs_unlink()
|
||||
@@ -1368,20 +1334,13 @@ fail:
|
||||
__apfs_undo_unlink(dentry);
|
||||
return err;
|
||||
}
|
||||
#define __APFS_UNLINK_MAXOPS (APFS_DELETE_DENTRY_MAXOPS + \
|
||||
APFS_CREATE_ORPHAN_LINK_MAXOPS + \
|
||||
APFS_UPDATE_INODE_MAXOPS())
|
||||
|
||||
int apfs_unlink(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
struct super_block *sb = dir->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
maxops.cat = __APFS_UNLINK_MAXOPS;
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_DEL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -1427,7 +1386,6 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
struct super_block *sb = old_dir->i_sb;
|
||||
struct inode *old_inode = d_inode(old_dentry);
|
||||
struct inode *new_inode = d_inode(new_dentry);
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
if (new_inode && APFS_I(new_inode)->i_nchildren)
|
||||
@@ -1436,12 +1394,7 @@ int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
if (flags & ~RENAME_NOREPLACE) /* TODO: support RENAME_EXCHANGE */
|
||||
return -EINVAL;
|
||||
|
||||
maxops.cat = __APFS_UNLINK_MAXOPS + __APFS_LINK_MAXOPS;
|
||||
if (new_inode)
|
||||
maxops.cat += __APFS_UNLINK_MAXOPS;
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -1481,3 +1434,64 @@ out_abort:
|
||||
apfs_transaction_abort(sb);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_any_orphan_ino - Find the inode number for any orphaned regular file
|
||||
* @sb: filesytem superblock
|
||||
* @ino_p: on return, the inode number found
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure, which may
|
||||
* be -ENODATA if there are no orphan files.
|
||||
*/
|
||||
u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_drec drec = {0};
|
||||
struct qstr qname = {0};
|
||||
bool hashed = apfs_is_normalization_insensitive(sb);
|
||||
bool found = false;
|
||||
int err;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
return -ENOMEM;
|
||||
apfs_init_drec_key(sb, APFS_PRIV_DIR_INO_NUM, NULL /* name */, 0 /* name_len */, &query->key);
|
||||
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
|
||||
|
||||
while (!found) {
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err) {
|
||||
if (err == -ENODATA)
|
||||
goto out;
|
||||
apfs_err(sb, "drec query failed for private dir");
|
||||
goto out;
|
||||
}
|
||||
err = apfs_drec_from_query(query, &drec, hashed);
|
||||
if (err) {
|
||||
apfs_alert(sb, "bad dentry record in private dir");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* These files are deleted immediately by ->evict_inode() */
|
||||
if (drec.type != DT_REG)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Confirm that this is an orphan file, because the official
|
||||
* reference allows other uses for the private directory.
|
||||
*/
|
||||
err = apfs_orphan_name(drec.ino, &qname);
|
||||
if (err)
|
||||
goto out;
|
||||
found = strcmp(drec.name, qname.name) == 0;
|
||||
kfree(qname.name);
|
||||
qname.name = NULL;
|
||||
}
|
||||
*ino_p = drec.ino;
|
||||
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
query = NULL;
|
||||
return err;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.6"
|
||||
PACKAGE_VERSION="0.3.13"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
AUTOINSTALL="yes"
|
||||
PRE_BUILD="genver.sh"
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <linux/blk_types.h>
|
||||
#include "apfs.h"
|
||||
|
||||
#define MAX(X, Y) ((X) <= (Y) ? (Y) : (X))
|
||||
|
||||
/**
|
||||
* apfs_ext_is_hole - Does this extent represent a hole in a sparse file?
|
||||
* @extent: the extent to check
|
||||
@@ -387,7 +385,7 @@ static int apfs_shrink_extent_tail(struct apfs_query *query, struct apfs_dstream
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_query_found_extent - Is this query pointing to an extent record?
|
||||
* apfs_query_found_extent - Did this query find an extent with the right id?
|
||||
* @query: the (successful) query that found the record
|
||||
*/
|
||||
static inline bool apfs_query_found_extent(struct apfs_query *query)
|
||||
@@ -398,7 +396,12 @@ static inline bool apfs_query_found_extent(struct apfs_query *query)
|
||||
if (query->key_len < sizeof(*hdr))
|
||||
return false;
|
||||
hdr = raw + query->key_off;
|
||||
return apfs_cat_type(hdr) == APFS_TYPE_FILE_EXTENT;
|
||||
|
||||
if (apfs_cat_type(hdr) != APFS_TYPE_FILE_EXTENT)
|
||||
return false;
|
||||
if (apfs_cat_cnid(hdr) != query->key.id)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,7 +758,6 @@ static int apfs_update_extent(struct apfs_dstream_info *dstream, const struct ap
|
||||
}
|
||||
return apfs_update_mid_extent(dstream, extent);
|
||||
}
|
||||
#define APFS_UPDATE_EXTENTS_MAXOPS (1 + 2 * APFS_CRYPTO_ADJ_REFCNT_MAXOPS())
|
||||
|
||||
static int apfs_extend_phys_extent(struct apfs_query *query, u64 bno, u64 blkcnt, u64 dstream_id)
|
||||
{
|
||||
@@ -1133,7 +1135,6 @@ int apfs_flush_extent_cache(struct apfs_dstream_info *dstream)
|
||||
dstream->ds_ext_dirty = false;
|
||||
return 0;
|
||||
}
|
||||
#define APFS_FLUSH_EXTENT_CACHE APFS_UPDATE_EXTENTS_MAXOPS
|
||||
|
||||
/**
|
||||
* apfs_create_hole - Create and insert a hole extent for the dstream
|
||||
@@ -1448,10 +1449,6 @@ static int apfs_dstream_get_new_block(struct apfs_dstream_info *dstream, u64 dsb
|
||||
dstream->ds_ext_dirty = true;
|
||||
return 0;
|
||||
}
|
||||
int APFS_GET_NEW_BLOCK_MAXOPS(void)
|
||||
{
|
||||
return APFS_FLUSH_EXTENT_CACHE;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_dstream_get_new_bno - Allocate a new block inside a dstream
|
||||
@@ -1625,6 +1622,125 @@ int apfs_truncate(struct apfs_dstream_info *dstream, loff_t new_size)
|
||||
return apfs_create_hole(dstream, old_blks, new_blks);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_dstream_delete_front - Deletes as many leading extents as possible
|
||||
* @sb: filesystem superblock
|
||||
* @ds_id: id for the dstream to delete
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure, which may
|
||||
* be -ENODATA if there are no more extents, or -EAGAIN if the free queue is
|
||||
* getting too full.
|
||||
*/
|
||||
static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_spaceman *sm = APFS_SM(sb);
|
||||
struct apfs_spaceman_phys *sm_raw = sm->sm_raw;
|
||||
struct apfs_spaceman_free_queue *fq = NULL;
|
||||
struct apfs_query *query = NULL;
|
||||
struct apfs_file_extent head;
|
||||
bool first_match = true;
|
||||
int ret;
|
||||
|
||||
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
||||
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
return -EAGAIN;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
if (!query)
|
||||
return -ENOMEM;
|
||||
apfs_init_file_extent_key(ds_id, 0, &query->key);
|
||||
query->flags = APFS_QUERY_CAT;
|
||||
|
||||
next_extent:
|
||||
ret = apfs_btree_query(sb, &query);
|
||||
if (ret && ret != -ENODATA) {
|
||||
apfs_err(sb, "query failed for first extent of id 0x%llx", ds_id);
|
||||
goto out;
|
||||
}
|
||||
apfs_query_direct_forward(query);
|
||||
if (!apfs_query_found_extent(query)) {
|
||||
/*
|
||||
* After the original lookup, the query may not be set to the
|
||||
* first extent, but instead to the record that comes right
|
||||
* before.
|
||||
*/
|
||||
if (first_match) {
|
||||
first_match = false;
|
||||
goto next_extent;
|
||||
}
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
first_match = false;
|
||||
|
||||
ret = apfs_extent_from_query(query, &head);
|
||||
if (ret) {
|
||||
apfs_err(sb, "bad head extent record on dstream 0x%llx", ds_id);
|
||||
goto out;
|
||||
}
|
||||
ret = apfs_btree_remove(query);
|
||||
if (ret) {
|
||||
apfs_err(sb, "removal failed for id 0x%llx, addr 0x%llx", ds_id, head.logical_addr);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* The official fsck doesn't complain about wrong sparse byte counts
|
||||
* for orphans, so I guess we don't need to update them here
|
||||
*/
|
||||
if (!apfs_ext_is_hole(&head)) {
|
||||
ret = apfs_range_put_reference(sb, head.phys_block_num, head.len);
|
||||
if (ret) {
|
||||
apfs_err(sb, "failed to put range 0x%llx-0x%llx", head.phys_block_num, head.len);
|
||||
goto out;
|
||||
}
|
||||
ret = apfs_crypto_adj_refcnt(sb, head.crypto_id, -1);
|
||||
if (ret) {
|
||||
apfs_err(sb, "failed to take crypto id 0x%llx", head.crypto_id);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
goto next_extent;
|
||||
ret = -EAGAIN;
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_inode_delete_front - Deletes as many leading extents as possible
|
||||
* @inode: inode to delete
|
||||
*
|
||||
* Tries to delete all extents for @inode, in which case it returns 0. If the
|
||||
* free queue is getting too full, deletes as much as is reasonable and returns
|
||||
* -EAGAIN. May return other negative error codes as well.
|
||||
*/
|
||||
int apfs_inode_delete_front(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_dstream_info *dstream = NULL;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
int ret;
|
||||
|
||||
if (!ai->i_has_dstream)
|
||||
return 0;
|
||||
|
||||
dstream = &ai->i_dstream;
|
||||
ret = apfs_flush_extent_cache(dstream);
|
||||
if (ret) {
|
||||
apfs_err(sb, "extent cache flush failed for dstream 0x%llx", dstream->ds_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = apfs_dstream_delete_front(sb, dstream->ds_id);
|
||||
if (ret == -ENODATA)
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
|
||||
loff_t apfs_remap_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, loff_t len, unsigned int remap_flags)
|
||||
#else
|
||||
@@ -1639,8 +1755,6 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
|
||||
struct apfs_dstream_info *dst_ds = &dst_ai->i_dstream;
|
||||
struct super_block *sb = src_inode->i_sb;
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
/* TODO: remember to update the maxops in the future */
|
||||
struct apfs_max_ops maxops = {0};
|
||||
const u64 xfield_flags = APFS_INODE_MAINTAIN_DIR_STATS | APFS_INODE_IS_SPARSE | APFS_INODE_HAS_PURGEABLE_FLAGS;
|
||||
int err;
|
||||
|
||||
@@ -1674,7 +1788,7 @@ int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_fi
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
apfs_inode_join_transaction(sb, src_inode);
|
||||
@@ -1787,7 +1901,7 @@ out:
|
||||
* @paddr_end: first block after the extent to put
|
||||
* @paddr_min: don't put references before this block
|
||||
*
|
||||
* Puts a reference to the physical extent range that ends in paddr. Sets
|
||||
* Puts a reference to the physical extent range that ends in @paddr_end. Sets
|
||||
* @paddr_end to the beginning of the extent, so that the caller can continue
|
||||
* with the previous one. Returns 0 on success, or a negative error code in
|
||||
* case of failure.
|
||||
@@ -1849,7 +1963,7 @@ restart:
|
||||
prev_end = prev_ext.bno + prev_ext.blkcount;
|
||||
if (prev_end < *paddr_end) {
|
||||
/* The extent to put is part of a snapshot */
|
||||
tmp.phys_block_num = MAX(prev_end, paddr_min);
|
||||
tmp.phys_block_num = max(prev_end, paddr_min);
|
||||
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
||||
ret = apfs_create_update_pext(query, &tmp, -1);
|
||||
*paddr_end = tmp.phys_block_num;
|
||||
@@ -1929,7 +2043,7 @@ static int apfs_range_put_reference(struct super_block *sb, u64 paddr, u64 lengt
|
||||
* @paddr_end: first block after the extent to take
|
||||
* @paddr_min: don't take references before this block
|
||||
*
|
||||
* Takes a reference to the physical extent range that ends in paddr. Sets
|
||||
* Takes a reference to the physical extent range that ends in @paddr_end. Sets
|
||||
* @paddr_end to the beginning of the extent, so that the caller can continue
|
||||
* with the previous one. Returns 0 on success, or a negative error code in
|
||||
* case of failure.
|
||||
@@ -1989,7 +2103,7 @@ restart:
|
||||
prev_end = prev_ext.bno + prev_ext.blkcount;
|
||||
if (prev_end < *paddr_end) {
|
||||
/* The extent to take is part of a snapshot */
|
||||
tmp.phys_block_num = MAX(prev_end, paddr_min);
|
||||
tmp.phys_block_num = max(prev_end, paddr_min);
|
||||
tmp.len = (*paddr_end - tmp.phys_block_num) << sb->s_blocksize_bits;
|
||||
ret = apfs_create_update_pext(query, &tmp, +1);
|
||||
*paddr_end = tmp.phys_block_num;
|
||||
@@ -2173,11 +2287,7 @@ int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, si
|
||||
goto out;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
|
||||
bhs[idx] = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#else
|
||||
bhs[idx] = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#endif
|
||||
bhs[idx] = __apfs_getblk(sb, bno);
|
||||
if (!bhs[idx]) {
|
||||
apfs_err(sb, "failed to map block 0x%llx", bno);
|
||||
ret = -EIO;
|
||||
@@ -2249,11 +2359,7 @@ void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream)
|
||||
if (ret || bno == 0)
|
||||
return;
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
|
||||
bh = __getblk_gfp(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#else
|
||||
bh = bdev_getblk(APFS_NXI(sb)->nx_bdev, bno, sb->s_blocksize, __GFP_MOVABLE);
|
||||
#endif
|
||||
bh = __apfs_getblk(sb, bno);
|
||||
if (!bh)
|
||||
return;
|
||||
if (!buffer_uptodate(bh)) {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "apfs.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
#include <linux/splice.h>
|
||||
#endif
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)
|
||||
typedef int vm_fault_t;
|
||||
#endif
|
||||
@@ -25,8 +28,6 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct buffer_head *bh, *head;
|
||||
vm_fault_t ret = VM_FAULT_LOCKED;
|
||||
struct apfs_max_ops maxops;
|
||||
int blkcount = PAGE_SIZE >> inode->i_blkbits;
|
||||
unsigned int blocksize, block_start, len;
|
||||
u64 size;
|
||||
int err = 0;
|
||||
@@ -34,12 +35,7 @@ static vm_fault_t apfs_page_mkwrite(struct vm_fault *vmf)
|
||||
sb_start_pagefault(inode->i_sb);
|
||||
file_update_time(vma->vm_file);
|
||||
|
||||
/* Placeholder values, I need to get back to this in the future */
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS() +
|
||||
blkcount * APFS_GET_NEW_BLOCK_MAXOPS();
|
||||
maxops.blks = blkcount;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
goto out;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
@@ -110,7 +106,7 @@ out_abort:
|
||||
apfs_transaction_abort(sb);
|
||||
out:
|
||||
if (err)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) && !RHEL_VERSION_GE(9, 5)
|
||||
ret = block_page_mkwrite_return(err);
|
||||
#else
|
||||
ret = vmf_fs_error(err);
|
||||
@@ -129,7 +125,7 @@ int apfs_file_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct address_space *mapping = file->f_mapping;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
if (!mapping->a_ops->read_folio)
|
||||
#else
|
||||
if (!mapping->a_ops->readpage)
|
||||
@@ -152,6 +148,16 @@ int apfs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
return apfs_sync_fs(sb, true /* wait */);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
static ssize_t apfs_copy_file_range(struct file *src_file, loff_t src_off,
|
||||
struct file *dst_file, loff_t dst_off,
|
||||
size_t len, unsigned int flags)
|
||||
{
|
||||
return (splice_copy_file_range(src_file, src_off,
|
||||
dst_file, dst_off, len));
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct file_operations apfs_file_operations = {
|
||||
.llseek = generic_file_llseek,
|
||||
.read_iter = generic_file_read_iter,
|
||||
@@ -161,7 +167,9 @@ const struct file_operations apfs_file_operations = {
|
||||
.fsync = apfs_fsync,
|
||||
.unlocked_ioctl = apfs_file_ioctl,
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
.copy_file_range = apfs_copy_file_range,
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
|
||||
.copy_file_range = generic_copy_file_range,
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Script to generate the module version header
|
||||
#
|
||||
|
||||
if command -v git >/dev/null 2>&1 && [ -d .git ]; then
|
||||
GIT_COMMIT=$(git describe HEAD | tail -c 9)
|
||||
else
|
||||
GIT_COMMIT="$(grep PACKAGE_VERSION dkms.conf | cut -d '"' -f2)?"
|
||||
fi
|
||||
|
||||
printf '#define GIT_COMMIT\t"%s"\n' "$GIT_COMMIT" > version.h
|
||||
@@ -180,7 +180,7 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
|
||||
{
|
||||
struct apfs_spaceman_free_queue_key *raw_key;
|
||||
|
||||
if (size < sizeof(struct apfs_spaceman_free_queue_key)) {
|
||||
if (size != sizeof(*raw_key)) {
|
||||
apfs_err(NULL, "bad key length (%d)", size);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
@@ -204,14 +204,17 @@ int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key)
|
||||
*/
|
||||
int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
|
||||
{
|
||||
if (size < sizeof(struct apfs_omap_key)) {
|
||||
struct apfs_omap_key *raw_key;
|
||||
|
||||
if (size != sizeof(*raw_key)) {
|
||||
apfs_err(NULL, "bad key length (%d)", size);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
raw_key = raw;
|
||||
|
||||
key->id = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_oid);
|
||||
key->id = le64_to_cpu(raw_key->ok_oid);
|
||||
key->type = 0;
|
||||
key->number = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_xid);
|
||||
key->number = le64_to_cpu(raw_key->ok_xid);
|
||||
key->name = NULL;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <linux/string.h>
|
||||
#include "libzbitmap.h"
|
||||
|
||||
#define MIN(x, y) ((x) > (y) ? (y) : (x))
|
||||
|
||||
#define ZBM_MAGIC "ZBM\x09"
|
||||
#define ZBM_MAGIC_SZ 4
|
||||
|
||||
|
||||
@@ -114,7 +114,11 @@ static int apfs_dentry_compare(const struct dentry *dentry, unsigned int len,
|
||||
return apfs_filename_cmp(dentry->d_sb, name->name, name->len, str, len);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 14, 0)
|
||||
static int apfs_dentry_revalidate(struct dentry *dentry, unsigned int flags)
|
||||
#else
|
||||
static int apfs_dentry_revalidate(struct inode *dir, const struct qstr *name, struct dentry *dentry, unsigned int flags)
|
||||
#endif
|
||||
{
|
||||
struct super_block *sb = dentry->d_sb;
|
||||
|
||||
|
||||
@@ -37,131 +37,129 @@ static u64 apfs_fletcher64(void *addr, size_t len)
|
||||
|
||||
int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh)
|
||||
{
|
||||
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)bh->b_data;
|
||||
|
||||
/* The checksum may be stale until the transaction is committed */
|
||||
if (buffer_trans(bh))
|
||||
return 1;
|
||||
return apfs_multiblock_verify_csum(bh->b_data, sb->s_blocksize);
|
||||
}
|
||||
|
||||
return (le64_to_cpu(obj->o_cksum) ==
|
||||
apfs_fletcher64((char *) obj + APFS_MAX_CKSUM_SIZE,
|
||||
sb->s_blocksize - APFS_MAX_CKSUM_SIZE));
|
||||
/**
|
||||
* apfs_multiblock_verify_csum - Verify an object's checksum
|
||||
* @object: the object to verify
|
||||
* @size: size of the object in bytes (may be multiple blocks)
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
int apfs_multiblock_verify_csum(char *object, u32 size)
|
||||
{
|
||||
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
|
||||
u64 actual_csum, header_csum;
|
||||
|
||||
header_csum = le64_to_cpu(obj->o_cksum);
|
||||
actual_csum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - APFS_MAX_CKSUM_SIZE);
|
||||
return header_csum == actual_csum;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_obj_set_csum - Set the fletcher checksum in an object header
|
||||
* @sb: superblock structure
|
||||
* @obj: the object header
|
||||
*
|
||||
* The object must have a length of a single block.
|
||||
*/
|
||||
void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj)
|
||||
{
|
||||
u64 cksum = apfs_fletcher64((char *)obj + APFS_MAX_CKSUM_SIZE,
|
||||
sb->s_blocksize - APFS_MAX_CKSUM_SIZE);
|
||||
apfs_multiblock_set_csum((char *)obj, sb->s_blocksize);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_multiblock_set_csum - Set an object's checksum
|
||||
* @object: the object to checksum
|
||||
* @size: size of the object in bytes (may be multiple blocks)
|
||||
*/
|
||||
void apfs_multiblock_set_csum(char *object, u32 size)
|
||||
{
|
||||
struct apfs_obj_phys *obj = (struct apfs_obj_phys *)object;
|
||||
u64 cksum;
|
||||
|
||||
cksum = apfs_fletcher64(object + APFS_MAX_CKSUM_SIZE, size - APFS_MAX_CKSUM_SIZE);
|
||||
obj->o_cksum = cpu_to_le64(cksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_cpm_lookup_oid - Search a checkpoint-mapping block for a given oid
|
||||
* @sb: superblock structure
|
||||
* @cpm: checkpoint-mapping block (on disk)
|
||||
* @oid: the ephemeral object id to look up
|
||||
* @bno: on return, the block number for the object
|
||||
*
|
||||
* Returns -EFSCORRUPTED in case of corruption, or -EAGAIN if @oid is not
|
||||
* listed in @cpm; returns 0 on success.
|
||||
*/
|
||||
static int apfs_cpm_lookup_oid(struct super_block *sb,
|
||||
struct apfs_checkpoint_map_phys *cpm,
|
||||
u64 oid, u64 *bno)
|
||||
{
|
||||
u32 map_count = le32_to_cpu(cpm->cpm_count);
|
||||
int i;
|
||||
|
||||
if (map_count > apfs_max_maps_per_block(sb)) {
|
||||
apfs_err(sb, "block has too many maps (%d)", map_count);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
|
||||
for (i = 0; i < map_count; ++i) {
|
||||
struct apfs_checkpoint_mapping *map = &cpm->cpm_map[i];
|
||||
|
||||
if (le64_to_cpu(map->cpm_oid) == oid) {
|
||||
*bno = le64_to_cpu(map->cpm_paddr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -EAGAIN; /* The mapping may still be in the next block */
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_read_cpm_block - Read the checkpoint mapping block
|
||||
* @sb: super block structure
|
||||
*
|
||||
* Only a single cpm block is supported for now. Returns the buffer head for
|
||||
* the block on success, or NULL in case of failure.
|
||||
*/
|
||||
static struct buffer_head *apfs_read_cpm_block(struct super_block *sb)
|
||||
{
|
||||
struct apfs_nx_superblock *raw_sb = APFS_NXI(sb)->nx_raw;
|
||||
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
|
||||
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
|
||||
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
|
||||
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
|
||||
u64 cpm_bno;
|
||||
|
||||
if (!desc_blks || desc_len < 2)
|
||||
return NULL;
|
||||
|
||||
/* Last block in area is superblock; we want the last mapping block */
|
||||
cpm_bno = desc_base + (desc_index + desc_len - 2) % desc_blks;
|
||||
return apfs_sb_bread(sb, cpm_bno);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_create_cpoint_map - Create a checkpoint mapping
|
||||
* apfs_create_cpm_block - Create a new checkpoint-mapping block
|
||||
* @sb: filesystem superblock
|
||||
* @oid: ephemeral object id
|
||||
* @bno: block number
|
||||
* @bno: block number to use
|
||||
* @bh_p: on return, the buffer head for the block
|
||||
*
|
||||
* Only mappings for free queue nodes are supported for now. Returns 0 on
|
||||
* success or a negative error code in case of failure.
|
||||
* Returns 0 on success or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_create_cpoint_map(struct super_block *sb, u64 oid, u64 bno)
|
||||
int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
struct apfs_checkpoint_mapping *map;
|
||||
u32 cpm_count;
|
||||
int err = 0;
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_checkpoint_map_phys *cpm = NULL;
|
||||
struct buffer_head *bh = NULL;
|
||||
int err;
|
||||
|
||||
bh = apfs_read_cpm_block(sb);
|
||||
if (!bh)
|
||||
bh = apfs_getblk(sb, bno);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to map cpm block");
|
||||
return -EIO;
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
}
|
||||
err = apfs_transaction_join(sb, bh);
|
||||
if (err) {
|
||||
brelse(bh);
|
||||
return err;
|
||||
}
|
||||
set_buffer_csum(bh);
|
||||
|
||||
cpm = (void *)bh->b_data;
|
||||
memset(cpm, 0, sb->s_blocksize);
|
||||
cpm->cpm_o.o_oid = cpu_to_le64(bno);
|
||||
cpm->cpm_o.o_xid = cpu_to_le64(nxi->nx_xid);
|
||||
cpm->cpm_o.o_type = cpu_to_le32(APFS_OBJ_PHYSICAL | APFS_OBJECT_TYPE_CHECKPOINT_MAP);
|
||||
cpm->cpm_o.o_subtype = cpu_to_le32(APFS_OBJECT_TYPE_INVALID);
|
||||
|
||||
/* For now: the caller will have to update these fields */
|
||||
cpm->cpm_flags = cpu_to_le32(APFS_CHECKPOINT_MAP_LAST);
|
||||
cpm->cpm_count = 0;
|
||||
|
||||
*bh_p = bh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_create_cpoint_map - Create a checkpoint mapping for an object
|
||||
* @sb: filesystem superblock
|
||||
* @cpm: checkpoint mapping block to use
|
||||
* @obj: header for the ephemeral object
|
||||
* @bno: block number for the ephemeral object
|
||||
* @size: size of the ephemeral object in bytes
|
||||
*
|
||||
* Returns 0 on success or a negative error code in case of failure, which may
|
||||
* be -ENOSPC if @cpm is full.
|
||||
*/
|
||||
int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size)
|
||||
{
|
||||
struct apfs_checkpoint_mapping *map = NULL;
|
||||
u32 cpm_count;
|
||||
|
||||
apfs_assert_in_transaction(sb, &cpm->cpm_o);
|
||||
|
||||
cpm_count = le32_to_cpu(cpm->cpm_count);
|
||||
if (cpm_count >= apfs_max_maps_per_block(sb)) { /* TODO */
|
||||
apfs_warn(sb, "creation of cpm blocks not yet supported");
|
||||
err = -EOPNOTSUPP;
|
||||
goto fail;
|
||||
}
|
||||
if (cpm_count >= apfs_max_maps_per_block(sb))
|
||||
return -ENOSPC;
|
||||
map = &cpm->cpm_map[cpm_count];
|
||||
le32_add_cpu(&cpm->cpm_count, 1);
|
||||
|
||||
map->cpm_type = cpu_to_le32(APFS_OBJ_EPHEMERAL |
|
||||
APFS_OBJECT_TYPE_BTREE_NODE);
|
||||
map->cpm_subtype = cpu_to_le32(APFS_OBJECT_TYPE_SPACEMAN_FREE_QUEUE);
|
||||
map->cpm_size = cpu_to_le32(sb->s_blocksize);
|
||||
map->cpm_type = obj->o_type;
|
||||
map->cpm_subtype = obj->o_subtype;
|
||||
map->cpm_size = cpu_to_le32(size);
|
||||
map->cpm_pad = 0;
|
||||
map->cpm_fs_oid = 0;
|
||||
map->cpm_oid = cpu_to_le64(oid);
|
||||
map->cpm_oid = obj->o_oid;
|
||||
map->cpm_paddr = cpu_to_le64(bno);
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,120 +195,23 @@ u64 apfs_data_index_to_bno(struct super_block *sb, u32 index)
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_remove_cpoint_map - Remove a checkpoint mapping
|
||||
* @sb: filesystem superblock
|
||||
* @bno: block number to delete
|
||||
*
|
||||
* Only mappings for free queue nodes are supported for now. Blocks that come
|
||||
* after the deleted one are assumed to shift back one place. Returns 0 on
|
||||
* success or a negative error code in case of failure.
|
||||
*/
|
||||
int apfs_remove_cpoint_map(struct super_block *sb, u64 bno)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
struct apfs_checkpoint_mapping *map, *maps_start, *maps_end;
|
||||
struct apfs_checkpoint_mapping *bno_map = NULL;
|
||||
u32 cpm_count;
|
||||
u32 bno_off;
|
||||
int err = 0;
|
||||
|
||||
bh = apfs_read_cpm_block(sb);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to read cpm block");
|
||||
return -EIO;
|
||||
}
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
apfs_assert_in_transaction(sb, &cpm->cpm_o);
|
||||
|
||||
/* TODO: multiple cpm blocks? */
|
||||
cpm_count = le32_to_cpu(cpm->cpm_count);
|
||||
if (cpm_count > apfs_max_maps_per_block(sb)) {
|
||||
apfs_err(sb, "block has too many maps (%d)", cpm_count);
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
maps_start = &cpm->cpm_map[0];
|
||||
maps_end = &cpm->cpm_map[cpm_count];
|
||||
|
||||
bno_off = apfs_index_in_data_area(sb, bno);
|
||||
for (map = maps_start; map < maps_end; ++map) {
|
||||
u32 curr_off;
|
||||
|
||||
if (le64_to_cpu(map->cpm_paddr) == bno)
|
||||
bno_map = map;
|
||||
|
||||
curr_off = apfs_index_in_data_area(sb, le64_to_cpu(map->cpm_paddr));
|
||||
if (curr_off > bno_off)
|
||||
map->cpm_paddr = cpu_to_le64(apfs_data_index_to_bno(sb, curr_off - 1));
|
||||
}
|
||||
if (!bno_map) {
|
||||
apfs_err(sb, "no mapping for bno 0x%llx", bno);
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
memmove(bno_map, bno_map + 1, (maps_end - bno_map - 1) * sizeof(*bno_map));
|
||||
le32_add_cpu(&cpm->cpm_count, -1);
|
||||
|
||||
fail:
|
||||
brelse(bh);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_read_ephemeral_object - Find and map an ephemeral object
|
||||
* apfs_ephemeral_object_lookup - Find an ephemeral object info in memory
|
||||
* @sb: superblock structure
|
||||
* @oid: ephemeral object id
|
||||
*
|
||||
* Returns the mapped buffer head for the object, or an error pointer in case
|
||||
* Returns a pointer to the object info on success, or an error pointer in case
|
||||
* of failure.
|
||||
*/
|
||||
struct buffer_head *apfs_read_ephemeral_object(struct super_block *sb, u64 oid)
|
||||
struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid)
|
||||
{
|
||||
struct apfs_nxsb_info *nxi = APFS_NXI(sb);
|
||||
struct apfs_nx_superblock *raw_sb = nxi->nx_raw;
|
||||
u64 desc_base = le64_to_cpu(raw_sb->nx_xp_desc_base);
|
||||
u32 desc_index = le32_to_cpu(raw_sb->nx_xp_desc_index);
|
||||
u32 desc_blks = le32_to_cpu(raw_sb->nx_xp_desc_blocks);
|
||||
u32 desc_len = le32_to_cpu(raw_sb->nx_xp_desc_len);
|
||||
u32 i;
|
||||
struct apfs_ephemeral_object_info *list = NULL;
|
||||
int i;
|
||||
|
||||
if (!desc_blks || !desc_len) {
|
||||
apfs_err(sb, "no blocks in checkpoint descriptor area");
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
}
|
||||
|
||||
/* Last block in the area is superblock; the rest are mapping blocks */
|
||||
for (i = 0; i < desc_len - 1; ++i) {
|
||||
struct buffer_head *bh;
|
||||
struct apfs_checkpoint_map_phys *cpm;
|
||||
u64 cpm_bno = desc_base + (desc_index + i) % desc_blks;
|
||||
u64 obj_bno;
|
||||
int err;
|
||||
|
||||
bh = apfs_sb_bread(sb, cpm_bno);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to read cpm block");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
cpm = (struct apfs_checkpoint_map_phys *)bh->b_data;
|
||||
|
||||
err = apfs_cpm_lookup_oid(sb, cpm, oid, &obj_bno);
|
||||
brelse(bh);
|
||||
cpm = NULL;
|
||||
if (err == -EAGAIN) /* Search the next mapping block */
|
||||
continue;
|
||||
if (err) {
|
||||
apfs_err(sb, "cpm lookup failed for oid 0x%llx", oid);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
bh = apfs_sb_bread(sb, obj_bno);
|
||||
if (!bh) {
|
||||
apfs_err(sb, "failed to read ephemeral block");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
return bh;
|
||||
list = nxi->nx_eph_list;
|
||||
for (i = 0; i < nxi->nx_eph_count; ++i) {
|
||||
if (list[i].oid == oid)
|
||||
return &list[i];
|
||||
}
|
||||
apfs_err(sb, "no mapping for oid 0x%llx", oid);
|
||||
return ERR_PTR(-EFSCORRUPTED);
|
||||
|
||||
+82
-7
@@ -149,9 +149,9 @@ static int apfs_create_snap_name_rec(struct apfs_node *snap_root, const char *na
|
||||
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err == 0) {
|
||||
/* TODO: avoid transaction abort here */
|
||||
apfs_info(sb, "a snapshot with that name already exists");
|
||||
err = -EEXIST;
|
||||
/* This should never happen here, the caller has checked */
|
||||
apfs_alert(sb, "snapshot name record already exists");
|
||||
err = -EFSCORRUPTED;
|
||||
goto fail;
|
||||
}
|
||||
if (err != -ENODATA) {
|
||||
@@ -332,6 +332,56 @@ static int apfs_update_omap_snapshots(struct super_block *sb)
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_snapshot_name_check - Check if a snapshot with the given name exists
|
||||
* @sb: filesystem superblock
|
||||
* @name: name to check
|
||||
* @name_len: length of @name
|
||||
* @eexist: on return, whether the name exists or not
|
||||
*
|
||||
* Returns 0 on success, or a negative error code in case of failure.
|
||||
*/
|
||||
static int apfs_snapshot_name_check(struct super_block *sb, const char *name, int name_len, bool *eexist)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_superblock *vsb_raw = sbi->s_vsb_raw;
|
||||
struct apfs_node *snap_root = NULL;
|
||||
struct apfs_query *query = NULL;
|
||||
int err;
|
||||
|
||||
snap_root = apfs_read_node(sb, le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid), APFS_OBJ_PHYSICAL, false /* write */);
|
||||
if (IS_ERR(snap_root)) {
|
||||
apfs_err(sb, "failed to read snap meta root 0x%llx", le64_to_cpu(vsb_raw->apfs_snap_meta_tree_oid));
|
||||
return PTR_ERR(snap_root);
|
||||
}
|
||||
vsb_raw = NULL;
|
||||
|
||||
query = apfs_alloc_query(snap_root, NULL /* parent */);
|
||||
if (!query) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
apfs_init_snap_name_key(name, &query->key);
|
||||
query->flags |= APFS_QUERY_SNAP_META | APFS_QUERY_EXACT;
|
||||
|
||||
err = apfs_btree_query(sb, &query);
|
||||
if (err == 0) {
|
||||
*eexist = true;
|
||||
} else if (err == -ENODATA) {
|
||||
*eexist = false;
|
||||
err = 0;
|
||||
} else {
|
||||
apfs_err(sb, "query failed (%s)", name);
|
||||
}
|
||||
|
||||
out:
|
||||
apfs_free_query(query);
|
||||
query = NULL;
|
||||
apfs_node_free(snap_root);
|
||||
snap_root = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_do_ioc_takesnapshot - Actual work for apfs_ioc_take_snapshot()
|
||||
* @mntpoint: inode of the mount point to snapshot
|
||||
@@ -345,15 +395,33 @@ static int apfs_do_ioc_take_snapshot(struct inode *mntpoint, const char *name, i
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
struct apfs_superblock *vsb_raw = NULL;
|
||||
struct apfs_omap *omap = sbi->s_omap;
|
||||
/* TODO: remember to update the maxops in the future */
|
||||
struct apfs_max_ops maxops = {0};
|
||||
u64 sblock_oid;
|
||||
bool eexist = false;
|
||||
int err;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* This check can fail in normal operation, so run it before making any
|
||||
* changes and exit the transaction cleanly if necessary. The name
|
||||
* lookup will have to be repeated later, but it's ok because I don't
|
||||
* expect snapshot creation to be a bottleneck for anyone.
|
||||
*/
|
||||
err = apfs_snapshot_name_check(sb, name, name_len, &eexist);
|
||||
if (err) {
|
||||
apfs_err(sb, "snapshot name existence check failed");
|
||||
goto fail;
|
||||
}
|
||||
if (eexist) {
|
||||
err = apfs_transaction_commit(sb);
|
||||
if (err)
|
||||
goto fail;
|
||||
apfs_warn(sb, "snapshot name already in use (%s)", name);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush the extent caches to the extenref tree before it gets moved to
|
||||
* the snapshot. It seems safer in general to avoid big unpredictable
|
||||
@@ -516,15 +584,22 @@ fail:
|
||||
|
||||
static int apfs_snap_sblock_from_query(struct apfs_query *query, u64 *sblock_oid)
|
||||
{
|
||||
struct super_block *sb = query->node->object.sb;
|
||||
char *raw = query->node->object.data;
|
||||
struct apfs_snap_metadata_val *val = NULL;
|
||||
|
||||
if (query->len < sizeof(*val)) {
|
||||
apfs_err(query->node->object.sb, "bad value length (%d)", query->len);
|
||||
apfs_err(sb, "bad value length (%d)", query->len);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
val = (struct apfs_snap_metadata_val *)(raw + query->off);
|
||||
|
||||
/* I don't know anything about these snapshots, can they be mounted? */
|
||||
if (le32_to_cpu(val->flags) & APFS_SNAP_META_PENDING_DATALESS) {
|
||||
apfs_warn(sb, "the requested snapshot is dataless");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
*sblock_oid = le64_to_cpu(val->sblock_oid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+421
-197
File diff suppressed because it is too large.
Load diff
Loaded 20 of 23 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user