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,17 +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, 9, 0)
|
||||
struct file *nx_bdev_file;
|
||||
#elif 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 */
|
||||
@@ -254,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;
|
||||
@@ -318,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 */
|
||||
|
||||
@@ -333,11 +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)
|
||||
@@ -646,8 +694,7 @@ static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
|
||||
#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 */
|
||||
@@ -660,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 */
|
||||
};
|
||||
@@ -925,7 +972,6 @@ 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 */
|
||||
@@ -940,7 +986,6 @@ 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)
|
||||
@@ -959,7 +1004,7 @@ 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,
|
||||
@@ -967,7 +1012,6 @@ extern struct inode *apfs_new_inode(struct inode *dir, umode_t mode,
|
||||
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);
|
||||
@@ -1007,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);
|
||||
@@ -1070,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);
|
||||
@@ -1081,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,
|
||||
@@ -1097,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);
|
||||
@@ -1157,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;
|
||||
}
|
||||
@@ -1166,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;
|
||||
|
||||
@@ -1474,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.
|
||||
@@ -1495,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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PACKAGE_NAME="linux-apfs-rw"
|
||||
PACKAGE_VERSION="0.3.9"
|
||||
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
|
||||
@@ -760,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)
|
||||
{
|
||||
@@ -1138,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
|
||||
@@ -1453,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
|
||||
@@ -1651,7 +1643,7 @@ static int apfs_dstream_delete_front(struct super_block *sb, u64 ds_id)
|
||||
int ret;
|
||||
|
||||
fq = &sm_raw->sm_fq[APFS_SFQ_MAIN];
|
||||
if (le64_to_cpu(fq->sfq_count) > TRANSACTION_MAIN_QUEUE_MAX)
|
||||
if (le64_to_cpu(fq->sfq_count) > APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
return -EAGAIN;
|
||||
|
||||
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||
@@ -1710,7 +1702,7 @@ next_extent:
|
||||
}
|
||||
}
|
||||
|
||||
if (le64_to_cpu(fq->sfq_count) <= TRANSACTION_MAIN_QUEUE_MAX)
|
||||
if (le64_to_cpu(fq->sfq_count) <= APFS_TRANS_MAIN_QUEUE_MAX)
|
||||
goto next_extent;
|
||||
ret = -EAGAIN;
|
||||
out:
|
||||
@@ -1763,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;
|
||||
|
||||
@@ -1798,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);
|
||||
@@ -1911,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.
|
||||
@@ -1973,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;
|
||||
@@ -2053,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.
|
||||
@@ -2113,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;
|
||||
@@ -2297,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;
|
||||
@@ -2373,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)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <linux/blk_types.h>
|
||||
#include "apfs.h"
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
#include <linux/sched/mm.h>
|
||||
#endif
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#define MAX_PFK_LEN 512
|
||||
|
||||
#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_read_folio(struct file *file, struct folio *folio)
|
||||
{
|
||||
@@ -90,7 +90,6 @@ out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CREATE_DSTREAM_REC_MAXOPS 1
|
||||
|
||||
static int apfs_check_dstream_refcnt(struct inode *inode);
|
||||
static int apfs_put_dstream_rec(struct apfs_dstream_info *dstream);
|
||||
@@ -308,7 +307,6 @@ out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CREATE_CRYPTO_REC_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_dflt_key_class - Returns default key class for files in volume
|
||||
@@ -371,10 +369,6 @@ out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
int APFS_CRYPTO_ADJ_REFCNT_MAXOPS(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_crypto_set_key - Modify content of crypto state record
|
||||
@@ -423,7 +417,6 @@ out:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
#define APFS_CRYPTO_SET_KEY_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_crypto_get_key - Retrieve content of crypto state record
|
||||
@@ -502,7 +495,7 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
|
||||
}
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
flags = memalloc_nofs_save();
|
||||
page = grab_cache_page_write_begin(mapping, index);
|
||||
memalloc_nofs_restore(flags);
|
||||
@@ -565,7 +558,11 @@ int __apfs_write_begin(struct file *file, struct address_space *mapping, loff_t
|
||||
}
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
err = __block_write_begin(page_folio(page), pos, len, apfs_get_new_block);
|
||||
#else
|
||||
err = __block_write_begin(page, pos, len, apfs_get_new_block);
|
||||
#endif
|
||||
if (err) {
|
||||
apfs_err(sb, "CoW failed in inode 0x%llx", apfs_ino(inode));
|
||||
goto out_put_page;
|
||||
@@ -580,7 +577,11 @@ out_put_page:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len,
|
||||
struct folio **foliop, void **fsdata)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len,
|
||||
struct page **pagep, void **fsdata)
|
||||
@@ -592,29 +593,28 @@ static int apfs_write_begin(struct file *file, struct address_space *mapping,
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
int blkcount = (len + sb->s_blocksize - 1) >> inode->i_blkbits;
|
||||
struct apfs_max_ops maxops;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
struct page *page = NULL;
|
||||
struct page **pagep = &page;
|
||||
#endif
|
||||
int err;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) || RHEL_VERSION_GE(9, 3)
|
||||
unsigned int flags = 0;
|
||||
#endif
|
||||
|
||||
if (unlikely(pos >= APFS_MAX_FILE_SIZE))
|
||||
return -EFBIG;
|
||||
|
||||
maxops.cat = APFS_CREATE_DSTREAM_REC_MAXOPS +
|
||||
APFS_CREATE_CRYPTO_REC_MAXOPS +
|
||||
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)
|
||||
return err;
|
||||
|
||||
err = __apfs_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
|
||||
if (err)
|
||||
goto fail;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
*foliop = page_folio(page);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@@ -628,7 +628,11 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
|
||||
struct apfs_dstream_info *dstream = &APFS_I(inode)->i_dstream;
|
||||
int ret, err;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
ret = generic_write_end(file, mapping, pos, len, copied, page_folio(page), fsdata);
|
||||
#else
|
||||
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
#endif
|
||||
dstream->ds_size = i_size_read(inode);
|
||||
if (ret < len && pos + len > inode->i_size) {
|
||||
truncate_pagecache(inode, inode->i_size);
|
||||
@@ -641,13 +645,22 @@ int __apfs_write_end(struct file *file, struct address_space *mapping, loff_t po
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
static int apfs_write_end(struct file *file, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, unsigned int copied,
|
||||
struct folio *folio, void *fsdata)
|
||||
#else
|
||||
static 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)
|
||||
#endif
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_nx_transaction *trans = &APFS_NXI(sb)->nx_transaction;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)
|
||||
struct page *page = &folio->page;
|
||||
#endif
|
||||
int ret, err;
|
||||
|
||||
ret = __apfs_write_end(file, mapping, pos, len, copied, page, fsdata);
|
||||
@@ -670,42 +683,42 @@ fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
|
||||
static void apfs_noop_invalidatepage(struct page *page, unsigned int offset, unsigned int length)
|
||||
#else
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 3)
|
||||
static void apfs_noop_invalidate_folio(struct folio *folio, size_t offset, size_t length)
|
||||
#else
|
||||
static void apfs_noop_invalidatepage(struct page *page, unsigned int offset, unsigned int length)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
/* bmap is not implemented to avoid issues with CoW on swapfiles */
|
||||
static const struct address_space_operations apfs_aops = {
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 2)
|
||||
.dirty_folio = block_dirty_folio,
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)
|
||||
.set_page_dirty = __set_page_dirty_buffers,
|
||||
#endif
|
||||
|
||||
#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_read_folio,
|
||||
#else
|
||||
.readpage = apfs_readpage,
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
|
||||
.readahead = apfs_readahead,
|
||||
.readahead = apfs_readahead,
|
||||
#else
|
||||
.readpages = apfs_readpages,
|
||||
.readpages = apfs_readpages,
|
||||
#endif
|
||||
|
||||
.write_begin = apfs_write_begin,
|
||||
.write_end = apfs_write_end,
|
||||
|
||||
/* The intention is to keep bhs around until the transaction is over */
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 18, 0)
|
||||
.invalidatepage = apfs_noop_invalidatepage,
|
||||
#else
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) || RHEL_VERSION_GE(9, 3)
|
||||
.invalidate_folio = apfs_noop_invalidate_folio,
|
||||
#else
|
||||
.invalidatepage = apfs_noop_invalidatepage,
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1225,7 +1238,6 @@ fail:
|
||||
kfree(new_val);
|
||||
return err;
|
||||
}
|
||||
#define APFS_INODE_RENAME_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_create_dstream_xfield - Create the inode xfield for a new data stream
|
||||
@@ -1280,7 +1292,6 @@ fail:
|
||||
kfree(new_val);
|
||||
return err;
|
||||
}
|
||||
#define APFS_CREATE_DSTREAM_XFIELD_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_inode_resize - Update the sizes reported in an inode record
|
||||
@@ -1331,7 +1342,6 @@ static int apfs_inode_resize(struct inode *inode, struct apfs_query *query)
|
||||
/* This inode has no dstream xfield, so we need to create it */
|
||||
return apfs_create_dstream_xfield(inode, query);
|
||||
}
|
||||
#define APFS_INODE_RESIZE_MAXOPS (1 + APFS_CREATE_DSTREAM_XFIELD_MAXOPS)
|
||||
|
||||
/**
|
||||
* apfs_create_sparse_xfield - Create an inode xfield to count sparse bytes
|
||||
@@ -1535,10 +1545,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return err;
|
||||
}
|
||||
int APFS_UPDATE_INODE_MAXOPS(void)
|
||||
{
|
||||
return APFS_INODE_RENAME_MAXOPS + APFS_INODE_RESIZE_MAXOPS + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_delete_inode - Delete an inode record
|
||||
@@ -1622,7 +1628,6 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
ai->i_cleaned = true;
|
||||
return ret;
|
||||
}
|
||||
#define APFS_DELETE_INODE_MAXOPS 1
|
||||
|
||||
/**
|
||||
* apfs_clean_single_orphan - Clean the given orphan file
|
||||
@@ -1634,12 +1639,11 @@ static int apfs_delete_inode(struct inode *inode)
|
||||
static int apfs_clean_single_orphan(struct inode *inode)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops = {0}; /* TODO: rethink this stuff */
|
||||
u64 ino = apfs_ino(inode);
|
||||
bool eagain = false;
|
||||
int err;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_DEL);
|
||||
if (err)
|
||||
return err;
|
||||
err = apfs_delete_inode(inode);
|
||||
@@ -1718,6 +1722,32 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_schedule_orphan_cleanup - Schedule cleanup for orphan inodes
|
||||
* @sb: filesystem superblock
|
||||
*/
|
||||
void apfs_schedule_orphan_cleanup(struct super_block *sb)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
|
||||
/*
|
||||
* Don't schedule cleanups during unmount: completing all of it could
|
||||
* take a while so just leave future mounts to handle the orphans.
|
||||
*/
|
||||
if (atomic_read(&sb->s_active) == 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Don't keep retrying orphan cleanups nonstop when they run into an
|
||||
* unexpected error: it won't do any good and it will flood dmesg. We
|
||||
* will retry eventually for ENOSPC, but that's handled elsewhere.
|
||||
*/
|
||||
if (atomic_read(&sbi->s_orphan_cleanup_err))
|
||||
return;
|
||||
|
||||
schedule_work(&sbi->s_orphan_cleanup_work);
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_clean_orphans - Delete as many orphan files as is reasonable
|
||||
* @sb: filesystem superblock
|
||||
@@ -1726,26 +1756,27 @@ out:
|
||||
*/
|
||||
static int apfs_clean_orphans(struct super_block *sb)
|
||||
{
|
||||
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < 100; ++i) {
|
||||
ret = apfs_clean_any_orphan(sb);
|
||||
if (ret) {
|
||||
if (ret == -ENODATA)
|
||||
return 0;
|
||||
if (ret == -EAGAIN)
|
||||
break;
|
||||
apfs_err(sb, "failed to delete an orphan file");
|
||||
return ret;
|
||||
}
|
||||
if (ret == 0)
|
||||
continue;
|
||||
if (ret == -ENODATA)
|
||||
return 0;
|
||||
if (ret == -EAGAIN)
|
||||
break;
|
||||
apfs_err(sb, "failed to delete an orphan file");
|
||||
atomic_set(&sbi->s_orphan_cleanup_err, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a file is too big, or if there are too many files, take a break
|
||||
* and continue later.
|
||||
*/
|
||||
if (atomic_read(&sb->s_active) != 0)
|
||||
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
|
||||
apfs_schedule_orphan_cleanup(sb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1761,18 +1792,15 @@ void apfs_evict_inode(struct inode *inode)
|
||||
if (!ai->i_has_dstream || ai->i_dstream.ds_size == 0) {
|
||||
/* For files with no extents, scheduled cleanup wastes time */
|
||||
err = apfs_clean_single_orphan(inode);
|
||||
if (err)
|
||||
if (err) {
|
||||
apfs_err(sb, "failed to clean orphan 0x%llx (err:%d)", apfs_ino(inode), err);
|
||||
atomic_set(&APFS_SB(sb)->s_orphan_cleanup_err, err);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the inode still has extents then schedule cleanup for the rest
|
||||
* of it. Not during unmount though: completing all cleanup could take
|
||||
* a while so just leave future mounts to handle the orphans.
|
||||
*/
|
||||
if (atomic_read(&sb->s_active))
|
||||
schedule_work(&APFS_SB(sb)->s_orphan_cleanup_work);
|
||||
/* If the inode still has extents then schedule cleanup for the rest */
|
||||
apfs_schedule_orphan_cleanup(sb);
|
||||
out:
|
||||
truncate_inode_pages_final(&inode->i_data);
|
||||
clear_inode(inode);
|
||||
@@ -1951,10 +1979,6 @@ fail:
|
||||
apfs_free_query(query);
|
||||
return ret;
|
||||
}
|
||||
int APFS_CREATE_INODE_REC_MAXOPS(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* apfs_setsize - Change the size of a regular file
|
||||
@@ -2009,12 +2033,14 @@ int apfs_setattr(struct mnt_idmap *idmap,
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
bool resizing = S_ISREG(inode->i_mode) && (iattr->ia_valid & ATTR_SIZE);
|
||||
bool shrinking = false;
|
||||
int err;
|
||||
|
||||
if (resizing && iattr->ia_size > APFS_MAX_FILE_SIZE)
|
||||
return -EFBIG;
|
||||
if (resizing && iattr->ia_size < inode->i_size)
|
||||
shrinking = true;
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
|
||||
err = setattr_prepare(dentry, iattr);
|
||||
@@ -2026,11 +2052,8 @@ int apfs_setattr(struct mnt_idmap *idmap,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
|
||||
/* TODO: figure out why ->write_inode() isn't firing */
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, shrinking ? APFS_TRANS_DEL : APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
@@ -2070,13 +2093,9 @@ int apfs_update_time(struct inode *inode, int flags)
|
||||
#endif
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
@@ -2137,7 +2156,6 @@ static int apfs_ioc_set_dir_class(struct file *file, u32 __user *user_class)
|
||||
struct inode *inode = file_inode(file);
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
u32 class;
|
||||
int err;
|
||||
|
||||
@@ -2146,10 +2164,7 @@ static int apfs_ioc_set_dir_class(struct file *file, u32 __user *user_class)
|
||||
|
||||
ai->i_key_class = class;
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
apfs_inode_join_transaction(sb, inode);
|
||||
@@ -2171,7 +2186,6 @@ static int apfs_ioc_set_pfk(struct file *file, void __user *user_pfk)
|
||||
struct apfs_crypto_state_val *pfk;
|
||||
struct apfs_inode_info *ai = APFS_I(inode);
|
||||
struct apfs_dstream_info *dstream = &ai->i_dstream;
|
||||
struct apfs_max_ops maxops;
|
||||
unsigned int key_len, key_class;
|
||||
int err;
|
||||
|
||||
@@ -2189,10 +2203,7 @@ static int apfs_ioc_set_pfk(struct file *file, void __user *user_pfk)
|
||||
}
|
||||
pfk->refcnt = cpu_to_le32(1);
|
||||
|
||||
maxops.cat = APFS_CRYPTO_SET_KEY_MAXOPS + APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err) {
|
||||
kfree(pfk);
|
||||
return err;
|
||||
@@ -2359,7 +2370,6 @@ static int apfs_ioc_getflags(struct file *file, int __user *arg)
|
||||
static int apfs_do_ioc_setflags(struct inode *inode, unsigned int newflags)
|
||||
{
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
unsigned int oldflags;
|
||||
int err;
|
||||
|
||||
@@ -2370,9 +2380,7 @@ static int apfs_do_ioc_setflags(struct inode *inode, unsigned int newflags)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -2442,7 +2450,6 @@ int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
if (sb->s_flags & SB_RDONLY)
|
||||
@@ -2455,9 +2462,7 @@ int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry,
|
||||
|
||||
lockdep_assert_held_write(&inode->i_rwsem);
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -2485,7 +2490,6 @@ int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fil
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct super_block *sb = inode->i_sb;
|
||||
struct apfs_max_ops maxops;
|
||||
int err;
|
||||
|
||||
if (sb->s_flags & SB_RDONLY)
|
||||
@@ -2498,9 +2502,7 @@ int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fil
|
||||
|
||||
lockdep_assert_held_write(&inode->i_rwsem);
|
||||
|
||||
maxops.cat = APFS_UPDATE_INODE_MAXOPS();
|
||||
maxops.blks = 0;
|
||||
err = apfs_transaction_start(sb, maxops);
|
||||
err = apfs_transaction_start(sb, APFS_TRANS_REG);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -831,7 +831,7 @@ static int apfs_node_prev(struct super_block *sb, struct apfs_query *query)
|
||||
apfs_err(sb, "bad key for index %d", query->index);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
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;
|
||||
@@ -881,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;
|
||||
@@ -913,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.
|
||||
@@ -979,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;
|
||||
}
|
||||
|
||||
@@ -993,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1071,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;
|
||||
@@ -1079,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);
|
||||
|
||||
@@ -1112,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 */
|
||||
@@ -1173,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 */);
|
||||
@@ -1199,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;
|
||||
@@ -1483,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;
|
||||
@@ -1519,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
|
||||
@@ -1616,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;
|
||||
@@ -1636,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 */);
|
||||
@@ -1650,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1856,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;
|
||||
}
|
||||
@@ -1900,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);
|
||||
@@ -2021,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;
|
||||
}
|
||||
|
||||
+360
-186
File diff suppressed because it is too large.
Load diff
+197
-141
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