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
@@ -13,6 +13,11 @@ 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:
|
||||
./genver.sh
|
||||
make -C $(KERNEL_DIR) M=$(PWD)
|
||||
|
||||
+26
-12
@@ -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,15 +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, and neither are
|
||||
fusion drives.
|
||||
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)::
|
||||
|
||||
@@ -65,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
|
||||
|
||||
/*
|
||||
@@ -81,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 */
|
||||
@@ -117,6 +132,11 @@ 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.
|
||||
*/
|
||||
@@ -132,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
|
||||
@@ -147,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 */
|
||||
@@ -160,28 +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 {
|
||||
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 */
|
||||
@@ -196,14 +224,6 @@ struct apfs_bh_info {
|
||||
struct list_head list; /* List of buffers in the transaction */
|
||||
};
|
||||
|
||||
/*
|
||||
* Used to report how many operations may be needed for a transaction
|
||||
*/
|
||||
struct apfs_max_ops {
|
||||
int cat; /* Maximum catalog records that may need changing */
|
||||
int blks; /* Maximum extent blocks that may need changing */
|
||||
};
|
||||
|
||||
/*
|
||||
* List entry for an in-memory ephemeral object
|
||||
*/
|
||||
@@ -224,15 +244,44 @@ 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 */
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
||||
struct bdev_handle *nx_bdev_handle;
|
||||
#endif
|
||||
/* 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 */
|
||||
u64 nx_bno; /* Current block number for the checkpoint superblock */
|
||||
u64 nx_xid; /* Latest transaction id */
|
||||
@@ -252,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;
|
||||
@@ -316,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 */
|
||||
|
||||
@@ -331,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)
|
||||
@@ -626,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 */
|
||||
@@ -656,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 */
|
||||
};
|
||||
@@ -755,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;
|
||||
};
|
||||
|
||||
@@ -872,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);
|
||||
@@ -918,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,
|
||||
@@ -932,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
|
||||
@@ -950,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);
|
||||
@@ -997,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);
|
||||
@@ -1060,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);
|
||||
@@ -1071,13 +1126,15 @@ extern int apfs_sync_fs(struct super_block *sb, int wait);
|
||||
|
||||
/* transaction.c */
|
||||
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,
|
||||
@@ -1087,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);
|
||||
@@ -1147,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;
|
||||
}
|
||||
@@ -1156,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);
|
||||
@@ -1132,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,5 +1,5 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.8"
|
||||
PACKAGE_VERSION="0.3.13"
|
||||
|
||||
BUILT_MODULE_NAME[0]="apfs"
|
||||
DEST_MODULE_LOCATION[0]="/extra"
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -28,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;
|
||||
@@ -37,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);
|
||||
@@ -113,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);
|
||||
@@ -132,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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* Verifies that the node index fits in a single block, and that the number
|
||||
* of records fits in the index. Without this check a crafted filesystem could
|
||||
* pretend to have too many records, and calls to apfs_node_locate_key() and
|
||||
* apfs_node_locate_data() would read beyond the limits of the node.
|
||||
* apfs_node_locate_value() would read beyond the limits of the node.
|
||||
*/
|
||||
static bool apfs_node_is_valid(struct super_block *sb,
|
||||
struct apfs_node *node)
|
||||
@@ -142,7 +142,7 @@ struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid, u32 storage,
|
||||
node->key = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off)
|
||||
+ le16_to_cpu(raw->btn_table_space.len);
|
||||
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
|
||||
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
|
||||
node->val = node->free + le16_to_cpu(raw->btn_free_space.len);
|
||||
|
||||
free_head = &raw->btn_key_free_list;
|
||||
node->key_free_list_len = le16_to_cpu(free_head->len);
|
||||
@@ -559,7 +559,7 @@ void apfs_update_node(struct apfs_node *node)
|
||||
toc_off = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off);
|
||||
raw->btn_table_space.len = cpu_to_le16(node->key - toc_off);
|
||||
raw->btn_free_space.off = cpu_to_le16(node->free - node->key);
|
||||
raw->btn_free_space.len = cpu_to_le16(node->data - node->free);
|
||||
raw->btn_free_space.len = cpu_to_le16(node->val - node->free);
|
||||
|
||||
/* Reset the lists on zero length, a defragmentation is taking place */
|
||||
free_head = &raw->btn_key_free_list;
|
||||
@@ -584,8 +584,8 @@ void apfs_update_node(struct apfs_node *node)
|
||||
* @off: on return will hold the offset in the block
|
||||
*
|
||||
* Returns the length of the key, or 0 in case of failure. The function checks
|
||||
* that this length fits within the block; callers must use the returned value
|
||||
* to make sure they never operate outside its bounds.
|
||||
* that this length fits within the block; callers must use it to make sure
|
||||
* they never operate outside its bounds.
|
||||
*/
|
||||
int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
|
||||
{
|
||||
@@ -613,7 +613,7 @@ int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
|
||||
/* Translate offset in key area to offset in block */
|
||||
*off = node->key + le16_to_cpu(entry->k);
|
||||
} else {
|
||||
/* These node types have variable length keys and data */
|
||||
/* These node types have variable length keys and values */
|
||||
struct apfs_kvloc *entry;
|
||||
|
||||
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
||||
@@ -630,17 +630,17 @@ int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_node_locate_data - Locate the data of a node record
|
||||
* apfs_node_locate_value - Locate the value of a node record
|
||||
* @node: node to be searched
|
||||
* @index: number of the entry to locate
|
||||
* @off: on return will hold the offset in the block
|
||||
*
|
||||
* Returns the length of the data, which may be 0 in case of corruption or if
|
||||
* Returns the length of the value, which may be 0 in case of corruption or if
|
||||
* the record is a ghost. The function checks that this length fits within the
|
||||
* block; callers must use the returned value to make sure they never operate
|
||||
* outside its bounds.
|
||||
* block; callers must use it to make sure they never operate outside its
|
||||
* bounds.
|
||||
*/
|
||||
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
||||
static int apfs_node_locate_value(struct apfs_node *node, int index, int *off)
|
||||
{
|
||||
struct super_block *sb = node->object.sb;
|
||||
struct apfs_btree_node_phys *raw;
|
||||
@@ -653,7 +653,7 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
||||
|
||||
raw = (struct apfs_btree_node_phys *)node->object.data;
|
||||
if (apfs_node_has_fixed_kv_size(node)) {
|
||||
/* These node types have fixed length keys and data */
|
||||
/* These node types have fixed length keys and values */
|
||||
struct apfs_kvoff *entry;
|
||||
|
||||
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
||||
@@ -669,7 +669,7 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
||||
len = apfs_node_is_leaf(node) ? 16 : 8;
|
||||
}
|
||||
/*
|
||||
* Data offsets are counted backwards from the end of the
|
||||
* Value offsets are counted backwards from the end of the
|
||||
* block, or from the beginning of the footer when it exists
|
||||
*/
|
||||
if (apfs_node_is_root(node)) /* has footer */
|
||||
@@ -678,13 +678,13 @@ static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
||||
else
|
||||
*off = sb->s_blocksize - le16_to_cpu(entry->v);
|
||||
} else {
|
||||
/* These node types have variable length keys and data */
|
||||
/* These node types have variable length keys and values */
|
||||
struct apfs_kvloc *entry;
|
||||
|
||||
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
||||
len = le16_to_cpu(entry->v.len);
|
||||
/*
|
||||
* Data offsets are counted backwards from the end of the
|
||||
* Value offsets are counted backwards from the end of the
|
||||
* block, or from the beginning of the footer when it exists
|
||||
*/
|
||||
if (apfs_node_is_root(node)) /* has footer */
|
||||
@@ -803,6 +803,42 @@ static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_node_prev - Find the previous record in the current node
|
||||
* @sb: filesystem superblock
|
||||
* @query: query in execution
|
||||
*
|
||||
* Returns 0 on success, -EAGAIN if the previous record is in another node,
|
||||
* -ENODATA if no more records exist, or another negative error code in case
|
||||
* of failure.
|
||||
*
|
||||
* The meaning of "next" and "previous" is reverted here, because regular
|
||||
* multiple always start with the final record, and then they go backwards.
|
||||
* TODO: consider renaming this for clarity.
|
||||
*/
|
||||
static int apfs_node_prev(struct super_block *sb, struct apfs_query *query)
|
||||
{
|
||||
struct apfs_node *node = query->node;
|
||||
|
||||
if (query->index + 1 == node->records) {
|
||||
/* The next record may be in another node */
|
||||
return -EAGAIN;
|
||||
}
|
||||
++query->index;
|
||||
|
||||
query->key_len = apfs_node_locate_key(node, query->index, &query->key_off);
|
||||
if (query->key_len == 0) {
|
||||
apfs_err(sb, "bad key for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
query->len = apfs_node_locate_value(node, query->index, &query->off);
|
||||
if (query->len == 0) {
|
||||
apfs_err(sb, "bad value for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_node_next - Find the next matching record in the current node
|
||||
* @sb: filesystem superblock
|
||||
@@ -845,7 +881,7 @@ static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
|
||||
query->flags & APFS_QUERY_EXACT)
|
||||
return -ENODATA;
|
||||
|
||||
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||
query->len = apfs_node_locate_value(node, query->index, &query->off);
|
||||
if (query->len == 0) {
|
||||
apfs_err(sb, "bad value for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
@@ -877,7 +913,7 @@ static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
|
||||
* as soon as we return to this level. The function may also return -EAGAIN,
|
||||
* to signal that the search should go on in a different branch.
|
||||
*
|
||||
* On success returns 0; the offset of the data within the block will be saved
|
||||
* On success returns 0; the offset of the value within the block will be saved
|
||||
* in @query->off, and its length in @query->len. The function checks that this
|
||||
* length fits within the block; callers must use the returned value to make
|
||||
* sure they never operate outside its bounds.
|
||||
@@ -892,6 +928,8 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
|
||||
int cmp;
|
||||
int err;
|
||||
|
||||
if (query->flags & APFS_QUERY_PREV)
|
||||
return apfs_node_prev(sb, query);
|
||||
if (query->flags & APFS_QUERY_NEXT)
|
||||
return apfs_node_next(sb, query);
|
||||
|
||||
@@ -941,7 +979,7 @@ int apfs_node_query(struct super_block *sb, struct apfs_query *query)
|
||||
query->flags |= APFS_QUERY_NEXT;
|
||||
}
|
||||
|
||||
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||
query->len = apfs_node_locate_value(node, query->index, &query->off);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -955,7 +993,7 @@ void apfs_node_query_first(struct apfs_query *query)
|
||||
|
||||
query->index = 0;
|
||||
query->key_len = apfs_node_locate_key(node, query->index, &query->key_off);
|
||||
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||
query->len = apfs_node_locate_value(node, query->index, &query->off);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1033,7 +1071,7 @@ static int apfs_btree_inc_height(struct apfs_query *query)
|
||||
new_node->records = root->records;
|
||||
new_node->key = root->key;
|
||||
new_node->free = root->free;
|
||||
new_node->data = root->data + sizeof(*info);
|
||||
new_node->val = root->val + sizeof(*info);
|
||||
new_node->key_free_list_len = root->key_free_list_len;
|
||||
new_node->val_free_list_len = root->val_free_list_len;
|
||||
new_raw = (void *)new_node->object.data;
|
||||
@@ -1041,9 +1079,9 @@ static int apfs_btree_inc_height(struct apfs_query *query)
|
||||
memcpy((void *)new_raw + sizeof(new_raw->btn_o),
|
||||
(void *)root_raw + sizeof(root_raw->btn_o),
|
||||
root->free - sizeof(new_raw->btn_o));
|
||||
memcpy((void *)new_raw + new_node->data,
|
||||
(void *)root_raw + root->data,
|
||||
sb->s_blocksize - new_node->data);
|
||||
memcpy((void *)new_raw + new_node->val,
|
||||
(void *)root_raw + root->val,
|
||||
sb->s_blocksize - new_node->val);
|
||||
query->off += sizeof(*info);
|
||||
apfs_update_node(new_node);
|
||||
|
||||
@@ -1074,10 +1112,10 @@ static int apfs_btree_inc_height(struct apfs_query *query)
|
||||
|
||||
/* The new root is a nonleaf node; the record value is the child id */
|
||||
root->flags &= ~APFS_BTNODE_LEAF;
|
||||
root->data = sb->s_blocksize - sizeof(*info) - sizeof(*raw_oid);
|
||||
raw_oid = (void *)root_raw + root->data;
|
||||
root->val = sb->s_blocksize - sizeof(*info) - sizeof(*raw_oid);
|
||||
raw_oid = (void *)root_raw + root->val;
|
||||
*raw_oid = cpu_to_le64(new_node->object.oid);
|
||||
root_query->off = root->data;
|
||||
root_query->off = root->val;
|
||||
root_query->len = sizeof(*raw_oid);
|
||||
|
||||
/* With the key and value in place, set the table-of-contents */
|
||||
@@ -1135,9 +1173,9 @@ static int apfs_copy_record_range(struct apfs_node *dest_node,
|
||||
toc_size = toc_entry_size * round_up(end - start, APFS_BTREE_TOC_ENTRY_INCREMENT);
|
||||
dest_node->key = sizeof(*dest_raw) + toc_size;
|
||||
dest_node->free = dest_node->key;
|
||||
dest_node->data = sb->s_blocksize;
|
||||
dest_node->val = sb->s_blocksize;
|
||||
if (apfs_node_is_root(dest_node))
|
||||
dest_node->data -= sizeof(struct apfs_btree_info);
|
||||
dest_node->val -= sizeof(struct apfs_btree_info);
|
||||
|
||||
/* We'll use a temporary query structure to move the records around */
|
||||
query = apfs_alloc_query(dest_node, NULL /* parent */);
|
||||
@@ -1161,15 +1199,15 @@ static int apfs_copy_record_range(struct apfs_node *dest_node,
|
||||
query->key_len = len;
|
||||
dest_node->free += len;
|
||||
|
||||
len = apfs_node_locate_data(src_node, i, &off);
|
||||
dest_node->data -= len;
|
||||
if (dest_node->data < 0) {
|
||||
len = apfs_node_locate_value(src_node, i, &off);
|
||||
dest_node->val -= len;
|
||||
if (dest_node->val < 0) {
|
||||
apfs_err(sb, "value of length %d doesn't fit", len);
|
||||
goto fail;
|
||||
}
|
||||
memcpy((char *)dest_raw + dest_node->data,
|
||||
memcpy((char *)dest_raw + dest_node->val,
|
||||
(char *)src_raw + off, len);
|
||||
query->off = dest_node->data;
|
||||
query->off = dest_node->val;
|
||||
query->len = len;
|
||||
|
||||
query->index = i - start;
|
||||
@@ -1445,7 +1483,7 @@ static void apfs_node_free_list_add(struct apfs_node *node, u16 off, u16 len)
|
||||
|
||||
apfs_assert_in_transaction(sb, &node_raw->btn_o);
|
||||
|
||||
if (off >= node->data) { /* Value area */
|
||||
if (off >= node->val) { /* Value area */
|
||||
off_to_rel = apfs_off_to_val_off;
|
||||
head = &node_raw->btn_val_free_list;
|
||||
node->val_free_list_len += len;
|
||||
@@ -1481,8 +1519,8 @@ void apfs_node_free_range(struct apfs_node *node, u16 off, u16 len)
|
||||
|
||||
apfs_assert_in_transaction(sb, &raw->btn_o);
|
||||
|
||||
if (off == node->data)
|
||||
node->data += len;
|
||||
if (off == node->val)
|
||||
node->val += len;
|
||||
else if (off + len == node->free)
|
||||
node->free -= len;
|
||||
else
|
||||
@@ -1578,7 +1616,7 @@ static int apfs_node_alloc_key(struct apfs_node *node, u16 len)
|
||||
{
|
||||
int off;
|
||||
|
||||
if (node->free + len <= node->data) {
|
||||
if (node->free + len <= node->val) {
|
||||
off = node->free;
|
||||
node->free += len;
|
||||
return off;
|
||||
@@ -1598,9 +1636,9 @@ static int apfs_node_alloc_val(struct apfs_node *node, u16 len)
|
||||
{
|
||||
int off;
|
||||
|
||||
if (node->free + len <= node->data) {
|
||||
off = node->data - len;
|
||||
node->data -= len;
|
||||
if (node->free + len <= node->val) {
|
||||
off = node->val - len;
|
||||
node->val -= len;
|
||||
return off;
|
||||
}
|
||||
return apfs_node_free_list_alloc(node, len, true /* value */);
|
||||
@@ -1612,7 +1650,7 @@ static int apfs_node_alloc_val(struct apfs_node *node, u16 len)
|
||||
*/
|
||||
static int apfs_node_total_room(struct apfs_node *node)
|
||||
{
|
||||
return node->data - node->free + node->key_free_list_len + node->val_free_list_len;
|
||||
return node->val - node->free + node->key_free_list_len + node->val_free_list_len;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1818,7 +1856,7 @@ defrag:
|
||||
defragged = true;
|
||||
|
||||
/* The record to replace probably moved around */
|
||||
query->len = apfs_node_locate_data(query->node, query->index, &query->off);
|
||||
query->len = apfs_node_locate_value(query->node, query->index, &query->off);
|
||||
query->key_len = apfs_node_locate_key(query->node, query->index, &query->key_off);
|
||||
goto retry;
|
||||
}
|
||||
@@ -1862,7 +1900,7 @@ retry:
|
||||
|
||||
new_key_base += inc;
|
||||
new_free_base += inc;
|
||||
if (new_free_base > node->data)
|
||||
if (new_free_base > node->val)
|
||||
goto defrag;
|
||||
memmove((void *)node_raw + new_key_base,
|
||||
(void *)node_raw + node->key, node->free - node->key);
|
||||
@@ -1983,7 +2021,7 @@ int apfs_create_single_rec_node(struct apfs_query *query, void *key, int key_len
|
||||
new_node->key_free_list_len = 0;
|
||||
new_node->val_free_list_len = 0;
|
||||
new_node->key = new_node->free = sizeof(*new_raw);
|
||||
new_node->data = sb->s_blocksize; /* Nonroot */
|
||||
new_node->val = sb->s_blocksize; /* Nonroot */
|
||||
|
||||
prev_raw = (void *)prev_node->object.data;
|
||||
new_raw = (void *)new_node->object.data;
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+408
-178
File diff suppressed because it is too large.
Load diff
+201
-146
File diff suppressed because it is too large.
Load diff
@@ -12,10 +12,9 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/nls.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/version.h>
|
||||
#include "unicode.h"
|
||||
|
||||
#define MIN(X, Y) ((X) <= (Y) ? (X) : (Y))
|
||||
|
||||
/* The arrays of unicode data are defined at the bottom of the file */
|
||||
/* TODO: would a single trie with all the data be more efficient? */
|
||||
static u16 apfs_nfd_trie[];
|
||||
@@ -234,7 +233,7 @@ static int apfs_get_normalization_length(const char *utf8str, unsigned int total
|
||||
while (1) {
|
||||
if (!total_len || !*utf8str)
|
||||
return norm_len;
|
||||
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
|
||||
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
|
||||
if (utf8len < 0) /* Invalid unicode; don't normalize anything */
|
||||
return 0;
|
||||
|
||||
@@ -306,7 +305,7 @@ new_starter:
|
||||
unicode_t utf32char;
|
||||
int utf8len, pos;
|
||||
|
||||
utf8len = utf8_to_utf32(utf8str, MIN(total_len, 4), &utf32char);
|
||||
utf8len = utf8_to_utf32(utf8str, min(total_len, 4U), &utf32char);
|
||||
for (pos = 0;; pos++, str_pos++) {
|
||||
unicode_t utf32norm;
|
||||
u8 ccc;
|
||||
|
||||
Loaded 20 of 21 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user