You've already forked linux-apfs-rw
mirror of
https://github.com/linux-apfs/linux-apfs-rw.git
synced 2026-05-01 15:01:34 -07:00
1158bd03a6
I'm trying to unify mount option processing across kernel versions as much as possible. Instead of calling parse_options_set_flags() directly from parse_options() whenever possible, always set the new s_mount_opt and use it to call parse_options_set_flags() later. Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
1347 lines
45 KiB
C
1347 lines
45 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
|
*/
|
|
|
|
#ifndef _APFS_H
|
|
#define _APFS_H
|
|
|
|
#include <linux/buffer_head.h>
|
|
#include <linux/fs.h>
|
|
#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 */
|
|
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) /* SB_RDONLY came in 4.14 */
|
|
#define SB_RDONLY MS_RDONLY
|
|
#define SB_SILENT MS_SILENT
|
|
#define SB_NOSEC MS_NOSEC
|
|
#define SB_ACTIVE MS_ACTIVE
|
|
static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags & SB_RDONLY; }
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)
|
|
#define lockdep_assert_held_write(l) ((void)(l))
|
|
#endif
|
|
|
|
/* Compatibility wrapper around submit_bh() */
|
|
#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
|
|
|
|
/*
|
|
* Parameter for the snapshot creation ioctl
|
|
*/
|
|
struct apfs_ioctl_snap_name {
|
|
char name[APFS_SNAP_MAX_NAMELEN + 1];
|
|
};
|
|
|
|
#define APFS_IOC_SET_DFLT_PFK _IOW('@', 0x80, struct apfs_wrapped_crypto_state)
|
|
#define APFS_IOC_SET_DIR_CLASS _IOW('@', 0x81, u32)
|
|
#define APFS_IOC_SET_PFK _IOW('@', 0x82, struct apfs_wrapped_crypto_state)
|
|
#define APFS_IOC_GET_CLASS _IOR('@', 0x83, u32)
|
|
#define APFS_IOC_GET_PFK _IOR('@', 0x84, struct apfs_wrapped_crypto_state)
|
|
#define APFS_IOC_TAKE_SNAPSHOT _IOW('@', 0x85, struct apfs_ioctl_snap_name)
|
|
|
|
/*
|
|
* In-memory representation of an APFS object
|
|
*/
|
|
struct apfs_object {
|
|
struct super_block *sb;
|
|
u64 block_nr;
|
|
u64 oid;
|
|
|
|
/*
|
|
* Buffer head containing the one block of the object, may be NULL if
|
|
* the object is only in memory. TODO: support objects with more than
|
|
* one block.
|
|
*/
|
|
struct buffer_head *o_bh;
|
|
char *data; /* The raw object */
|
|
bool ephemeral; /* Is this an ephemeral object? */
|
|
};
|
|
|
|
/* Constants used in managing the size of a node's table of contents */
|
|
#define APFS_BTREE_TOC_ENTRY_INCREMENT 8
|
|
#define APFS_BTREE_TOC_ENTRY_MAX_UNUSED (2 * BTREE_TOC_ENTRY_INCREMENT)
|
|
|
|
/*
|
|
* In-memory representation of an APFS node
|
|
*/
|
|
struct apfs_node {
|
|
u32 tree_type; /* Tree type (subtype of the node object) */
|
|
u16 flags; /* Node flags */
|
|
u32 records; /* Number of records in the node */
|
|
|
|
int key; /* Offset of the key area in the block */
|
|
int free; /* Offset of the free 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 */
|
|
|
|
struct apfs_object object; /* Object holding the node */
|
|
};
|
|
|
|
/**
|
|
* apfs_node_is_leaf - Check if a b-tree node is a leaf
|
|
* @node: the node to check
|
|
*/
|
|
static inline bool apfs_node_is_leaf(struct apfs_node *node)
|
|
{
|
|
return (node->flags & APFS_BTNODE_LEAF) != 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_is_root - Check if a b-tree node is the root
|
|
* @node: the node to check
|
|
*/
|
|
static inline bool apfs_node_is_root(struct apfs_node *node)
|
|
{
|
|
return (node->flags & APFS_BTNODE_ROOT) != 0;
|
|
}
|
|
|
|
/**
|
|
* apfs_node_has_fixed_kv_size - Check if a b-tree node has fixed key/value
|
|
* sizes
|
|
* @node: the node to check
|
|
*/
|
|
static inline bool apfs_node_has_fixed_kv_size(struct apfs_node *node)
|
|
{
|
|
return (node->flags & APFS_BTNODE_FIXED_KV_SIZE) != 0;
|
|
}
|
|
|
|
struct apfs_ip_bitmap_block_info {
|
|
bool dirty; /* Do we need to commit this block to disk? */
|
|
void *block; /* In-memory address of the bitmap block */
|
|
};
|
|
|
|
/*
|
|
* Space manager data in memory.
|
|
*/
|
|
struct apfs_spaceman {
|
|
struct apfs_spaceman_phys *sm_raw; /* On-disk spaceman structure */
|
|
struct apfs_nxsb_info *sm_nxi; /* Container superblock */
|
|
u32 sm_size; /* Size of @sm_raw in bytes */
|
|
|
|
u32 sm_blocks_per_chunk; /* Blocks covered by a bitmap block */
|
|
u32 sm_chunks_per_cib; /* Chunk count in a chunk-info block */
|
|
u64 sm_block_count; /* Block count for the container */
|
|
u64 sm_chunk_count; /* Number of bitmap blocks */
|
|
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
|
|
* much as possible before creating an actual record.
|
|
*/
|
|
u64 sm_free_cache_base;
|
|
u64 sm_free_cache_blkcnt;
|
|
|
|
/* Shift to match an ip block with its bitmap in the array */
|
|
int sm_ip_bmaps_shift;
|
|
/* Mask to find an ip block's offset inside its ip bitmap */
|
|
u32 sm_ip_bmaps_mask;
|
|
/* Number of ip bitmaps */
|
|
u32 sm_ip_bmaps_count;
|
|
/* List of ip bitmaps, in order */
|
|
struct apfs_ip_bitmap_block_info sm_ip_bmaps[];
|
|
};
|
|
|
|
#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 */
|
|
#define APFS_NX_TRANS_DEFER_COMMIT 2 /* Commit banned right now */
|
|
#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 */
|
|
};
|
|
|
|
/* 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 */
|
|
BUFFER_FNS(TRANS, trans);
|
|
BUFFER_FNS(CSUM, csum);
|
|
|
|
/*
|
|
* Additional information for a buffer in a transaction.
|
|
*/
|
|
struct apfs_bh_info {
|
|
struct buffer_head *bh; /* The buffer head */
|
|
struct list_head list; /* List of buffers in the transaction */
|
|
};
|
|
|
|
/*
|
|
* List entry for an in-memory ephemeral object
|
|
*/
|
|
struct apfs_ephemeral_object_info {
|
|
u64 oid; /* Ephemeral object id */
|
|
u32 size; /* Size of the object in bytes */
|
|
void *object; /* In-memory address of the object */
|
|
};
|
|
|
|
/*
|
|
* We allocate a fixed space for the list of ephemeral objects. I don't
|
|
* actually know how big this should be allowed to get, but all the objects
|
|
* must be written down with each transaction commit, so probably not too big.
|
|
*/
|
|
#define APFS_EPHEMERAL_LIST_SIZE 32768
|
|
#define APFS_EPHEMERAL_LIST_LIMIT (APFS_EPHEMERAL_LIST_SIZE / sizeof(struct apfs_ephemeral_object_info))
|
|
|
|
/* Mount option flags for a container */
|
|
#define APFS_CHECK_NODES 1
|
|
#define APFS_READWRITE 2
|
|
/*
|
|
* Mount options for a container are decided on its first mount. This flag lets
|
|
* future mounts know that has happened already.
|
|
*/
|
|
#define APFS_FLAGS_SET 4
|
|
|
|
/*
|
|
* Wrapper around block devices for portability.
|
|
*/
|
|
struct apfs_blkdev_info {
|
|
struct block_device *blki_bdev;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0) || RHEL_VERSION_GE(9, 5)
|
|
struct file *blki_bdev_file;
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
|
|
struct bdev_handle *blki_bdev_handle;
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0) && !RHEL_VERSION_GE(9, 4)
|
|
fmode_t blki_mode;
|
|
#endif
|
|
|
|
/*
|
|
* For tier 2, we need to remember the mount path for show_options().
|
|
* We don't need this for the main device of course, but better to
|
|
* keep things consistent.
|
|
*/
|
|
char *blki_path;
|
|
};
|
|
|
|
/*
|
|
* Container superblock data in memory
|
|
*/
|
|
struct apfs_nxsb_info {
|
|
/* 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 */
|
|
|
|
/* List of ephemeral objects in memory (except the superblock) */
|
|
struct apfs_ephemeral_object_info *nx_eph_list;
|
|
int nx_eph_count;
|
|
|
|
struct list_head vol_list; /* List of mounted volumes in container */
|
|
|
|
unsigned int nx_flags; /* Mount options shared by all volumes */
|
|
unsigned int nx_refcnt; /* Number of mounted volumes in container */
|
|
|
|
/* TODO: handle block sizes above the maximum of PAGE_SIZE? */
|
|
unsigned long nx_blocksize;
|
|
unsigned char nx_blocksize_bits;
|
|
|
|
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;
|
|
|
|
/* List of currently mounted containers */
|
|
struct list_head nx_list;
|
|
};
|
|
|
|
extern struct mutex nxs_mutex;
|
|
|
|
/*
|
|
* Omap mapping in memory.
|
|
* TODO: could this and apfs_omap_rec be the same struct?
|
|
*/
|
|
struct apfs_omap_map {
|
|
u64 xid;
|
|
u64 bno;
|
|
u32 flags;
|
|
};
|
|
|
|
/*
|
|
* Omap record data in memory
|
|
*/
|
|
struct apfs_omap_rec {
|
|
u64 oid;
|
|
u64 bno;
|
|
};
|
|
|
|
#define APFS_OMAP_CACHE_SLOTS 128
|
|
#define APFS_OMAP_CACHE_SLOT_MASK (APFS_OMAP_CACHE_SLOTS - 1)
|
|
|
|
/**
|
|
* Cache of omap records
|
|
*/
|
|
struct apfs_omap_cache {
|
|
struct apfs_omap_rec recs[APFS_OMAP_CACHE_SLOTS];
|
|
bool disabled;
|
|
spinlock_t lock;
|
|
};
|
|
|
|
/*
|
|
* Omap structure shared by all snapshots for the same volume.
|
|
*/
|
|
struct apfs_omap {
|
|
struct apfs_node *omap_root;
|
|
struct apfs_omap_cache omap_cache;
|
|
|
|
/* Transaction id for most recent snapshot */
|
|
u64 omap_latest_snap;
|
|
|
|
/* Number of snapshots sharing this omap */
|
|
unsigned int omap_refcnt;
|
|
};
|
|
|
|
/*
|
|
* Volume superblock data in memory
|
|
*/
|
|
struct apfs_sb_info {
|
|
struct apfs_nxsb_info *s_nxi; /* In-memory container sb for volume */
|
|
struct list_head list; /* List of mounted volumes in container */
|
|
struct apfs_superblock *s_vsb_raw; /* On-disk volume sb */
|
|
|
|
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 */
|
|
|
|
struct apfs_node *s_cat_root; /* Root of the catalog tree */
|
|
struct apfs_omap *s_omap; /* The object map */
|
|
|
|
struct apfs_object s_vobject; /* Volume superblock object */
|
|
|
|
/* Mount options */
|
|
unsigned int s_vol_nr; /* Index of the volume in the sb list */
|
|
kuid_t s_uid; /* uid to override on-disk uid */
|
|
kgid_t s_gid; /* gid to override on-disk gid */
|
|
unsigned int s_mount_opt;
|
|
|
|
struct apfs_crypto_state_val *s_dflt_pfk; /* default per-file key */
|
|
|
|
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)
|
|
{
|
|
return sb->s_fs_info;
|
|
}
|
|
|
|
static inline bool apfs_is_sealed(struct super_block *sb)
|
|
{
|
|
u64 flags = le64_to_cpu(APFS_SB(sb)->s_vsb_raw->apfs_incompatible_features);
|
|
|
|
return flags & APFS_INCOMPAT_SEALED_VOLUME;
|
|
}
|
|
|
|
/**
|
|
* apfs_vol_is_encrypted - Check if a volume is encrypting files
|
|
* @sb: superblock
|
|
*/
|
|
static inline bool apfs_vol_is_encrypted(struct super_block *sb)
|
|
{
|
|
struct apfs_superblock *vsb_raw = APFS_SB(sb)->s_vsb_raw;
|
|
|
|
return (vsb_raw->apfs_fs_flags & cpu_to_le64(APFS_FS_UNENCRYPTED)) == 0;
|
|
}
|
|
|
|
/**
|
|
* APFS_NXI - Get the shared container info for a volume's superblock
|
|
* @sb: superblock structure
|
|
*/
|
|
static inline struct apfs_nxsb_info *APFS_NXI(struct super_block *sb)
|
|
{
|
|
return APFS_SB(sb)->s_nxi;
|
|
}
|
|
|
|
/**
|
|
* APFS_SM - Get the shared spaceman struct for a volume's superblock
|
|
* @sb: superblock structure
|
|
*/
|
|
static inline struct apfs_spaceman *APFS_SM(struct super_block *sb)
|
|
{
|
|
return APFS_NXI(sb)->nx_spaceman;
|
|
}
|
|
|
|
static inline bool apfs_is_case_insensitive(struct super_block *sb)
|
|
{
|
|
return (APFS_SB(sb)->s_vsb_raw->apfs_incompatible_features &
|
|
cpu_to_le64(APFS_INCOMPAT_CASE_INSENSITIVE)) != 0;
|
|
}
|
|
|
|
static inline bool apfs_is_normalization_insensitive(struct super_block *sb)
|
|
{
|
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
|
u64 flags = le64_to_cpu(sbi->s_vsb_raw->apfs_incompatible_features);
|
|
|
|
if (apfs_is_case_insensitive(sb))
|
|
return true;
|
|
if (flags & APFS_INCOMPAT_NORMALIZATION_INSENSITIVE)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* apfs_max_maps_per_block - Find the maximum map count for a mapping block
|
|
* @sb: superblock structure
|
|
*/
|
|
static inline int apfs_max_maps_per_block(struct super_block *sb)
|
|
{
|
|
unsigned long maps_size;
|
|
|
|
maps_size = (sb->s_blocksize - sizeof(struct apfs_checkpoint_map_phys));
|
|
return maps_size / sizeof(struct apfs_checkpoint_mapping);
|
|
}
|
|
|
|
/*
|
|
* In-memory representation of a key, as relevant for a b-tree query.
|
|
*/
|
|
struct apfs_key {
|
|
u64 id;
|
|
u64 number; /* Extent offset, name hash or transaction id */
|
|
const char *name; /* On-disk name string */
|
|
u8 type; /* Record type (0 for the omap) */
|
|
};
|
|
|
|
/**
|
|
* apfs_init_free_queue_key - Initialize an in-memory key for a free queue query
|
|
* @xid: transaction id
|
|
* @paddr: block number
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_free_queue_key(u64 xid, u64 paddr,
|
|
struct apfs_key *key)
|
|
{
|
|
key->id = xid;
|
|
key->type = 0;
|
|
key->number = paddr;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_omap_key - Initialize an in-memory key for an omap query
|
|
* @oid: object id
|
|
* @xid: latest transaction id
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_omap_key(u64 oid, u64 xid, struct apfs_key *key)
|
|
{
|
|
key->id = oid;
|
|
key->type = 0;
|
|
key->number = xid;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_extent_key - Initialize an in-memory key for an extentref query
|
|
* @bno: physical block number for the start of the extent
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_extent_key(u64 bno, struct apfs_key *key)
|
|
{
|
|
key->id = bno;
|
|
key->type = APFS_TYPE_EXTENT;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_inode_key - Initialize an in-memory key for an inode query
|
|
* @ino: inode number
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_inode_key(u64 ino, struct apfs_key *key)
|
|
{
|
|
key->id = ino;
|
|
key->type = APFS_TYPE_INODE;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_file_extent_key - Initialize an in-memory key for an extent query
|
|
* @id: extent id
|
|
* @offset: logical address (0 for a multiple query)
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_file_extent_key(u64 id, u64 offset,
|
|
struct apfs_key *key)
|
|
{
|
|
key->id = id;
|
|
key->type = APFS_TYPE_FILE_EXTENT;
|
|
key->number = offset;
|
|
key->name = NULL;
|
|
}
|
|
|
|
static inline void apfs_init_fext_key(u64 id, u64 offset, struct apfs_key *key)
|
|
{
|
|
key->id = id;
|
|
key->type = 0;
|
|
key->number = offset;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_dstream_id_key - Initialize an in-memory key for a dstream query
|
|
* @id: data stream id
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_dstream_id_key(u64 id, struct apfs_key *key)
|
|
{
|
|
key->id = id;
|
|
key->type = APFS_TYPE_DSTREAM_ID;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_crypto_state_key - Initialize an in-memory key for a crypto query
|
|
* @id: crypto state id
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_crypto_state_key(u64 id, struct apfs_key *key)
|
|
{
|
|
key->id = id;
|
|
key->type = APFS_TYPE_CRYPTO_STATE;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_sibling_link_key - Initialize an in-memory key for a sibling query
|
|
* @ino: inode number
|
|
* @id: sibling id
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_sibling_link_key(u64 ino, u64 id,
|
|
struct apfs_key *key)
|
|
{
|
|
key->id = ino;
|
|
key->type = APFS_TYPE_SIBLING_LINK;
|
|
key->number = id; /* Only guessing (TODO) */
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_init_sibling_map_key - Initialize in-memory key for a sibling map query
|
|
* @id: sibling id
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_sibling_map_key(u64 id, struct apfs_key *key)
|
|
{
|
|
key->id = id;
|
|
key->type = APFS_TYPE_SIBLING_MAP;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
extern void apfs_init_drec_key(struct super_block *sb, u64 ino, const char *name,
|
|
unsigned int name_len, struct apfs_key *key);
|
|
|
|
/**
|
|
* apfs_init_xattr_key - Initialize an in-memory key for a xattr query
|
|
* @ino: inode number of the parent file
|
|
* @name: xattr name (NULL for a multiple query)
|
|
* @key: apfs_key structure to initialize
|
|
*/
|
|
static inline void apfs_init_xattr_key(u64 ino, const char *name,
|
|
struct apfs_key *key)
|
|
{
|
|
key->id = ino;
|
|
key->type = APFS_TYPE_XATTR;
|
|
key->number = 0;
|
|
key->name = name;
|
|
}
|
|
|
|
static inline void apfs_init_snap_metadata_key(u64 xid, struct apfs_key *key)
|
|
{
|
|
key->id = xid;
|
|
key->type = APFS_TYPE_SNAP_METADATA;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
static inline void apfs_init_snap_name_key(const char *name, struct apfs_key *key)
|
|
{
|
|
key->id = APFS_SNAP_NAME_OBJ_ID;
|
|
key->type = APFS_TYPE_SNAP_NAME;
|
|
key->number = 0;
|
|
key->name = name;
|
|
}
|
|
|
|
static inline void apfs_init_omap_snap_key(u64 xid, struct apfs_key *key)
|
|
{
|
|
key->id = xid;
|
|
key->type = 0;
|
|
key->number = 0;
|
|
key->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* apfs_key_set_hdr - Set the header for a raw catalog key
|
|
* @type: record type
|
|
* @id: record id
|
|
* @key: the key to initialize
|
|
*/
|
|
static inline void apfs_key_set_hdr(u64 type, u64 id, void *key)
|
|
{
|
|
struct apfs_key_header *hdr = key;
|
|
|
|
hdr->obj_id_and_type = cpu_to_le64(id | type << APFS_OBJ_TYPE_SHIFT);
|
|
}
|
|
|
|
/**
|
|
* apfs_cat_type - Read the record type of a catalog key
|
|
* @key: the raw catalog key
|
|
*/
|
|
static inline int apfs_cat_type(struct apfs_key_header *key)
|
|
{
|
|
return (le64_to_cpu(key->obj_id_and_type) & APFS_OBJ_TYPE_MASK) >> APFS_OBJ_TYPE_SHIFT;
|
|
}
|
|
|
|
/**
|
|
* apfs_cat_cnid - Read the cnid value on a catalog key
|
|
* @key: the raw catalog key
|
|
*
|
|
* TODO: rename this function, since it's not just for the catalog anymore
|
|
*/
|
|
static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
|
|
{
|
|
return le64_to_cpu(key->obj_id_and_type) & APFS_OBJ_ID_MASK;
|
|
}
|
|
|
|
/* Flags for the query structure */
|
|
#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.
|
|
*/
|
|
struct apfs_query {
|
|
struct apfs_node *node; /* Node being searched */
|
|
struct apfs_key key; /* What the query is looking for */
|
|
|
|
struct apfs_query *parent; /* Query for parent node */
|
|
unsigned int flags;
|
|
|
|
/* Set by the query on success */
|
|
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 value in the node */
|
|
int len; /* Length of the value */
|
|
|
|
int depth; /* Put a limit on recursion */
|
|
};
|
|
|
|
/**
|
|
* apfs_query_storage - Get the storage type for a query's btree
|
|
* @query: the query structure
|
|
*/
|
|
static inline u32 apfs_query_storage(struct apfs_query *query)
|
|
{
|
|
if (query->flags & APFS_QUERY_OMAP)
|
|
return APFS_OBJ_PHYSICAL;
|
|
if (query->flags & APFS_QUERY_CAT)
|
|
return APFS_OBJ_VIRTUAL;
|
|
if (query->flags & APFS_QUERY_FEXT)
|
|
return APFS_OBJ_PHYSICAL;
|
|
if (query->flags & APFS_QUERY_FREE_QUEUE)
|
|
return APFS_OBJ_EPHEMERAL;
|
|
if (query->flags & APFS_QUERY_EXTENTREF)
|
|
return APFS_OBJ_PHYSICAL;
|
|
if (query->flags & APFS_QUERY_SNAP_META)
|
|
return APFS_OBJ_PHYSICAL;
|
|
if (query->flags & APFS_QUERY_OMAP_SNAP)
|
|
return APFS_OBJ_PHYSICAL;
|
|
|
|
/* Absurd, but don't panic: let the callers fail and report it */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Extent record data in memory
|
|
*/
|
|
struct apfs_file_extent {
|
|
u64 logical_addr;
|
|
u64 phys_block_num;
|
|
u64 len;
|
|
u64 crypto_id;
|
|
};
|
|
|
|
/*
|
|
* Physical extent record data in memory
|
|
*/
|
|
struct apfs_phys_extent {
|
|
u64 bno;
|
|
u64 blkcount;
|
|
u64 len; /* In bytes */
|
|
u32 refcnt;
|
|
u8 kind;
|
|
};
|
|
|
|
/*
|
|
* Data stream info in memory
|
|
*/
|
|
struct apfs_dstream_info {
|
|
struct super_block *ds_sb; /* Filesystem superblock */
|
|
struct inode *ds_inode; /* NULL for xattr dstreams */
|
|
u64 ds_id; /* ID of the extent records */
|
|
u64 ds_size; /* Length of the stream */
|
|
u64 ds_sparse_bytes;/* Hole byte count in stream */
|
|
struct apfs_file_extent ds_cached_ext; /* Latest extent record */
|
|
bool ds_ext_dirty; /* Is ds_cached_ext dirty? */
|
|
spinlock_t ds_ext_lock; /* Protects ds_cached_ext */
|
|
bool ds_shared; /* Has multiple references? */
|
|
};
|
|
|
|
/**
|
|
* apfs_alloced_size - Return the alloced size for a data stream
|
|
* @dstream: data stream info
|
|
*
|
|
* TODO: is this always correct? Or could the extents have an unused tail?
|
|
*/
|
|
static inline u64 apfs_alloced_size(struct apfs_dstream_info *dstream)
|
|
{
|
|
struct super_block *sb = dstream->ds_sb;
|
|
u64 blks = (dstream->ds_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
|
|
return blks << sb->s_blocksize_bits;
|
|
}
|
|
|
|
/*
|
|
* APFS inode data in memory
|
|
*/
|
|
struct apfs_inode_info {
|
|
u64 i_ino64; /* 32-bit-safe inode number */
|
|
u64 i_parent_id; /* ID of primary parent */
|
|
struct timespec64 i_crtime; /* Time of creation */
|
|
u32 i_nchildren; /* Child count for directory */
|
|
uid_t i_saved_uid; /* User ID on disk */
|
|
gid_t i_saved_gid; /* Group ID on disk */
|
|
u32 i_key_class; /* Security class for directory */
|
|
u64 i_int_flags; /* Internal flags */
|
|
u32 i_bsd_flags; /* BSD flags */
|
|
struct list_head i_list; /* List of inodes in transaction */
|
|
|
|
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;
|
|
};
|
|
|
|
static inline struct apfs_inode_info *APFS_I(const struct inode *inode)
|
|
{
|
|
return container_of(inode, struct apfs_inode_info, vfs_inode);
|
|
}
|
|
|
|
/**
|
|
* apfs_ino - Get the 64-bit id of an inode
|
|
* @inode: the vfs inode
|
|
*
|
|
* Returns all 64 bits of @inode's id, even on 32-bit architectures.
|
|
*/
|
|
static inline u64 apfs_ino(const struct inode *inode)
|
|
{
|
|
return APFS_I(inode)->i_ino64;
|
|
}
|
|
|
|
/**
|
|
* apfs_set_ino - Set a 64-bit id on an inode
|
|
* @inode: the vfs inode
|
|
* @id: id to set
|
|
*
|
|
* Sets both the vfs inode number and the actual 32-bit-safe id.
|
|
*/
|
|
static inline void apfs_set_ino(struct inode *inode, u64 id)
|
|
{
|
|
inode->i_ino = id; /* Higher bits may be lost, but it doesn't matter */
|
|
APFS_I(inode)->i_ino64 = id;
|
|
}
|
|
|
|
/* Make the compiler complain if we ever access i_ino directly by mistake */
|
|
#define i_ino DONT_USE_I_INO
|
|
|
|
/*
|
|
* Directory entry record in memory
|
|
*/
|
|
struct apfs_drec {
|
|
u8 *name;
|
|
u64 ino;
|
|
u64 sibling_id; /* The sibling id; 0 if none */
|
|
int name_len;
|
|
unsigned int type;
|
|
};
|
|
|
|
/*
|
|
* Xattr record data in memory
|
|
*/
|
|
struct apfs_xattr {
|
|
u8 *name;
|
|
u8 *xdata;
|
|
int name_len;
|
|
int xdata_len;
|
|
bool has_dstream;
|
|
};
|
|
|
|
struct apfs_compressed_data {
|
|
bool has_dstream;
|
|
u64 size;
|
|
union {
|
|
struct apfs_dstream_info *dstream;
|
|
void *data;
|
|
};
|
|
};
|
|
|
|
/*
|
|
* Report function name and line number for the message types that are likely
|
|
* to signal a bug, to make things easier for reporters. Don't do this for the
|
|
* common messages, there is no point and it makes the console look too busy.
|
|
*/
|
|
#define apfs_emerg(sb, fmt, ...) apfs_msg(sb, KERN_EMERG, __func__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#define apfs_alert(sb, fmt, ...) apfs_msg(sb, KERN_ALERT, __func__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#define apfs_crit(sb, fmt, ...) apfs_msg(sb, KERN_CRIT, __func__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#define apfs_err(sb, fmt, ...) apfs_msg(sb, KERN_ERR, __func__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#define apfs_warn(sb, fmt, ...) apfs_msg(sb, KERN_WARNING, NULL, 0, fmt, ##__VA_ARGS__)
|
|
#define apfs_notice(sb, fmt, ...) apfs_msg(sb, KERN_NOTICE, NULL, 0, fmt, ##__VA_ARGS__)
|
|
#define apfs_info(sb, fmt, ...) apfs_msg(sb, KERN_INFO, NULL, 0, fmt, ##__VA_ARGS__)
|
|
|
|
#ifdef CONFIG_APFS_DEBUG
|
|
#define ASSERT(expr) WARN_ON(!(expr))
|
|
#define apfs_debug(sb, fmt, ...) apfs_msg(sb, KERN_DEBUG, __func__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#else
|
|
#define ASSERT(expr) ((void)0)
|
|
#define apfs_debug(sb, fmt, ...) no_printk(fmt, ##__VA_ARGS__)
|
|
#endif /* CONFIG_APFS_DEBUG */
|
|
|
|
/**
|
|
* apfs_assert_in_transaction - Assert that the object is in current transaction
|
|
* @sb: superblock structure
|
|
* @obj: on-disk object to check
|
|
*/
|
|
#define apfs_assert_in_transaction(sb, obj) \
|
|
do { \
|
|
(void)sb; \
|
|
(void)obj; \
|
|
ASSERT(le64_to_cpu((obj)->o_xid) == APFS_NXI(sb)->nx_xid); \
|
|
} while (0)
|
|
|
|
/* btree.c */
|
|
extern struct apfs_node *apfs_query_root(const struct apfs_query *query);
|
|
extern struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
|
struct apfs_query *parent);
|
|
extern void apfs_free_query(struct apfs_query *query);
|
|
extern int apfs_btree_query(struct super_block *sb, struct apfs_query **query);
|
|
extern int apfs_omap_lookup_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
|
|
extern int apfs_omap_lookup_newest_block(struct super_block *sb, struct apfs_omap *omap, u64 id, u64 *block, bool write);
|
|
extern int apfs_create_omap_rec(struct super_block *sb, u64 oid, u64 bno);
|
|
extern int apfs_delete_omap_rec(struct super_block *sb, u64 oid);
|
|
extern int apfs_query_join_transaction(struct apfs_query *query);
|
|
extern int __apfs_btree_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
|
extern int apfs_btree_insert(struct apfs_query *query, void *key, int key_len,
|
|
void *val, int val_len);
|
|
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);
|
|
|
|
/* dir.c */
|
|
extern int apfs_inode_by_name(struct inode *dir, const struct qstr *child,
|
|
u64 *ino);
|
|
extern int apfs_mkany(struct inode *dir, struct dentry *dentry,
|
|
umode_t mode, dev_t rdev, const char *symname);
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
|
|
extern int apfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
dev_t rdev);
|
|
extern int apfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode);
|
|
extern int apfs_rename(struct inode *old_dir, struct dentry *old_dentry,
|
|
struct inode *new_dir, struct dentry *new_dentry,
|
|
unsigned int flags);
|
|
extern int apfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
|
|
bool excl);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
|
|
extern int apfs_mknod(struct user_namespace *mnt_userns, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, dev_t rdev);
|
|
extern int apfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode);
|
|
extern int apfs_rename(struct user_namespace *mnt_userns, struct inode *old_dir,
|
|
struct dentry *old_dentry, struct inode *new_dir,
|
|
struct dentry *new_dentry, unsigned int flags);
|
|
extern int apfs_create(struct user_namespace *mnt_userns, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, bool excl);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0)
|
|
extern int apfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, dev_t rdev);
|
|
extern int apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode);
|
|
extern int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
|
struct dentry *old_dentry, struct inode *new_dir,
|
|
struct dentry *new_dentry, unsigned int flags);
|
|
extern int apfs_create(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, bool excl);
|
|
#else
|
|
extern int apfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, dev_t rdev);
|
|
extern struct dentry *apfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode);
|
|
extern int apfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
|
struct dentry *old_dentry, struct inode *new_dir,
|
|
struct dentry *new_dentry, unsigned int flags);
|
|
extern int apfs_create(struct mnt_idmap *idmap, struct inode *dir,
|
|
struct dentry *dentry, umode_t mode, bool excl);
|
|
#endif
|
|
|
|
extern int apfs_link(struct dentry *old_dentry, struct inode *dir,
|
|
struct dentry *dentry);
|
|
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 u64 apfs_any_orphan_ino(struct super_block *sb, u64 *ino_p);
|
|
|
|
/* extents.c */
|
|
extern int apfs_extent_from_query(struct apfs_query *query,
|
|
struct apfs_file_extent *extent);
|
|
extern int apfs_logic_to_phys_bno(struct apfs_dstream_info *dstream, sector_t dsblock, u64 *bno);
|
|
extern int __apfs_get_block(struct apfs_dstream_info *dstream, sector_t iblock,
|
|
struct buffer_head *bh_result, int create);
|
|
extern int apfs_get_block(struct inode *inode, sector_t iblock,
|
|
struct buffer_head *bh_result, int create);
|
|
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_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
|
|
extern int apfs_clone_file_range(struct file *src_file, loff_t off, struct file *dst_file, loff_t destoff, u64 len);
|
|
#endif
|
|
extern int apfs_clone_extents(struct apfs_dstream_info *dstream, u64 new_id);
|
|
extern int apfs_nonsparse_dstream_read(struct apfs_dstream_info *dstream, void *buf, size_t count, u64 offset);
|
|
extern void apfs_nonsparse_dstream_preread(struct apfs_dstream_info *dstream);
|
|
|
|
/* file.c */
|
|
extern int apfs_file_mmap(struct file *file, struct vm_area_struct *vma);
|
|
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 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);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
|
|
extern int __apfs_write_begin(const struct kiocb *iocb, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata);
|
|
extern int __apfs_write_end(const struct kiocb *iocb, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int copied, struct page *page, void *fsdata);
|
|
#else
|
|
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);
|
|
#endif
|
|
extern int apfs_dstream_adj_refcnt(struct apfs_dstream_info *dstream, u32 delta);
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
|
|
extern int apfs_setattr(struct dentry *dentry, struct iattr *iattr);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
|
|
extern int apfs_setattr(struct user_namespace *mnt_userns,
|
|
struct dentry *dentry, struct iattr *iattr);
|
|
#else
|
|
extern int apfs_setattr(struct mnt_idmap *idmap,
|
|
struct dentry *dentry, struct iattr *iattr);
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 0, 0)
|
|
extern int apfs_update_time(struct inode *inode, enum fs_update_time time, unsigned int flags);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
|
|
extern int apfs_update_time(struct inode *inode, struct timespec64 *time, int flags);
|
|
#else
|
|
extern int apfs_update_time(struct inode *inode, int flags);
|
|
#endif
|
|
long apfs_dir_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
|
long apfs_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) /* No statx yet... */
|
|
extern int apfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
|
struct kstat *stat);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0)
|
|
extern int apfs_getattr(const struct path *path, struct kstat *stat,
|
|
u32 request_mask, unsigned int query_flags);
|
|
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) && !RHEL_VERSION_GE(9, 6)
|
|
extern int apfs_getattr(struct user_namespace *mnt_userns,
|
|
const struct path *path, struct kstat *stat, u32 request_mask,
|
|
unsigned int query_flags);
|
|
#else
|
|
extern int apfs_getattr(struct mnt_idmap *idmap,
|
|
const struct path *path, struct kstat *stat, u32 request_mask,
|
|
unsigned int query_flags);
|
|
#endif
|
|
|
|
extern int apfs_crypto_adj_refcnt(struct super_block *sb, u64 crypto_id, int delta);
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 17, 0)
|
|
#define fileattr file_kattr
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) || RHEL_VERSION_GE(9, 6)
|
|
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
|
|
extern int apfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fileattr *fa);
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)
|
|
extern int apfs_fileattr_get(struct dentry *dentry, struct fileattr *fa);
|
|
extern int apfs_fileattr_set(struct user_namespace *mnt_userns, struct dentry *dentry, struct fileattr *fa);
|
|
#endif
|
|
|
|
/* key.c */
|
|
extern int apfs_filename_cmp(struct super_block *sb, const char *name1, unsigned int len1, const char *name2, unsigned int len2);
|
|
extern int apfs_keycmp(struct apfs_key *k1, struct apfs_key *k2);
|
|
extern int apfs_read_cat_key(void *raw, int size, struct apfs_key *key, bool hashed);
|
|
extern int apfs_read_fext_key(void *raw, int size, struct apfs_key *key);
|
|
extern int apfs_read_free_queue_key(void *raw, int size, struct apfs_key *key);
|
|
extern int apfs_read_omap_key(void *raw, int size, struct apfs_key *key);
|
|
extern int apfs_read_extentref_key(void *raw, int size, struct apfs_key *key);
|
|
extern int apfs_read_snap_meta_key(void *raw, int size, struct apfs_key *key);
|
|
extern int apfs_read_omap_snap_key(void *raw, int size, struct apfs_key *key);
|
|
|
|
/* message.c */
|
|
extern __printf(5, 6) void apfs_msg(struct super_block *sb, const char *prefix, const char *func, int line, const char *fmt, ...);
|
|
|
|
/* node.c */
|
|
extern struct apfs_node *apfs_read_node(struct super_block *sb, u64 oid,
|
|
u32 storage, bool write);
|
|
extern void apfs_update_node(struct apfs_node *node);
|
|
extern int apfs_delete_node(struct apfs_node *node, int type);
|
|
extern int apfs_node_query(struct super_block *sb, struct apfs_query *query);
|
|
extern void apfs_node_query_first(struct apfs_query *query);
|
|
extern int apfs_omap_map_from_query(struct apfs_query *query, struct apfs_omap_map *map);
|
|
extern int apfs_node_split(struct apfs_query *query);
|
|
extern int apfs_node_locate_key(struct apfs_node *node, int index, int *off);
|
|
extern void apfs_node_free(struct apfs_node *node);
|
|
extern void apfs_node_free_range(struct apfs_node *node, u16 off, u16 len);
|
|
extern bool apfs_node_has_room(struct apfs_node *node, int length, bool replace);
|
|
extern int apfs_node_replace(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
|
extern int apfs_node_insert(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
|
extern int apfs_create_single_rec_node(struct apfs_query *query, void *key, int key_len, void *val, int val_len);
|
|
extern int apfs_make_empty_btree_root(struct super_block *sb, u32 subtype, u64 *oid);
|
|
|
|
/* object.c */
|
|
extern int apfs_obj_verify_csum(struct super_block *sb, struct buffer_head *bh);
|
|
extern void apfs_obj_set_csum(struct super_block *sb, struct apfs_obj_phys *obj);
|
|
extern int apfs_multiblock_verify_csum(char *object, u32 size);
|
|
extern void apfs_multiblock_set_csum(char *object, u32 size);
|
|
extern int apfs_create_cpm_block(struct super_block *sb, u64 bno, struct buffer_head **bh_p);
|
|
extern int apfs_create_cpoint_map(struct super_block *sb, struct apfs_checkpoint_map_phys *cpm, struct apfs_obj_phys *obj, u64 bno, u32 size);
|
|
extern struct apfs_ephemeral_object_info *apfs_ephemeral_object_lookup(struct super_block *sb, u64 oid);
|
|
extern struct buffer_head *apfs_read_object_block(struct super_block *sb, u64 bno, bool write, bool preserve);
|
|
extern u32 apfs_index_in_data_area(struct super_block *sb, u64 bno);
|
|
extern u64 apfs_data_index_to_bno(struct super_block *sb, u32 index);
|
|
|
|
/* snapshot.c */
|
|
extern int apfs_ioc_take_snapshot(struct file *file, void __user *user_arg);
|
|
extern int apfs_switch_to_snapshot(struct super_block *sb);
|
|
|
|
/* spaceman.c */
|
|
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);
|
|
extern int apfs_map_volume_super(struct super_block *sb, bool write);
|
|
extern void apfs_unmap_volume_super(struct super_block *sb);
|
|
extern int apfs_read_omap(struct super_block *sb, bool write);
|
|
extern int apfs_read_catalog(struct super_block *sb, bool write);
|
|
extern int apfs_sync_fs(struct super_block *sb, int wait);
|
|
|
|
/* transaction.c */
|
|
extern int apfs_cpoint_data_free(struct super_block *sb, u64 bno);
|
|
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,
|
|
size_t size, bool only_whole);
|
|
extern int __apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
|
|
size_t size);
|
|
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 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);
|
|
extern int apfs_compressed_data_read(struct apfs_compressed_data *cdata, void *buf, size_t count, u64 offset);
|
|
|
|
/* xfield.c */
|
|
extern int apfs_find_xfield(u8 *xfields, int len, u8 xtype, char **xval);
|
|
extern int apfs_init_xfields(u8 *buffer, int buflen);
|
|
extern int apfs_insert_xfield(u8 *buffer, int buflen,
|
|
const struct apfs_x_field *xkey,
|
|
const void *xval);
|
|
|
|
/*
|
|
* Inode and file operations
|
|
*/
|
|
|
|
/* compress.c */
|
|
extern const struct address_space_operations apfs_compress_aops;
|
|
extern const struct file_operations apfs_compress_file_operations;
|
|
|
|
/* dir.c */
|
|
extern const struct file_operations apfs_dir_operations;
|
|
|
|
/* file.c */
|
|
extern const struct file_operations apfs_file_operations;
|
|
extern const struct inode_operations apfs_file_inode_operations;
|
|
|
|
/* namei.c */
|
|
extern const struct inode_operations apfs_dir_inode_operations;
|
|
extern const struct inode_operations apfs_special_inode_operations;
|
|
extern const struct dentry_operations apfs_dentry_operations;
|
|
|
|
/* symlink.c */
|
|
extern const struct inode_operations apfs_symlink_inode_operations;
|
|
|
|
/* xattr.c */
|
|
extern const struct xattr_handler *apfs_xattr_handlers[];
|
|
|
|
/**
|
|
* apfs_assert_query_is_valid - Assert that all of a query's ancestors are set
|
|
* @query: the query to check
|
|
*
|
|
* A query may lose some of its ancestors during a node split, but nothing
|
|
* should be done to such a query until it gets refreshed.
|
|
*/
|
|
static inline void apfs_assert_query_is_valid(const struct apfs_query *query)
|
|
{
|
|
ASSERT(apfs_node_is_root(apfs_query_root(query)));
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)
|
|
#define page_has_buffers(page) folio_buffers(page_folio(page))
|
|
#endif
|
|
|
|
static inline int apfs_inode_state_read_once(struct inode *inode)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 19, 0)
|
|
return inode_state_read_once(inode);
|
|
#else
|
|
return inode->i_state;
|
|
#endif
|
|
}
|
|
|
|
static inline void apfs_inode_state_set_raw(struct inode *inode, int flags)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 19, 0)
|
|
inode_state_set_raw(inode, flags);
|
|
#else
|
|
inode->i_state |= flags;
|
|
#endif
|
|
}
|
|
|
|
static inline void apfs_inode_state_clear_raw(struct inode *inode, int flags)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 19, 0)
|
|
inode_state_clear_raw(inode, flags);
|
|
#else
|
|
inode->i_state &= ~flags;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* TODO: the following are modified variants of buffer head functions that will
|
|
* work with the shared block device for the container. The correct approach
|
|
* here would be to avoid buffer heads and use bios, but for now this will do.
|
|
*/
|
|
|
|
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 = info->blki_bdev;
|
|
bh->b_blocknr = block;
|
|
bh->b_size = sb->s_blocksize;
|
|
}
|
|
|
|
static inline struct buffer_head *
|
|
apfs_sb_bread(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;
|
|
}
|
|
|
|
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 = NULL;
|
|
|
|
bh = __apfs_getblk(sb, block);
|
|
if (bh)
|
|
set_buffer_uptodate(bh);
|
|
return bh;
|
|
}
|
|
|
|
#endif /* _APFS_H */
|