diff --git a/Documentation/Makefile.in b/Documentation/Makefile.in index 76e0cbbc24..a00ddea3f6 100644 --- a/Documentation/Makefile.in +++ b/Documentation/Makefile.in @@ -23,6 +23,7 @@ endif endif mandir ?= $(prefix)/share/man +man2dir = $(mandir)/man2 man3dir = $(mandir)/man3 man5dir = $(mandir)/man5 man8dir = $(mandir)/man8 @@ -44,8 +45,10 @@ help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) install: man + $(INSTALL) -d -m 755 $(DESTDIR)$(man2dir) $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir) $(INSTALL) -d -m 755 $(DESTDIR)$(man8dir) + $(INSTALL) -m 644 $(BUILDDIR)/man/*.2 $(DESTDIR)$(man2dir) $(INSTALL) -m 644 $(BUILDDIR)/man/*.5 $(DESTDIR)$(man5dir) $(INSTALL) -m 644 $(BUILDDIR)/man/*.8 $(DESTDIR)$(man8dir) $(INSTALL) -m 644 btrfsck.8 $(DESTDIR)$(man8dir) diff --git a/Documentation/btrfs-man5.rst b/Documentation/btrfs-man5.rst index ca9a4efe41..56f9c939d6 100644 --- a/Documentation/btrfs-man5.rst +++ b/Documentation/btrfs-man5.rst @@ -455,6 +455,7 @@ SEE ALSO :manref:`chattr(1)`, :manref:`fstrim(8)`, :manref:`ioctl(2)`, +:doc:`btrfs-ioctl`, :doc:`mkfs.btrfs`, :manref:`mount(8)`, :manref:`swapon(8)` diff --git a/Documentation/btrfs-subvolume.rst b/Documentation/btrfs-subvolume.rst index d1e89f15e1..a7bc457263 100644 --- a/Documentation/btrfs-subvolume.rst +++ b/Documentation/btrfs-subvolume.rst @@ -112,6 +112,15 @@ delete [options] [ [...]], delete -i|--subvolid -i|--subvolid subvolume id to be removed instead of the that should point to the filesystem with the subvolume + + -R|--recursive + delete subvolumes beneath each subvolume recursively + + This requires either `CAP_SYS_ADMIN` or the filesystem must be + mounted with `user_subvol_rm_allowed` mount option. + In the unprivileged case, subvolumes which cannot be accessed + are skipped. The deletion is not atomic. + -v|--verbose (deprecated) alias for global *-v* option @@ -134,7 +143,11 @@ list [options] [-G [\+|-]] [-C [+|-]] [--sort=rootid,gen,ogen,path where *ID* is subvolume's (root)id, *generation* is an internal counter which is updated every transaction, *parent_ID* is the same as the parent subvolume's id, - and *path* is the relative path of the subvolume to the top level subvolume. + and *path* is the path of the subvolume. The exact meaning of *path* + depends on the **Path filtering** option used. + + If -O or -A is given, "top level" is replaced by "parent". + The subvolume's ID may be used by the subvolume set-default command, or at mount time via the *subvolid=* option. @@ -142,12 +155,35 @@ list [options] [-G [\+|-]] [-C [+|-]] [--sort=rootid,gen,ogen,path Path filtering: + -O + Print and all subvolumes below it, recursively. + must be a subvolume. Paths are printed relative to . + + This may be used by unprivileged users, in which case this only + lists subvolumes that the user has access to. + -A + Print all subvolumes in the filesystem. Paths are printed + relative to the root of the filesystem. + + You likely always want either -O or -A. The -o and -a options and the + default if no path filtering options are given have very confusing, + accidental behavior that is only kept for backwards compatibility. + -o - Print only subvolumes below specified . Note that this is not a - recursive command, and won't show nested subvolumes under . + Print only the immediate children subvolumes of the subvolume + containing . Paths are printed relative to the root of + the filesystem. -a - print all the subvolumes in the filesystem and distinguish between - absolute and relative path with respect to the given *path*. + Print all subvolumes in the filesystem other than the root + subvolume. Paths are printed relative to the root of the + filesystem, except that subvolumes that are not an immediate + child of the subvolume containing are prefixed with + "/". + + If none of these are given, print all subvolumes in the filesystem + other than the root subvolume. Paths below the subvolume containing + are printed relative to that subvolume, and other paths are + printed relative to the root of the filesystem. Field selection: diff --git a/Documentation/conf.py b/Documentation/conf.py index 7d909165e0..cc5c4eac59 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -66,6 +66,7 @@ ('btrfs-qgroup', 'btrfs-qgroup', 'control the quota group of a btrfs filesystem', '', 8), ('btrfs-property', 'btrfs-property', 'get/set/list properties for given filesystem object', '', 8), ('btrfs-inspect-internal', 'btrfs-inspect-internal', 'query various internal information', '', 8), + ('btrfs-ioctl', 'btrfs-ioctl', 'documentation about btrfs ioctls', '', 2), ('btrfs-image', 'btrfs-image', 'create/restore an image of the filesystem', '', 8), ('btrfs-find-root', 'btrfs-find-root', 'filter to find btrfs root', '', 8), ('btrfs-filesystem', 'btrfs-filesystem', 'command group that primarily does work on the whole filesystems', '', 8), diff --git a/Documentation/dev/On-disk-format.rst b/Documentation/dev/On-disk-format.rst index 6d62d03ab3..c3d5a0ff59 100644 --- a/Documentation/dev/On-disk-format.rst +++ b/Documentation/dev/On-disk-format.rst @@ -40,14 +40,14 @@ Note that the fields are unsigned, so object ID −1 will be treated as little‐endian, a simple byte‐by‐byte comparison of KEYs will not work. - === ==== ==== =================================================== - Off Size Type Description - === ==== ==== =================================================== - 0 8 UINT Object ID. Each tree has its own set of Object IDs. - 8 1 UINT `Item type <#Item_Types>`__. - 9 8 UINT Offset. The meaning depends on the item type. - 11 - === ==== ==== =================================================== + ==== ==== ==== =================================================== + Off Size Type Description + ==== ==== ==== =================================================== + 0x0 0x8 UINT Object ID. Each tree has its own set of Object IDs. + 0x8 0x1 UINT `Item type <#Item_Types>`__. + 0x9 0x8 UINT Offset. The meaning depends on the item type. + 0x11 + ==== ==== ==== =================================================== Btrfs uses `Unix time `__. @@ -55,9 +55,9 @@ Btrfs uses `Unix time `__. === ==== ==== ======================================================== Off Size Type Description === ==== ==== ======================================================== - 0 8 SINT Number of seconds since 1970-01-01T00:00:00Z. - 8 4 UINT Number of nanoseconds since the beginning of the second. - c + 0x0 0x8 SINT Number of seconds since 1970-01-01T00:00:00Z. + 0x8 0x4 UINT Number of nanoseconds since the beginning of the second. + 0xc === ==== ==== ======================================================== Superblock @@ -75,87 +75,87 @@ otherwise, there would be confusion with other filesystems. TODO - +------+------+-------+-------------------------------------------------------------------------+ - | Off | Size | Type | Description | - +======+======+=======+=========================================================================+ - | 0 | 20 | CSUM | Checksum of everything past this field (from 20 to 1000) | - +------+------+-------+-------------------------------------------------------------------------+ - | 20 | 10 | UUID | FS UUID | - +------+------+-------+-------------------------------------------------------------------------+ - | 30 | 8 | UINT | physical address of this block (different for mirrors) | - +------+------+-------+-------------------------------------------------------------------------+ - | 38 | 8 | | flags | - +------+------+-------+-------------------------------------------------------------------------+ - | 40 | 8 | ASCII | magic ("_BHRfS_M") | - +------+------+-------+-------------------------------------------------------------------------+ - | 48 | 8 | | generation | - +------+------+-------+-------------------------------------------------------------------------+ - | 50 | 8 | | logical address of the root tree root | - +------+------+-------+-------------------------------------------------------------------------+ - | 58 | 8 | | logical address of the `chunk tree <#Chunk_tree_.283.29>`__ root | - +------+------+-------+-------------------------------------------------------------------------+ - | 60 | 8 | | logical address of the log tree root | - +------+------+-------+-------------------------------------------------------------------------+ - | 68 | 8 | | log_root_transid | - +------+------+-------+-------------------------------------------------------------------------+ - | 70 | 8 | | total_bytes | - +------+------+-------+-------------------------------------------------------------------------+ - | 78 | 8 | | bytes_used | - +------+------+-------+-------------------------------------------------------------------------+ - | 80 | 8 | | root_dir_objectid (usually 6) | - +------+------+-------+-------------------------------------------------------------------------+ - | 88 | 8 | | num_devices | - +------+------+-------+-------------------------------------------------------------------------+ - | 90 | 4 | | sectorsize | - +------+------+-------+-------------------------------------------------------------------------+ - | 94 | 4 | | nodesize | - +------+------+-------+-------------------------------------------------------------------------+ - | 98 | 4 | | leafsize | - +------+------+-------+-------------------------------------------------------------------------+ - | 9c | 4 | | stripesize | - +------+------+-------+-------------------------------------------------------------------------+ - | a0 | 4 | | sys_chunk_array_size | - +------+------+-------+-------------------------------------------------------------------------+ - | a4 | 8 | | chunk_root_generation | - +------+------+-------+-------------------------------------------------------------------------+ - | ac | 8 | | compat_flags | - +------+------+-------+-------------------------------------------------------------------------+ - | b4 | 8 | | compat_ro_flags - only implementations that support the flags can write | - | | | | to the filesystem | - +------+------+-------+-------------------------------------------------------------------------+ - | bc | 8 | | incompat_flags - only implementations that support the flags can use | - | | | | the filesystem | - +------+------+-------+-------------------------------------------------------------------------+ - | c4 | 2 | | csum_type - Btrfs currently uses the CRC32c little-endian hash function | - | | | | with seed -1. | - +------+------+-------+-------------------------------------------------------------------------+ - | c6 | 1 | | root_level | - +------+------+-------+-------------------------------------------------------------------------+ - | c7 | 1 | | chunk_root_level | - +------+------+-------+-------------------------------------------------------------------------+ - | c8 | 1 | | log_root_level | - +------+------+-------+-------------------------------------------------------------------------+ - | c9 | 62 | | `DEV_ITEM <#DEV_ITEM_.28d8.29>`__ data for this device | - +------+------+-------+-------------------------------------------------------------------------+ - | 12b | 100 | | label (may not contain '/' or '\\\\') | - +------+------+-------+-------------------------------------------------------------------------+ - | 22b | 8 | | cache_generation | - +------+------+-------+-------------------------------------------------------------------------+ - | 233 | 8 | | uuid_tree_generation | - +------+------+-------+-------------------------------------------------------------------------+ - | 23b | f0 | | reserved /\* future expansion \*/ | - +------+------+-------+-------------------------------------------------------------------------+ - | 32b | 800 | | sys_chunk_array:(*n* bytes valid) Contains (KEY, | - | | | | `CHUNK_ITEM <#CHUNK_ITEM_.28e4.29>`__) pairs for all SYSTEM chunks. | - | | | | This is needed to bootstrap the mapping from logical addresses to | - | | | | physical. | - +------+------+-------+-------------------------------------------------------------------------+ - | b2b | 2a0 | | Contain super_roots (4 btrfs_root_backup) | - +------+------+-------+-------------------------------------------------------------------------+ - | dcb | 235 | | current unused | - +------+------+-------+-------------------------------------------------------------------------+ - | 1000 | | | | - +------+------+-------+-------------------------------------------------------------------------+ + +--------+-------+-------+-------------------------------------------------------------------------+ + | Off | Size | Type | Description | + +========+=======+=======+=========================================================================+ + | 0x0 | 0x20 | CSUM | Checksum of everything past this field (from 20 to 1000) | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x20 | 0x10 | UUID | FS UUID | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x30 | 0x8 | UINT | physical address of this block (different for mirrors) | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x38 | 0x8 | | flags | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x40 | 0x8 | ASCII | magic ("_BHRfS_M") | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x48 | 0x8 | | generation | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x50 | 0x8 | | logical address of the root tree root | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x58 | 0x8 | | logical address of the `chunk tree <#Chunk_tree_.283.29>`__ root | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x60 | 0x8 | | logical address of the log tree root | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x68 | 0x8 | | log_root_transid | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x70 | 0x8 | | total_bytes | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x78 | 0x8 | | bytes_used | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x80 | 0x8 | | root_dir_objectid (usually 6) | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x88 | 0x8 | | num_devices | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x90 | 0x4 | | sectorsize | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x94 | 0x4 | | nodesize | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x98 | 0x4 | | leafsize | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x9c | 0x4 | | stripesize | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xa0 | 0x4 | | sys_chunk_array_size | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xa4 | 0x8 | | chunk_root_generation | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xac | 0x8 | | compat_flags | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xb4 | 0x8 | | compat_ro_flags - only implementations that support the flags can write | + | | | | to the filesystem | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xbc | 0x8 | | incompat_flags - only implementations that support the flags can use | + | | | | the filesystem | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xc4 | 0x2 | | csum_type - Btrfs currently uses the CRC32c little-endian hash function | + | | | | with seed -1. | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xc6 | 0x1 | | root_level | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xc7 | 0x1 | | chunk_root_level | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xc8 | 0x1 | | log_root_level | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xc9 | 0x62 | | `DEV_ITEM <#DEV_ITEM_.28d8.29>`__ data for this device | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x12b | 0x100 | | label (may not contain '/' or '\\\\') | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x22b | 0x8 | | cache_generation | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x233 | 0x8 | | uuid_tree_generation | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x23b | 0xf0 | | reserved /\* future expansion \*/ | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x2b | 0x800 | | sys_chunk_array:(*n* bytes valid) Contains (KEY, | + | | | | `CHUNK_ITEM <#CHUNK_ITEM_.28e4.29>`__) pairs for all SYSTEM chunks. | + | | | | This is needed to bootstrap the mapping from logical addresses to | + | | | | physical. | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xb2b | 0x2a0 | | Contain super_roots (4 btrfs_root_backup) | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0xdcb | 0x235 | | current unused | + +--------+-------+-------+-------------------------------------------------------------------------+ + | 0x1000 | | | | + +--------+-------+-------+-------------------------------------------------------------------------+ Header ^^^^^^ @@ -165,32 +165,32 @@ depends on whether it is an internal or leaf node, both of which are described below. - +-----+------+-------+--------------------------------------------------------------------------+ - | Off | Size | Type | Description | - +=====+======+=======+==========================================================================+ - | 0 | 20 | CSUM | Checksum of everything after this field (from 20 to the end of the node) | - +-----+------+-------+--------------------------------------------------------------------------+ - | 20 | 10 | UUID | FS UUID | - +-----+------+-------+--------------------------------------------------------------------------+ - | 30 | 8 | UINT | Logical address of this node | - +-----+------+-------+--------------------------------------------------------------------------+ - | 38 | 7 | FIELD | Flags | - +-----+------+-------+--------------------------------------------------------------------------+ - | 3f | 1 | UINT | Backref. Rev.: always 1 (MIXED) for new filesystems; 0 (OLD) indicates | - | | | | an old filesystem. | - +-----+------+-------+--------------------------------------------------------------------------+ - | 40 | 10 | UUID | Chunk tree UUID | - +-----+------+-------+--------------------------------------------------------------------------+ - | 50 | 8 | UINT | Generation | - +-----+------+-------+--------------------------------------------------------------------------+ - | 58 | 8 | UINT | The ID of the tree that contains this node | - +-----+------+-------+--------------------------------------------------------------------------+ - | 60 | 4 | UINT | Number of items | - +-----+------+-------+--------------------------------------------------------------------------+ - | 64 | 1 | UINT | Level (0 for leaf nodes) | - +-----+------+-------+--------------------------------------------------------------------------+ - | 65 | | | | - +-----+------+-------+--------------------------------------------------------------------------+ + +-------+------+-------+--------------------------------------------------------------------------+ + | Off | Size | Type | Description | + +=======+======+=======+==========================================================================+ + | 0x0 | 0x20 | CSUM | Checksum of everything after this field (from 20 to the end of the node) | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x20 | 0x10 | UUID | FS UUID | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x30 | 0x8 | UINT | Logical address of this node | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x38 | 0x7 | FIELD | Flags | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x3f | 0x1 | UINT | Backref. Rev.: always 1 (MIXED) for new filesystems; 0 (OLD) indicates | + | | | | an old filesystem. | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x40 | 0x10 | UUID | Chunk tree UUID | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x50 | 0x8 | UINT | Generation | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x58 | 0x8 | UINT | The ID of the tree that contains this node | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x60 | 0x4 | UINT | Number of items | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x64 | 0x1 | UINT | Level (0 for leaf nodes) | + +-------+------+-------+--------------------------------------------------------------------------+ + | 0x65 | | | | + +-------+------+-------+--------------------------------------------------------------------------+ Internal Node @@ -199,14 +199,14 @@ Internal Node In internal nodes, the node header is followed by a number of key pointers. - === ==== ==== ============ - Off Size Type Description - === ==== ==== ============ - 0 11 KEY key - 11 8 UINT block number - 19 8 UINT generation - 21 - === ==== ==== ============ + ===== ==== ==== ============ + Off Size Type Description + ===== ==== ==== ============ + 0x0 0x11 KEY key + 0x11 0x8 UINT block number + 0x19 0x8 UINT generation + 0x21 + ===== ==== ==== ============ ====== ======= ======= ======= === ========== @@ -222,14 +222,14 @@ data is stored at the end of the node, and the contents of the item data depends on the item type stored in the key. - === ==== ==== ========================================== - Off Size Type Description - === ==== ==== ========================================== - 0 11 KEY key - 11 4 UINT data offset relative to end of header (65) - 15 4 UINT data size - 19 - === ==== ==== ========================================== + ===== ==== ==== ========================================== + Off Size Type Description + ===== ==== ==== ========================================== + 0x0 0x11 KEY key + 0x11 0x4 UINT data offset relative to end of header (65) + 0x15 0x4 UINT data size + 0x19 + ===== ==== ==== ========================================== ====== ====== ====== === ====== ========== ====== === ====== ====== @@ -546,10 +546,10 @@ From an inode to a name in a directory. ======= ==== ===== ====================== Off Size Type Description ======= ==== ===== ====================== -0 8 UINT index in the directory -8 2 UINT (*n*) +0x0 0x8 UINT index in the directory +0x8 0x2 UINT (*n*) a *n* ASCII name in the directory -a+\ *n* +a+\ *n* ======= ==== ===== ====================== This structure can be repeated...? @@ -563,15 +563,15 @@ INODE_EXTREF (0d) From an inode to a name in a directory. Used if the regarding INODE_REF array ran out of space. *This item requires the EXTENDED_IREF feature.* -======== ==== ===== ====================== -Off Size Type Description -======== ==== ===== ====================== -0 8 UINT directory object ID -8 8 UINT index in the directory -10 2 UINT (*n*) -12 *n* ASCII name in the directory -12+\ *n* -======== ==== ===== ====================== +========== ==== ===== ====================== +Off Size Type Description +========== ==== ===== ====================== +0x0 0x8 UINT directory object ID +0x8 0x8 UINT index in the directory +0x10 0x2 UINT (*n*) +0x12 *n* ASCII name in the directory +0x12+\ *n* +========== ==== ===== ====================== This structure can be repeated...? @@ -739,37 +739,37 @@ EXTENT_DATA (6c) The contents of a file. -=== ==== ==== ====================================== -Off Size Type Description -=== ==== ==== ====================================== -0 8 UINT generation -8 8 UINT (*n*) size of decoded extent -10 1 UINT compression (0=none, 1=zlib, 2=LZO) -11 1 UINT encryption (0=none) -12 2 UINT other encoding (0=none) -14 1 UINT type (0=inline, 1=regular, 2=prealloc) -15 -=== ==== ==== ====================================== +===== ==== ==== ====================================== +Off Size Type Description +===== ==== ==== ====================================== +0x0 0x8 UINT generation +0x8 0x8 UINT (*n*) size of decoded extent +0x10 0x1 UINT compression (0=none, 1=zlib, 2=LZO) +0x11 0x1 UINT encryption (0=none) +0x12 0x2 UINT other encoding (0=none) +0x14 0x1 UINT type (0=inline, 1=regular, 2=prealloc) +0x15 +===== ==== ==== ====================================== If the extent is inline, the remaining item bytes are the data bytes (*n* bytes in case no compression/encryption/other encoding is used). Otherwise, the structure continues: -+-----+------+------+---------------------------------------------------------------------------+ -| Off | Size | Type | Description | -+=====+======+======+===========================================================================+ -| 15 | 8 | UINT | (*ea*) logical address of extent. If this is zero, the extent is sparse | -| | | | and consists of all zeroes. | -+-----+------+------+---------------------------------------------------------------------------+ -| 1d | 8 | UINT | (*es*) size of extent | -+-----+------+------+---------------------------------------------------------------------------+ -| 25 | 8 | UINT | (*o*) offset within the extent | -+-----+------+------+---------------------------------------------------------------------------+ -| 2d | 8 | UINT | (*s*) logical number of bytes in file | -+-----+------+------+---------------------------------------------------------------------------+ -| 35 | | | | -+-----+------+------+---------------------------------------------------------------------------+ ++-------+------+------+---------------------------------------------------------------------------+ +| Off | Size | Type | Description | ++=======+======+======+===========================================================================+ +| 0x15 | 0x8 | UINT | (*ea*) logical address of extent. If this is zero, the extent is sparse | +| | | | and consists of all zeroes. | ++-------+------+------+---------------------------------------------------------------------------+ +| 0x1d | 0x8 | UINT | (*es*) size of extent | ++-------+------+------+---------------------------------------------------------------------------+ +| 0x25 | 0x8 | UINT | (*o*) offset within the extent | ++-------+------+------+---------------------------------------------------------------------------+ +| 0x2d | 0x8 | UINT | (*s*) logical number of bytes in file | ++-------+------+------+---------------------------------------------------------------------------+ +| 0x35 | | | | ++-------+------+------+---------------------------------------------------------------------------+ *ea* and *es* must exactly match an EXTENT_ITEM. If the *es* bytes of data at logical address *ea* are decoded, *n* bytes will result. The file's data @@ -941,11 +941,14 @@ EXTENT_DATA_REF (b2) (logical address, b2, hash of first three fields) TODO -| ``    0   8 UINT   root objectid (id of tree contained in)`` -| ``    8   8 UINT   object id (owner)`` -| ``   10   8 UINT   offset (in the file data)`` -| ``   18   4 UINT   count (always 1?)`` - +===== ==== ==== ======================================= +Off Size Type Description +===== ==== ==== ======================================= +0x0 0x8 UINT root objectid (id of tree contained in) +0x8 0x8 UINT object id (owner) +0x10  0x8  UINT offset (in the file data) +0x18  0x4  UINT count (always 1?) +===== ==== ==== ======================================= EXTENT_REF_V0 (b4) ^^^^^^^^^^^^^^^^^^ @@ -958,12 +961,12 @@ SHARED_BLOCK_REF (b6) (logical address, b6, parent) TODO -=== ==== ==== =========== -Off Size Type Description -=== ==== ==== =========== -0 8 UINT offset -8 -=== ==== ==== =========== +===== ==== ==== =========== +Off Size Type Description +===== ==== ==== =========== +0x0 0x8 UINT offset +0x8 +===== ==== ==== =========== SHARED_DATA_REF (b8) @@ -971,13 +974,13 @@ SHARED_DATA_REF (b8) (logical address, b8, parent) TODO -=== ==== ==== ================= -Off Size Type Description -=== ==== ==== ================= -0 8 UINT offset -8 4 UINT count (always 1?) -c -=== ==== ==== ================= +===== ==== ==== ================= +Off Size Type Description +===== ==== ==== ================= +0x0 0x8 UINT offset +0x8 0x4 UINT count (always 1?) +0xc +===== ==== ==== ================= BLOCK_GROUP_ITEM (c0) @@ -1031,16 +1034,16 @@ DEV_EXTENT (cc) Maps from physical address to logical. -=== ==== ===== ======================= -Off Size Type Description -=== ==== ===== ======================= -0 8 UINT chunk tree (always 3) -8 8 OBJID chunk oid (always 256?) -10 8 UINT logical address -18 8 UINT size in bytes -20 10 UUID chunk tree UUID -30 -=== ==== ===== ======================= +===== ==== ===== ======================= +Off Size Type Description +===== ==== ===== ======================= +0x0 0x8 UINT chunk tree (always 3) +0x8 0x8 OBJID chunk oid (always 256?) +0x10 0x8 UINT logical address +0x18 0x8 UINT size in bytes +0x20 0x10 UUID chunk tree UUID +0x30 +===== ==== ===== ======================= DEV_ITEM (d8) @@ -1050,25 +1053,25 @@ DEV_ITEM (d8) Contains information about one device. -=== ==== ==== ============================== -Off Size Type Description -=== ==== ==== ============================== -0 8 UINT device id -8 8 UINT number of bytes -10 8 UINT number of bytes used -18 4 UINT optimal I/O align -1c 4 UINT optimal I/O width -20 4 UINT minimal I/O size (sector size) -24 8 UINT type -2c 8 UINT generation -34 8 UINT start offset -3c 4 UINT dev group -40 1 UINT seek speed -41 1 UINT bandwidth -42 10 UUID device UUID -52 10 UUID FS UUID -62 -=== ==== ==== ============================== +===== ==== ==== ============================== +Off Size Type Description +===== ==== ==== ============================== +0x0 0x8 UINT device id +0x8 0x8 UINT number of bytes +0x10 0x8 UINT number of bytes used +0x18 0x4 UINT optimal I/O align +0x1c 0x4 UINT optimal I/O width +0x20 0x4 UINT minimal I/O size (sector size) +0x24 0x8 UINT type +0x2c 0x8 UINT generation +0x34 0x8 UINT start offset +0x3c 0x4 UINT dev group +0x40 0x1 UINT seek speed +0x41 0x1 UINT bandwidth +0x42 0x10 UUID device UUID +0x52 0x10 UUID FS UUID +0x62 +===== ==== ==== ============================== CHUNK_ITEM (e4) diff --git a/Documentation/man-index.rst b/Documentation/man-index.rst index 36d45d2903..39c6ccfe2a 100644 --- a/Documentation/man-index.rst +++ b/Documentation/man-index.rst @@ -16,6 +16,7 @@ Manual pages btrfs-find-root btrfs-image btrfs-inspect-internal + btrfs-ioctl btrfs-map-logical btrfs-property btrfs-qgroup diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst index 3253ebf1ec..a6251afd2d 100644 --- a/Documentation/mkfs.btrfs.rst +++ b/Documentation/mkfs.btrfs.rst @@ -155,6 +155,29 @@ OPTIONS contain the files from *rootdir*. Since version 4.14.1 the filesystem size is not minimized. Please see option *--shrink* if you need that functionality. +-u|--subvol : + Specify that *subdir* is to be created as a subvolume rather than a regular + directory. The option *--rootdir* must also be specified, and *subdir* must be an + existing subdirectory within it. This option can be specified multiple times. + + *flags* is an optional comma-separated list of modifiers. Valid choices are: + + * *default*: create as default subvolume (this can only be specified once) + * *ro*: create as readonly subvolume + + If there are hard links inside *rootdir* and *subdir* will split the + subvolumes, like the following case:: + + rootdir/ + |- hardlink1 + |- hardlink2 + |- subdir/ <- will be a subvolume + |- hardlink3 + + In that case we cannot create `hardlink3` as hardlinks of + `hardlink1` and `hardlink2` because hardlink3 will be inside a new + subvolume. + --shrink Shrink the filesystem to its minimal size, only works with *--rootdir* option. diff --git a/check/main.c b/check/main.c index 205bbb4a3c..d10cf05146 100644 --- a/check/main.c +++ b/check/main.c @@ -86,6 +86,7 @@ bool no_holes = false; bool is_free_space_tree = false; bool init_extent_tree = false; bool check_data_csum = false; +static bool found_free_ino_cache = false; struct cache_tree *roots_info_cache = NULL; enum btrfs_check_mode { @@ -606,6 +607,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec) fprintf(stderr, "root %llu inode %llu errors %x", root_objectid, rec->ino, rec->errors); + if (errors & I_ERR_DEPRECATED_FREE_INO) + fprintf(stderr, ", deprecated free inode cache"); if (errors & I_ERR_NO_INODE_ITEM) fprintf(stderr, ", no inode item"); if (errors & I_ERR_NO_ORPHAN_ITEM) @@ -773,9 +776,6 @@ static struct inode_record *get_inode_rec(struct cache_tree *inode_cache, node->cache.size = 1; node->data = rec; - if (ino == BTRFS_FREE_INO_OBJECTID) - rec->found_link = 1; - ret = insert_cache_extent(inode_cache, &node->cache); if (ret) { free(rec); @@ -1745,6 +1745,13 @@ static int process_file_extent(struct btrfs_root *root, rec->errors |= I_ERR_BAD_FILE_EXTENT; if (disk_bytenr > 0) rec->found_size += num_bytes; + /* + * Symbolic links should only have inlined extents. + * A regular extent means it's already too large to + * be inlined. + */ + if (S_ISLNK(rec->imode)) + rec->errors |= I_ERR_FILE_EXTENT_TOO_LARGE; } else { rec->errors |= I_ERR_BAD_FILE_EXTENT; } @@ -3217,6 +3224,10 @@ static int check_inode_recs(struct btrfs_root *root, } } + if (rec->ino == BTRFS_FREE_INO_OBJECTID) { + rec->errors |= I_ERR_DEPRECATED_FREE_INO; + found_free_ino_cache = true; + } if (!rec->found_inode_item) rec->errors |= I_ERR_NO_INODE_ITEM; if (rec->found_link != rec->nlink) @@ -9670,6 +9681,264 @@ static int zero_log_tree(struct btrfs_root *root) return ret; } +static int check_log_csum(struct btrfs_root *root, u64 addr, u64 length) +{ + struct btrfs_path path = { 0 }; + struct btrfs_key key = { + .objectid = BTRFS_EXTENT_CSUM_OBJECTID, + .type = BTRFS_EXTENT_CSUM_KEY, + .offset = addr + }; + struct extent_buffer *leaf; + u32 num_entries; + u64 data_len; + int ret; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return ret; + + if (ret > 0 && path.slots[0]) + path.slots[0]--; + + ret = 0; + + while (1) { + leaf = path.nodes[0]; + if (path.slots[0] >= btrfs_header_nritems(leaf)) { + ret = btrfs_next_leaf(root, &path); + if (ret) { + if (ret > 0) + ret = 0; + + break; + } + leaf = path.nodes[0]; + } + + btrfs_item_key_to_cpu(leaf, &key, path.slots[0]); + + if (key.objectid > BTRFS_EXTENT_CSUM_OBJECTID) + break; + + if (key.objectid != BTRFS_EXTENT_CSUM_OBJECTID || + key.type != BTRFS_EXTENT_CSUM_KEY) + goto next; + + if (key.offset >= addr + length) + break; + + num_entries = btrfs_item_size(leaf, path.slots[0]) / gfs_info->csum_size; + data_len = num_entries * gfs_info->sectorsize; + + if (addr >= key.offset && addr <= key.offset + data_len) { + u64 end = min(addr + length, key.offset + data_len); + + length = addr + length - end; + addr = end; + + if (length == 0) + break; + } + +next: + path.slots[0]++; + } + + btrfs_release_path(&path); + + if (ret >= 0) + ret = (length != 0); + + return ret; +} + +static int check_log_root(struct btrfs_root *root, struct cache_tree *root_cache) +{ + struct btrfs_path path = { 0 }; + struct btrfs_key key = { + .objectid = BTRFS_FIRST_FREE_OBJECTID, + .type = BTRFS_INODE_ITEM_KEY, + .offset = 0 + }; + struct extent_buffer *leaf; + int ret, err = 0; + u64 last_csum_inode = 0; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret < 0) + return 1; + + while (1) { + leaf = path.nodes[0]; + if (path.slots[0] >= btrfs_header_nritems(leaf)) { + ret = btrfs_next_leaf(root, &path); + if (ret) { + if (ret < 0) + err = 1; + + break; + } + leaf = path.nodes[0]; + } + btrfs_item_key_to_cpu(leaf, &key, path.slots[0]); + + if (key.objectid == BTRFS_EXTENT_CSUM_OBJECTID) + break; + + if (key.type == BTRFS_INODE_ITEM_KEY) { + struct btrfs_inode_item *item; + + item = btrfs_item_ptr(leaf, path.slots[0], struct btrfs_inode_item); + + if (!(btrfs_inode_flags(leaf, item) & BTRFS_INODE_NODATASUM)) + last_csum_inode = key.objectid; + } else if (key.type == BTRFS_EXTENT_DATA_KEY && + key.objectid == last_csum_inode) { + struct btrfs_file_extent_item *fi; + u64 addr, length; + + fi = btrfs_item_ptr(leaf, path.slots[0], struct btrfs_file_extent_item); + + if (btrfs_file_extent_type(leaf, fi) != BTRFS_FILE_EXTENT_REG) + goto next; + + addr = btrfs_file_extent_disk_bytenr(leaf, fi) + + btrfs_file_extent_offset(leaf, fi); + length = btrfs_file_extent_num_bytes(leaf, fi); + + ret = check_log_csum(root, addr, length); + if (ret < 0) { + err = 1; + break; + } + + if (ret) { + error( +"csum missing in log (root %llu inode %llu offset %llu address 0x%llx length %llu)", + root->objectid, last_csum_inode, key.offset, + addr, length); + err = 1; + } + } + +next: + path.slots[0]++; + } + + btrfs_release_path(&path); + + return err; +} + +static int load_log_root(u64 root_id, struct btrfs_path *path, struct btrfs_root *tmp_root) +{ + struct extent_buffer *leaf; + struct btrfs_tree_parent_check check = { 0 }; + int ret; + + btrfs_setup_root(tmp_root, gfs_info, root_id); + + leaf = path->nodes[0]; + read_extent_buffer(leaf, &tmp_root->root_item, + btrfs_item_ptr_offset(leaf, path->slots[0]), + sizeof(tmp_root->root_item)); + + tmp_root->root_key.objectid = root_id; + tmp_root->root_key.type = BTRFS_ROOT_ITEM_KEY; + tmp_root->root_key.offset = 0; + + check.owner_root = btrfs_root_id(tmp_root); + check.transid = btrfs_root_generation(&tmp_root->root_item); + check.level = btrfs_root_level(&tmp_root->root_item); + + tmp_root->node = read_tree_block(gfs_info, + btrfs_root_bytenr(&tmp_root->root_item), + &check); + if (IS_ERR(tmp_root->node)) { + ret = PTR_ERR(tmp_root->node); + tmp_root->node = NULL; + return ret; + } + + if (btrfs_header_level(tmp_root->node) != btrfs_root_level(&tmp_root->root_item)) { + error("root [%llu %llu] level %d does not match %d", + tmp_root->root_key.objectid, + tmp_root->root_key.offset, + btrfs_header_level(tmp_root->node), + btrfs_root_level(&tmp_root->root_item)); + return -EIO; + } + + return 0; +} + +static int check_log(struct cache_tree *root_cache) +{ + struct btrfs_path path = { 0 }; + struct btrfs_key key = { + .objectid = BTRFS_TREE_LOG_OBJECTID, + .type = BTRFS_ROOT_ITEM_KEY, + .offset = 0 + }; + struct extent_buffer *leaf; + struct btrfs_root *log_root = gfs_info->log_root_tree; + int ret; + int err = 0; + + ret = btrfs_search_slot(NULL, log_root, &key, &path, 0, 0); + if (ret < 0) { + err = 1; + goto out; + } + + while (1) { + leaf = path.nodes[0]; + if (path.slots[0] >= btrfs_header_nritems(leaf)) { + ret = btrfs_next_leaf(log_root, &path); + if (ret) { + if (ret < 0) + err = 1; + break; + } + leaf = path.nodes[0]; + } + btrfs_item_key_to_cpu(leaf, &key, path.slots[0]); + + if (key.objectid > BTRFS_TREE_LOG_OBJECTID || + key.type > BTRFS_ROOT_ITEM_KEY) + break; + + if (key.objectid == BTRFS_TREE_LOG_OBJECTID && + key.type == BTRFS_ROOT_ITEM_KEY && + fs_root_objectid(key.offset)) { + struct btrfs_root tmp_root; + + memset(&tmp_root, 0, sizeof(tmp_root)); + + ret = load_log_root(key.offset, &path, &tmp_root); + if (ret) { + err = 1; + goto next; + } + + ret = check_log_root(&tmp_root, root_cache); + if (ret) + err = 1; + +next: + if (tmp_root.node) + free_extent_buffer(tmp_root.node); + } + + path.slots[0]++; + } +out: + btrfs_release_path(&path); + + return err; +} + static void free_roots_info_cache(void) { if (!roots_info_cache) @@ -10468,9 +10737,21 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) goto close_out; } + if (gfs_info->log_root_tree) { + fprintf(stderr, "[1/8] checking log\n"); + ret = check_log(&root_cache); + + if (ret) + error("errors found in log"); + err |= !!ret; + } else { + fprintf(stderr, + "[1/8] checking log skipped (none written)\n"); + } + if (!init_extent_tree) { if (!g_task_ctx.progress_enabled) { - fprintf(stderr, "[1/7] checking root items\n"); + fprintf(stderr, "[2/8] checking root items\n"); } else { g_task_ctx.tp = TASK_ROOT_ITEMS; task_start(g_task_ctx.info, &g_task_ctx.start_time, @@ -10505,11 +10786,11 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) } } } else { - fprintf(stderr, "[1/7] checking root items... skipped\n"); + fprintf(stderr, "[2/8] checking root items... skipped\n"); } if (!g_task_ctx.progress_enabled) { - fprintf(stderr, "[2/7] checking extents\n"); + fprintf(stderr, "[3/8] checking extents\n"); } else { g_task_ctx.tp = TASK_EXTENTS; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10527,9 +10808,9 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) if (!g_task_ctx.progress_enabled) { if (is_free_space_tree) - fprintf(stderr, "[3/7] checking free space tree\n"); + fprintf(stderr, "[4/8] checking free space tree\n"); else - fprintf(stderr, "[3/7] checking free space cache\n"); + fprintf(stderr, "[4/8] checking free space cache\n"); } else { g_task_ctx.tp = TASK_FREE_SPACE; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10547,7 +10828,7 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) */ no_holes = btrfs_fs_incompat(gfs_info, NO_HOLES); if (!g_task_ctx.progress_enabled) { - fprintf(stderr, "[4/7] checking fs roots\n"); + fprintf(stderr, "[5/8] checking fs roots\n"); } else { g_task_ctx.tp = TASK_FS_ROOTS; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10555,6 +10836,9 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) ret = do_check_fs_roots(&root_cache); task_stop(g_task_ctx.info); + if (found_free_ino_cache) + pr_verbose(LOG_DEFAULT, + "deprecated inode cache can be removed by 'btrfs rescue clear-ino-cache'\n"); err |= !!ret; if (ret) { error("errors found in fs roots"); @@ -10563,10 +10847,10 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) if (!g_task_ctx.progress_enabled) { if (check_data_csum) - fprintf(stderr, "[5/7] checking csums against data\n"); + fprintf(stderr, "[6/8] checking csums against data\n"); else fprintf(stderr, - "[5/7] checking only csums items (without verifying data)\n"); + "[6/8] checking only csums items (without verifying data)\n"); } else { g_task_ctx.tp = TASK_CSUMS; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10585,7 +10869,7 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) /* For low memory mode, check_fs_roots_v2 handles root refs */ if (check_mode != CHECK_MODE_LOWMEM) { if (!g_task_ctx.progress_enabled) { - fprintf(stderr, "[6/7] checking root refs\n"); + fprintf(stderr, "[7/8] checking root refs\n"); } else { g_task_ctx.tp = TASK_ROOT_REFS; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10600,7 +10884,7 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) } } else { fprintf(stderr, - "[6/7] checking root refs done with fs roots in lowmem mode, skipping\n"); + "[7/8] checking root refs done with fs roots in lowmem mode, skipping\n"); } while (opt_check_repair && !list_empty(&gfs_info->recow_ebs)) { @@ -10632,7 +10916,7 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) if (gfs_info->quota_enabled) { if (!g_task_ctx.progress_enabled) { - fprintf(stderr, "[7/7] checking quota groups\n"); + fprintf(stderr, "[8/8] checking quota groups\n"); } else { g_task_ctx.tp = TASK_QGROUPS; task_start(g_task_ctx.info, &g_task_ctx.start_time, &g_task_ctx.item_count); @@ -10655,7 +10939,7 @@ static int cmd_check(const struct cmd_struct *cmd, int argc, char **argv) ret = 0; } else { fprintf(stderr, - "[7/7] checking quota groups skipped (not enabled on this FS)\n"); + "[8/8] checking quota groups skipped (not enabled on this FS)\n"); } if (!list_empty(&gfs_info->recow_ebs)) { diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c index a9908eaf62..34af77f884 100644 --- a/check/mode-lowmem.c +++ b/check/mode-lowmem.c @@ -44,6 +44,7 @@ static u64 last_allocated_chunk; static u64 total_used = 0; +static bool found_free_ino_cache = false; static int calc_extent_flag(struct btrfs_root *root, struct extent_buffer *eb, u64 *flags_ret) @@ -2629,6 +2630,12 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path) return err; } + if (inode_id == BTRFS_FREE_INO_OBJECTID) { + warning("subvolume %lld has deprecated inode cache", + root->root_key.objectid); + found_free_ino_cache = true; + } + is_orphan = has_orphan_item(root, inode_id); ii = btrfs_item_ptr(node, slot, struct btrfs_inode_item); isize = btrfs_inode_size(node, ii); @@ -3351,6 +3358,31 @@ static int repair_extent_data_item(struct btrfs_root *root, return err; } +static int read_inode_item(struct btrfs_root *root, + u64 ino, struct btrfs_inode_item *ret_ii) +{ + struct btrfs_path path = { 0 }; + struct btrfs_key key = { + .objectid = ino, + .type = BTRFS_INODE_ITEM_KEY, + .offset = 0 + }; + int ret; + + ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0); + if (ret > 0) + ret = -ENOENT; + if (ret < 0) + goto out; + + read_extent_buffer(path.nodes[0], ret_ii, + btrfs_item_ptr_offset(path.nodes[0], path.slots[0]), + sizeof(*ret_ii)); +out: + btrfs_release_path(&path); + return ret; +} + /* * Check EXTENT_DATA item, mainly for its dbackref in extent tree * @@ -3371,6 +3403,7 @@ static int check_extent_data_item(struct btrfs_root *root, struct btrfs_extent_item *ei; struct btrfs_extent_inline_ref *iref; struct btrfs_extent_data_ref *dref; + struct btrfs_inode_item inode_item; u64 owner; u64 disk_bytenr; u64 disk_num_bytes; @@ -3400,6 +3433,24 @@ static int check_extent_data_item(struct btrfs_root *root, extent_num_bytes = btrfs_file_extent_num_bytes(eb, fi); offset = btrfs_file_extent_offset(eb, fi); + /* + * There is a regular/preallocated data extent. Make sure the owning + * inode is not a symbolic link. + * As symbolic links can only have inline data extents. + */ + ret = read_inode_item(root, fi_key.objectid, &inode_item); + if (ret < 0) { + errno = -ret; + error("failed to grab the inode item for inode %llu: %m", + fi_key.objectid); + err |= INODE_ITEM_MISSING; + } + if (S_ISLNK(inode_item.mode)) { + error("symbolic link at root %lld ino %llu has regular/preallocated extents", + root->root_key.objectid, fi_key.objectid); + err |= FILE_EXTENT_ERROR; + } + /* Check unaligned disk_bytenr, disk_num_bytes and num_bytes */ if (!IS_ALIGNED(disk_bytenr, gfs_info->sectorsize)) { error( @@ -5572,6 +5623,9 @@ int check_fs_roots_lowmem(void) out: btrfs_release_path(&path); + if (found_free_ino_cache) + pr_verbose(LOG_DEFAULT, + "deprecated inode cache can be removed by 'btrfs rescue clear-ino-cache'\n"); return err; } diff --git a/check/mode-original.h b/check/mode-original.h index ac8de57cc5..949d75513f 100644 --- a/check/mode-original.h +++ b/check/mode-original.h @@ -189,6 +189,7 @@ struct unaligned_extent_rec_t { #define I_ERR_INVALID_GEN (1U << 20) #define I_ERR_INVALID_NLINK (1U << 21) #define I_ERR_INVALID_XATTR (1U << 22) +#define I_ERR_DEPRECATED_FREE_INO (1U << 23) struct inode_record { struct list_head backrefs; diff --git a/ci/actions/delete-all-failed-devel-runs b/ci/actions/delete-all-failed-devel-runs index 79d33b3982..983cb17843 100755 --- a/ci/actions/delete-all-failed-devel-runs +++ b/ci/actions/delete-all-failed-devel-runs @@ -8,7 +8,7 @@ repo="kdave/btrfs-progs" type -p gh > /dev/null || { echo "ERROR: gh tool not found"; exit 1; } type -p jq > /dev/null || { echo "ERROR: jq tool not found"; exit 1; } -for id in $(gh run -R "$repo" list --workflow "$workflow" --status failure --json databaseId | jq '.[].databaseId'); do +for id in $(gh run -R "$repo" list --limit 100 --workflow "$workflow" --status failure --json databaseId | jq '.[].databaseId'); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/delete-all-runs-of-branch b/ci/actions/delete-all-runs-of-branch index 9e1e194896..e7596f79dc 100755 --- a/ci/actions/delete-all-runs-of-branch +++ b/ci/actions/delete-all-runs-of-branch @@ -16,11 +16,13 @@ notthatone() { exit 1 } +areyousure() { + echo "WARNING: protected branch, make sure you want to remove it: $1" +} + case "$branch" in master) notthatone "$branch";; - devel) notthatone "$branch";; - coverage-test) notthatone "$branch";; - release-test) notthatone "$branch";; + devel) areyousure "$branch";; esac echo "Delete all runs of branch $branch, are you sure? [y/N]" @@ -31,7 +33,7 @@ if ! [ "$answer" = 'y' ]; then fi echo -for id in $(gh run -R "$repo" list --json databaseId --branch "$branch" | jq '.[].databaseId'); do +for id in $(gh run -R "$repo" list --limit 100 --json databaseId --branch "$branch" | jq '.[].databaseId'); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/keep-last-ci-image-tests b/ci/actions/keep-last-ci-image-tests index ceff31a6bd..40186edc1f 100755 --- a/ci/actions/keep-last-ci-image-tests +++ b/ci/actions/keep-last-ci-image-tests @@ -9,7 +9,7 @@ type -p gh > /dev/null || { echo "ERROR: gh tool not found"; exit 1; } type -p jq > /dev/null || { echo "ERROR: jq tool not found"; exit 1; } for branch in master release-test; do - for id in $(gh run -R "$repo" list --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do + for id in $(gh run -R "$repo" list --limit 100 --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/keep-last-coverage b/ci/actions/keep-last-coverage index 76eb7033ee..f2543f7282 100755 --- a/ci/actions/keep-last-coverage +++ b/ci/actions/keep-last-coverage @@ -9,7 +9,7 @@ type -p gh > /dev/null || { echo "ERROR: gh tool not found"; exit 1; } type -p jq > /dev/null || { echo "ERROR: jq tool not found"; exit 1; } for branch in master coverage-test; do - for id in $(gh run -R "$repo" list --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do + for id in $(gh run -R "$repo" list --limit 100 --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/keep-last-devel-runs b/ci/actions/keep-last-devel-runs index 5a65aaaeb3..96f5f08fd7 100755 --- a/ci/actions/keep-last-devel-runs +++ b/ci/actions/keep-last-devel-runs @@ -7,7 +7,7 @@ repo="kdave/btrfs-progs" from=11 -for id in $(gh run -R "$repo" list -w 'Devel build and tests' --json databaseId | jq '.[].databaseId' | tail -n +${from}); do +for id in $(gh run -R "$repo" list --limit 100 --workflow 'Devel build and tests' --json databaseId | jq '.[].databaseId' | tail -n +${from}); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/keep-last-static-binaries b/ci/actions/keep-last-static-binaries index 421a68679a..ebd769ddb8 100755 --- a/ci/actions/keep-last-static-binaries +++ b/ci/actions/keep-last-static-binaries @@ -9,7 +9,7 @@ type -p gh > /dev/null || { echo "ERROR: gh tool not found"; exit 1; } type -p jq > /dev/null || { echo "ERROR: jq tool not found"; exit 1; } for branch in master release-test; do - for id in $(gh run -R "$repo" list --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do + for id in $(gh run -R "$repo" list --limit 100 --workflow "$workflow" --branch "$branch" --json databaseId | jq '.[1:] | .[].databaseId'); do echo "Delete run $id" gh run -R "$repo" delete "$id" done diff --git a/ci/actions/keep-last-week b/ci/actions/keep-last-week index e985380d0b..08834027c9 100755 --- a/ci/actions/keep-last-week +++ b/ci/actions/keep-last-week @@ -11,7 +11,7 @@ daysmax=8 clean_workflow() { local wf="$1" - local json=$(gh run -R "$repo" list --workflow "$wf" --json databaseId,startedAt) + local json=$(gh run -R "$repo" list --limit 100 --workflow "$wf" --json databaseId,startedAt) echo "Cleaning workflow $wf" i=0 @@ -40,3 +40,6 @@ clean_workflow() { clean_workflow "Testing CI build" clean_workflow "Devel build and tests" clean_workflow "Pull request build and tests" +clean_workflow "Codespell" +clean_workflow "CI image tests" +clean_workflow "Sanitizer checks" diff --git a/ci/actions/update-artifacts b/ci/actions/update-artifacts index 4c6c349e9d..6bf4e8e576 100755 --- a/ci/actions/update-artifacts +++ b/ci/actions/update-artifacts @@ -16,8 +16,8 @@ repo="kdave/btrfs-progs" tag="$1" # TODO: verify that tag exists -# Read last workflow id -id=$(gh run -R "$repo" list -w 'Static binaries' -L 1 --json databaseId | jq '.[].databaseId') +# Read last workflow id for master branch +id=$(gh run -R "$repo" list --limit 1 --workflow 'Static binaries' --branch 'master' --json databaseId | jq '.[].databaseId') for asset in btrfs.box.static btrfs.static; do gh run -R "$repo" download "$id" -n "$asset" diff --git a/cmds/filesystem.c b/cmds/filesystem.c index 8e2697de6a..41ad2f8d3e 100644 --- a/cmds/filesystem.c +++ b/cmds/filesystem.c @@ -365,7 +365,6 @@ static void print_one_uuid(struct btrfs_fs_devices *fs_devices, if (devs_found < total) { pr_verbose(LOG_DEFAULT, "\t*** Some devices missing\n"); } - pr_verbose(LOG_DEFAULT, "\n"); } /* adds up all the used spaces as reported by the space info ioctl @@ -431,7 +430,6 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, free(canonical_path); } - pr_verbose(LOG_DEFAULT, "\n"); return 0; } @@ -481,6 +479,10 @@ static int btrfs_scan_kernel(void *search, unsigned unit_mode) fd = open(mnt->mnt_dir, O_RDONLY); if ((fd != -1) && !get_df(fd, &space_info_arg)) { + /* Put space between filesystem entries for readability. */ + if (found != 0) + pr_verbose(LOG_DEFAULT, "\n"); + print_one_fs(&fs_info_arg, dev_info_arg, space_info_arg, label, unit_mode); free(space_info_arg); @@ -757,6 +759,7 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, char uuid_buf[BTRFS_UUID_UNPARSED_SIZE]; unsigned unit_mode; int found = 0; + bool needs_newline = false; unit_mode = get_unit_mode_from_arg(&argc, argv, 0); @@ -845,6 +848,12 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, goto out; } + /* + * The above call will return 0 if it found anything, in those cases we + * need an extra newline below. + */ + needs_newline = !ret; + /* shows mounted only */ if (where == BTRFS_SCAN_MOUNTED) goto out; @@ -882,8 +891,14 @@ static int cmd_filesystem_show(const struct cmd_struct *cmd, goto out; } - list_for_each_entry(fs_devices, &all_uuids, fs_list) + list_for_each_entry(fs_devices, &all_uuids, fs_list) { + /* Put space between filesystem entries for readability. */ + if (needs_newline) + pr_verbose(LOG_DEFAULT, "\n"); + print_one_uuid(fs_devices, unit_mode); + needs_newline = true; + } if (search && !found) { error("not a valid btrfs filesystem: %s", search); diff --git a/cmds/qgroup.c b/cmds/qgroup.c index 20b97f7ae5..5705286120 100644 --- a/cmds/qgroup.c +++ b/cmds/qgroup.c @@ -1688,70 +1688,6 @@ static int qgroup_parse_sort_string(const char *opt_arg, return ret; } -int btrfs_qgroup_inherit_size(struct btrfs_qgroup_inherit *p) -{ - return sizeof(*p) + sizeof(p->qgroups[0]) * - (p->num_qgroups + 2 * p->num_ref_copies + - 2 * p->num_excl_copies); -} - -static int qgroup_inherit_realloc(struct btrfs_qgroup_inherit **inherit, int n, - int pos) -{ - struct btrfs_qgroup_inherit *out; - int nitems = 0; - - if (*inherit) { - nitems = (*inherit)->num_qgroups + - (*inherit)->num_ref_copies + - (*inherit)->num_excl_copies; - } - - out = calloc(1, sizeof(*out) + sizeof(out->qgroups[0]) * (nitems + n)); - if (out == NULL) { - error_msg(ERROR_MSG_MEMORY, NULL); - return -ENOMEM; - } - - if (*inherit) { - struct btrfs_qgroup_inherit *i = *inherit; - int s = sizeof(out->qgroups[0]); - - out->num_qgroups = i->num_qgroups; - out->num_ref_copies = i->num_ref_copies; - out->num_excl_copies = i->num_excl_copies; - memcpy(out->qgroups, i->qgroups, pos * s); - memcpy(out->qgroups + pos + n, i->qgroups + pos, - (nitems - pos) * s); - } - free(*inherit); - *inherit = out; - - return 0; -} - -int btrfs_qgroup_inherit_add_group(struct btrfs_qgroup_inherit **inherit, char *arg) -{ - int ret; - u64 qgroupid = parse_qgroupid_or_path(arg); - int pos = 0; - - if (qgroupid == 0) { - error("invalid qgroup specification, qgroupid must not 0"); - return -EINVAL; - } - - if (*inherit) - pos = (*inherit)->num_qgroups; - ret = qgroup_inherit_realloc(inherit, 1, pos); - if (ret) - return ret; - - (*inherit)->qgroups[(*inherit)->num_qgroups++] = qgroupid; - - return 0; -} - static const char * const qgroup_cmd_group_usage[] = { "btrfs qgroup [options] ", NULL diff --git a/cmds/qgroup.h b/cmds/qgroup.h index 1fc107221c..32309ce4ae 100644 --- a/cmds/qgroup.h +++ b/cmds/qgroup.h @@ -36,8 +36,6 @@ struct btrfs_qgroup_stats { struct btrfs_qgroup_limit limit; }; -int btrfs_qgroup_inherit_size(struct btrfs_qgroup_inherit *p); -int btrfs_qgroup_inherit_add_group(struct btrfs_qgroup_inherit **inherit, char *arg); int btrfs_qgroup_query(int fd, u64 qgroupid, struct btrfs_qgroup_stats *stats); #endif diff --git a/cmds/rescue.c b/cmds/rescue.c index 6d7d526df1..c60bf11675 100644 --- a/cmds/rescue.c +++ b/cmds/rescue.c @@ -203,7 +203,7 @@ static int cmd_rescue_zero_log(const struct cmd_struct *cmd, } root = open_ctree(devname, 0, OPEN_CTREE_WRITES | OPEN_CTREE_PARTIAL | - OPEN_CTREE_NO_BLOCK_GROUPS); + OPEN_CTREE_NO_BLOCK_GROUPS | OPEN_CTREE_EXCLUSIVE); if (!root) { error("could not open ctree"); return 1; @@ -258,7 +258,7 @@ static int cmd_rescue_fix_device_size(const struct cmd_struct *cmd, } oca.filename = devname; - oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_PARTIAL; + oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_PARTIAL | OPEN_CTREE_EXCLUSIVE; fs_info = open_ctree_fs_info(&oca); if (!fs_info) { error("could not open btrfs"); @@ -437,7 +437,7 @@ static int cmd_rescue_clear_ino_cache(const struct cmd_struct *cmd, goto out; } oca.filename = devname; - oca.flags = OPEN_CTREE_WRITES; + oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_EXCLUSIVE; fs_info = open_ctree_fs_info(&oca); if (!fs_info) { error("could not open btrfs"); @@ -449,7 +449,7 @@ static int cmd_rescue_clear_ino_cache(const struct cmd_struct *cmd, errno = -ret; error("failed to clear ino cache: %m"); } else { - pr_verbose(LOG_DEFAULT, "Successfully cleared ino cache"); + pr_verbose(LOG_DEFAULT, "Successfully cleared ino cache\n"); } close_ctree(fs_info->tree_root); out: diff --git a/cmds/subvolume-list.c b/cmds/subvolume-list.c index 24e5c6856f..4dde9fbe4a 100644 --- a/cmds/subvolume-list.c +++ b/cmds/subvolume-list.c @@ -15,6 +15,7 @@ */ #include "kerncompat.h" +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include "libbtrfsutil/btrfsutil.h" #include "kernel-lib/rbtree.h" #include "kernel-lib/rbtree_types.h" #include "kernel-shared/accessors.h" @@ -57,9 +59,23 @@ static const char * const cmd_subvolume_list_usage[] = { "List subvolumes and snapshots in the filesystem.", "", "Path filtering:", - OPTLINE("-o", "print only subvolumes below specified path"), - OPTLINE("-a", "print all the subvolumes in the filesystem and " - "distinguish absolute and relative path with respect to the given "), + OPTLINE("-O", "print all subvolumes below relative to "), + OPTLINE("-A", "print all subvolumes in the filesystem relative to the " + "root of the filesystem"), + "", + "You likely always want either -O or -A. The -o and -a options and the", + "default are confusing and only kept for backwards compatibility.", + "", + OPTLINE("-o", "print only the immediate children subvolumes of the " + "subvolume containing "), + OPTLINE("-a", "print all subvolumes in the filesystem other than the " + "root subvolume, and prefix subvolumes that are not an " + "immediate child of the subvolume containing with " + "\"/\""), + "", + "If none of these are given, print all subvolumes other than the root", + "subvolume relative to the subvolume containing if below it,", + "otherwise relative to the root of the filesystem.", "", "Field selection:", OPTLINE("-p", "print parent ID"), @@ -98,58 +114,17 @@ static const char * const cmd_subvolume_list_usage[] = { enum btrfs_list_layout { BTRFS_LIST_LAYOUT_DEFAULT = 0, BTRFS_LIST_LAYOUT_TABLE, - BTRFS_LIST_LAYOUT_RAW, BTRFS_LIST_LAYOUT_JSON }; -/* - * one of these for each root we find. - */ struct root_info { - struct rb_node rb_node; - struct rb_node sort_node; - - /* this root's id */ - u64 root_id; - - /* equal the offset of the root's key */ - u64 root_offset; - - /* flags of the root */ - u64 flags; - - /* the id of the root that references this one */ - u64 ref_tree; - - /* the dir id we're in from ref_tree */ - u64 dir_id; - - u64 top_id; - - /* generation when the root is created or last updated */ - u64 gen; - - /* creation generation of this root in sec*/ - u64 ogen; - - /* creation time of this root in sec*/ - time_t otime; - - u8 uuid[BTRFS_UUID_SIZE]; - u8 puuid[BTRFS_UUID_SIZE]; - u8 ruuid[BTRFS_UUID_SIZE]; - - /* path from the subvol we live in to this root, including the - * root's name. This is null until we do the extra lookup ioctl. - */ + struct btrfs_util_subvolume_info info; char *path; +}; - /* the name of this root in the directory it lives in */ - char *name; - - char *full_path; - - int deleted; +struct subvol_list { + size_t num; + struct root_info subvols[]; }; typedef int (*btrfs_list_filter_func)(struct root_info *, u64); @@ -194,7 +169,6 @@ enum btrfs_list_column_enum { }; enum btrfs_list_filter_enum { - BTRFS_LIST_FILTER_ROOTID, BTRFS_LIST_FILTER_SNAPSHOT_ONLY, BTRFS_LIST_FILTER_FLAGS, BTRFS_LIST_FILTER_GEN, @@ -207,8 +181,7 @@ enum btrfs_list_filter_enum { BTRFS_LIST_FILTER_CGEN_MORE, BTRFS_LIST_FILTER_TOPID_EQUAL, BTRFS_LIST_FILTER_FULL_PATH, - BTRFS_LIST_FILTER_BY_PARENT, - BTRFS_LIST_FILTER_DELETED, + BTRFS_LIST_FILTER_REMOVE_PATH_PREFIX, BTRFS_LIST_FILTER_MAX, }; @@ -220,16 +193,6 @@ enum btrfs_list_comp_enum { BTRFS_LIST_COMP_MAX, }; -static inline struct root_info *to_root_info(struct rb_node *node) -{ - return rb_entry(node, struct root_info, rb_node); -} - -static inline struct root_info *to_root_info_sorted(struct rb_node *node) -{ - return rb_entry(node, struct root_info, sort_node); -} - static struct { char *name; char *column_name; @@ -313,9 +276,9 @@ static void btrfs_list_setup_print_column(enum btrfs_list_column_enum column) static int comp_entry_with_rootid(const struct root_info *entry1, const struct root_info *entry2) { - if (entry1->root_id > entry2->root_id) + if (entry1->info.id > entry2->info.id) return 1; - else if (entry1->root_id < entry2->root_id) + else if (entry1->info.id < entry2->info.id) return -1; return 0; } @@ -323,9 +286,9 @@ static int comp_entry_with_rootid(const struct root_info *entry1, static int comp_entry_with_gen(const struct root_info *entry1, const struct root_info *entry2) { - if (entry1->gen > entry2->gen) + if (entry1->info.generation > entry2->info.generation) return 1; - else if (entry1->gen < entry2->gen) + else if (entry1->info.generation < entry2->info.generation) return -1; return 0; } @@ -333,9 +296,9 @@ static int comp_entry_with_gen(const struct root_info *entry1, static int comp_entry_with_ogen(const struct root_info *entry1, const struct root_info *entry2) { - if (entry1->ogen > entry2->ogen) + if (entry1->info.otransid > entry2->info.otransid) return 1; - else if (entry1->ogen < entry2->ogen) + else if (entry1->info.otransid < entry2->info.otransid) return -1; return 0; } @@ -343,9 +306,9 @@ static int comp_entry_with_ogen(const struct root_info *entry1, static int comp_entry_with_path(const struct root_info *entry1, const struct root_info *entry2) { - if (strcmp(entry1->full_path, entry2->full_path) > 0) + if (strcmp(entry1->path, entry2->path) > 0) return 1; - else if (strcmp(entry1->full_path, entry2->full_path) < 0) + else if (strcmp(entry1->path, entry2->path) < 0) return -1; return 0; } @@ -414,9 +377,9 @@ static int btrfs_list_setup_comparer(struct btrfs_list_comparer_set **comp_set, return 0; } -static int sort_comp(const struct root_info *entry1, const struct root_info *entry2, - struct btrfs_list_comparer_set *set) +static int sort_comp(const void *entry1, const void *entry2, void *arg) { + struct btrfs_list_comparer_set *set = arg; bool rootid_compared = false; int i, ret = 0; @@ -443,581 +406,99 @@ static int sort_comp(const struct root_info *entry1, const struct root_info *ent return ret; } -static int sort_tree_insert(struct rb_root *sort_tree, - struct root_info *ins, - struct btrfs_list_comparer_set *comp_set) -{ - struct rb_node **p = &sort_tree->rb_node; - struct rb_node *parent = NULL; - struct root_info *curr; - int ret; - - while (*p) { - parent = *p; - curr = to_root_info_sorted(parent); - - ret = sort_comp(ins, curr, comp_set); - if (ret < 0) - p = &(*p)->rb_left; - else if (ret > 0) - p = &(*p)->rb_right; - else - return -EEXIST; - } - - rb_link_node(&ins->sort_node, parent, p); - rb_insert_color(&ins->sort_node, sort_tree); - return 0; -} - -/* - * insert a new root into the tree. returns the existing root entry - * if one is already there. Both root_id and ref_tree are used - * as the key - */ -static int root_tree_insert(struct rb_root *root_tree, - struct root_info *ins) -{ - struct rb_node **p = &root_tree->rb_node; - struct rb_node * parent = NULL; - struct root_info *curr; - int ret; - - while(*p) { - parent = *p; - curr = to_root_info(parent); - - ret = comp_entry_with_rootid(ins, curr); - if (ret < 0) - p = &(*p)->rb_left; - else if (ret > 0) - p = &(*p)->rb_right; - else - return -EEXIST; - } - - rb_link_node(&ins->rb_node, parent, p); - rb_insert_color(&ins->rb_node, root_tree); - return 0; -} - -/* - * find a given root id in the tree. We return the smallest one, - * rb_next can be used to move forward looking for more if required - */ -static struct root_info *root_tree_search(struct rb_root *root_tree, - u64 root_id) -{ - struct rb_node *n = root_tree->rb_node; - struct root_info *entry; - struct root_info tmp; - int ret; - - tmp.root_id = root_id; - - while(n) { - entry = to_root_info(n); - - ret = comp_entry_with_rootid(&tmp, entry); - if (ret < 0) - n = n->rb_left; - else if (ret > 0) - n = n->rb_right; - else - return entry; - } - return NULL; -} - -static int update_root(struct rb_root *root_lookup, - u64 root_id, u64 ref_tree, u64 root_offset, u64 flags, - u64 dir_id, char *name, int name_len, u64 ogen, u64 gen, - time_t otime, u8 *uuid, u8 *puuid, u8 *ruuid) -{ - struct root_info *ri; - - ri = root_tree_search(root_lookup, root_id); - if (!ri || ri->root_id != root_id) - return -ENOENT; - if (name && name_len > 0) { - free(ri->name); - - ri->name = malloc(name_len + 1); - if (!ri->name) { - error_msg(ERROR_MSG_MEMORY, NULL); - exit(1); - } - strncpy_null(ri->name, name, name_len + 1); - } - if (ref_tree) - ri->ref_tree = ref_tree; - if (root_offset) - ri->root_offset = root_offset; - if (flags) - ri->flags = flags; - if (dir_id) - ri->dir_id = dir_id; - if (gen) - ri->gen = gen; - if (ogen) - ri->ogen = ogen; - if (!ri->ogen && root_offset) - ri->ogen = root_offset; - if (otime) - ri->otime = otime; - if (uuid) - memcpy(&ri->uuid, uuid, BTRFS_UUID_SIZE); - if (puuid) - memcpy(&ri->puuid, puuid, BTRFS_UUID_SIZE); - if (ruuid) - memcpy(&ri->ruuid, ruuid, BTRFS_UUID_SIZE); - - return 0; -} - -/* - * add_root - update the existed root, or allocate a new root and insert it - * into the lookup tree. - * root_id: object id of the root - * ref_tree: object id of the referring root. - * root_offset: offset value of the root'key - * dir_id: inode id of the directory in ref_tree where this root can be found. - * name: the name of root_id in that directory - * name_len: the length of name - * ogen: the original generation of the root - * gen: the current generation of the root - * otime: the original time (creation time) of the root - * uuid: uuid of the root - * puuid: uuid of the root parent if any - * ruuid: uuid of the received subvol, if any - */ -static int add_root(struct rb_root *root_lookup, - u64 root_id, u64 ref_tree, u64 root_offset, u64 flags, - u64 dir_id, char *name, int name_len, u64 ogen, u64 gen, - time_t otime, u8 *uuid, u8 *puuid, u8 *ruuid) -{ - struct root_info *ri; - int ret; - - ret = update_root(root_lookup, root_id, ref_tree, root_offset, flags, - dir_id, name, name_len, ogen, gen, otime, - uuid, puuid, ruuid); - if (!ret) - return 0; - - ri = calloc(1, sizeof(*ri)); - if (!ri) { - error_msg(ERROR_MSG_MEMORY, NULL); - exit(1); - } - ri->root_id = root_id; - - if (name && name_len > 0) { - ri->name = malloc(name_len + 1); - if (!ri->name) { - error_msg(ERROR_MSG_MEMORY, NULL); - exit(1); - } - strncpy_null(ri->name, name, name_len + 1); - } - if (ref_tree) - ri->ref_tree = ref_tree; - if (dir_id) - ri->dir_id = dir_id; - if (root_offset) - ri->root_offset = root_offset; - if (flags) - ri->flags = flags; - if (gen) - ri->gen = gen; - if (ogen) - ri->ogen = ogen; - if (!ri->ogen && root_offset) - ri->ogen = root_offset; - if (otime) - ri->otime = otime; - - if (uuid) - memcpy(&ri->uuid, uuid, BTRFS_UUID_SIZE); - - if (puuid) - memcpy(&ri->puuid, puuid, BTRFS_UUID_SIZE); - - if (ruuid) - memcpy(&ri->ruuid, ruuid, BTRFS_UUID_SIZE); - - ret = root_tree_insert(root_lookup, ri); - if (ret < 0) { - errno = -ret; - error("failed to insert subvolume %llu to tree: %m", root_id); - exit(1); - } - return 0; -} - -/* - * Simplified add_root for back references, omits the uuid and original info - * parameters, root offset and flags. - */ -static int add_root_backref(struct rb_root *root_lookup, u64 root_id, - u64 ref_tree, u64 dir_id, char *name, int name_len) -{ - return add_root(root_lookup, root_id, ref_tree, 0, 0, dir_id, name, - name_len, 0, 0, 0, NULL, NULL, NULL); -} - -static void free_root_info(struct rb_node *node) -{ - struct root_info *ri; - - ri = to_root_info(node); - free(ri->name); - free(ri->path); - free(ri->full_path); - free(ri); -} - -/* - * for a given root_info, search through the root_lookup tree to construct - * the full path name to it. - * - * This can't be called until all the root_info->path fields are filled - * in by lookup_ino_path - */ -static int resolve_root(struct rb_root *rl, struct root_info *ri, - u64 top_id) -{ - char *full_path = NULL; - int len = 0; - struct root_info *found; - - /* - * we go backwards from the root_info object and add pathnames - * from parent directories as we go. - */ - found = ri; - while (1) { - char *tmp; - u64 next; - int add_len; - - /* - * ref_tree = 0 indicates the subvolume - * has been deleted. - */ - if (!found->ref_tree) { - free(full_path); - return -ENOENT; - } - - add_len = strlen(found->path); - - if (full_path) { - /* room for / and for null */ - tmp = malloc(add_len + 2 + len); - if (!tmp) { - error_msg(ERROR_MSG_MEMORY, NULL); - exit(1); - } - memcpy(tmp + add_len + 1, full_path, len); - tmp[add_len] = '/'; - memcpy(tmp, found->path, add_len); - tmp [add_len + len + 1] = '\0'; - free(full_path); - full_path = tmp; - len += add_len + 1; - } else { - full_path = strdup(found->path); - len = add_len; - } - if (!ri->top_id) - ri->top_id = found->ref_tree; - - next = found->ref_tree; - if (next == top_id) - break; - /* - * if the ref_tree = BTRFS_FS_TREE_OBJECTID, - * we are at the top - */ - if (next == BTRFS_FS_TREE_OBJECTID) - break; - /* - * if the ref_tree wasn't in our tree of roots, the - * subvolume was deleted. - */ - found = root_tree_search(rl, next); - if (!found) { - free(full_path); - return -ENOENT; - } - } - - ri->full_path = full_path; - - return 0; -} - -/* - * for a single root_info, ask the kernel to give us a path name - * inside it's ref_root for the dir_id where it lives. - * - * This fills in root_info->path with the path to the directory and and - * appends this root's name. - */ -static int lookup_ino_path(int fd, struct root_info *ri) -{ - struct btrfs_ioctl_ino_lookup_args args; - int ret; - - if (ri->path) - return 0; - - if (!ri->ref_tree) - return -ENOENT; - - memset(&args, 0, sizeof(args)); - args.treeid = ri->ref_tree; - args.objectid = ri->dir_id; - - ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); - if (ret < 0) { - if (errno == ENOENT) { - ri->ref_tree = 0; - return -ENOENT; - } - error("failed to lookup path for root %llu: %m", ri->ref_tree); - return ret; - } - - if (args.name[0]) { - /* - * we're in a subdirectory of ref_tree, the kernel ioctl - * puts a / in there for us - */ - ri->path = malloc(strlen(ri->name) + strlen(args.name) + 1); - if (!ri->path) { - error_msg(ERROR_MSG_MEMORY, NULL); - exit(1); - } - strcpy(ri->path, args.name); - strcat(ri->path, ri->name); - } else { - /* we're at the root of ref_tree */ - ri->path = strdup(ri->name); - if (!ri->path) { - perror("strdup failed"); - exit(1); - } - } - return 0; -} - -static int list_subvol_search(int fd, struct rb_root *root_lookup) -{ - int ret; - struct btrfs_tree_search_args args; - struct btrfs_ioctl_search_key *sk; - struct btrfs_root_ref *ref; - struct btrfs_root_item *ri; - unsigned long off; - int name_len; - char *name; - u64 dir_id; - u64 gen = 0; - u64 ogen; - u64 flags; - int i; - - root_lookup->rb_node = NULL; - - memset(&args, 0, sizeof(args)); - sk = btrfs_tree_search_sk(&args); - sk->tree_id = BTRFS_ROOT_TREE_OBJECTID; - /* Search both live and deleted subvolumes */ - sk->min_type = BTRFS_ROOT_ITEM_KEY; - sk->max_type = BTRFS_ROOT_BACKREF_KEY; - sk->min_objectid = BTRFS_FS_TREE_OBJECTID; - sk->max_objectid = BTRFS_LAST_FREE_OBJECTID; - sk->max_offset = (u64)-1; - sk->max_transid = (u64)-1; - - while(1) { - sk->nr_items = 4096; - ret = btrfs_tree_search_ioctl(fd, &args); - if (ret < 0) - return ret; - if (sk->nr_items == 0) - break; - - off = 0; - - /* - * for each item, pull the key out of the header and then - * read the root_ref item it contains - */ - for (i = 0; i < sk->nr_items; i++) { - struct btrfs_ioctl_search_header sh; - - memcpy(&sh, btrfs_tree_search_data(&args, off), sizeof(sh)); - off += sizeof(sh); - if (sh.type == BTRFS_ROOT_BACKREF_KEY) { - ref = btrfs_tree_search_data(&args, off); - name_len = btrfs_stack_root_ref_name_len(ref); - name = (char *)(ref + 1); - dir_id = btrfs_stack_root_ref_dirid(ref); - - add_root_backref(root_lookup, sh.objectid, - sh.offset, dir_id, name, - name_len); - } else if (sh.type == BTRFS_ROOT_ITEM_KEY && - (sh.objectid >= BTRFS_FIRST_FREE_OBJECTID || - sh.objectid == BTRFS_FS_TREE_OBJECTID)) { - time_t otime; - u8 uuid[BTRFS_UUID_SIZE]; - u8 puuid[BTRFS_UUID_SIZE]; - u8 ruuid[BTRFS_UUID_SIZE]; - - ri = btrfs_tree_search_data(&args, off); - gen = btrfs_root_generation(ri); - flags = btrfs_root_flags(ri); - if(sh.len >= sizeof(struct btrfs_root_item)) { - /* - * The new full btrfs_root_item with - * timestamp and UUID. - */ - otime = btrfs_stack_timespec_sec(&ri->otime); - ogen = btrfs_root_otransid(ri); - memcpy(uuid, ri->uuid, BTRFS_UUID_SIZE); - memcpy(puuid, ri->parent_uuid, BTRFS_UUID_SIZE); - memcpy(ruuid, ri->received_uuid, BTRFS_UUID_SIZE); - } else { - /* - * The old v0 root item, which doesn't - * have timestamp nor UUID. - */ - otime = 0; - ogen = 0; - memset(uuid, 0, BTRFS_UUID_SIZE); - memset(puuid, 0, BTRFS_UUID_SIZE); - memset(ruuid, 0, BTRFS_UUID_SIZE); - } - - add_root(root_lookup, sh.objectid, 0, - sh.offset, flags, 0, NULL, 0, ogen, - gen, otime, uuid, puuid, ruuid); - } - - off += sh.len; - - sk->min_objectid = sh.objectid; - sk->min_type = sh.type; - sk->min_offset = sh.offset; - } - sk->min_offset++; - if (!sk->min_offset) - sk->min_type++; - else - continue; - - if (sk->min_type > BTRFS_ROOT_BACKREF_KEY) { - sk->min_type = BTRFS_ROOT_ITEM_KEY; - sk->min_objectid++; - } else - continue; - - if (sk->min_objectid > sk->max_objectid) - break; - } - - return 0; -} - -static int filter_by_rootid(struct root_info *ri, u64 data) +static void sort_subvols(struct btrfs_list_comparer_set *comp_set, + struct subvol_list *subvols) { - return ri->root_id == data; + qsort_r(subvols->subvols, subvols->num, sizeof(subvols->subvols[0]), + sort_comp, comp_set); } static int filter_snapshot(struct root_info *ri, u64 data) { - return !!ri->root_offset; + return !uuid_is_null(ri->info.parent_uuid); } static int filter_flags(struct root_info *ri, u64 flags) { - return ri->flags & flags; + return ri->info.flags & flags; } static int filter_gen_more(struct root_info *ri, u64 data) { - return ri->gen >= data; + return ri->info.generation >= data; } static int filter_gen_less(struct root_info *ri, u64 data) { - return ri->gen <= data; + return ri->info.generation <= data; } static int filter_gen_equal(struct root_info *ri, u64 data) { - return ri->gen == data; + return ri->info.generation == data; } static int filter_cgen_more(struct root_info *ri, u64 data) { - return ri->ogen >= data; + return ri->info.otransid >= data; } static int filter_cgen_less(struct root_info *ri, u64 data) { - return ri->ogen <= data; + return ri->info.otransid <= data; } static int filter_cgen_equal(struct root_info *ri, u64 data) { - return ri->ogen == data; + return ri->info.otransid == data; } static int filter_topid_equal(struct root_info *ri, u64 data) { - return ri->top_id == data; + /* See the comment in print_subvolume_column() about top level. */ + return ri->info.parent_id == data; } static int filter_full_path(struct root_info *ri, u64 data) { - if (ri->full_path && ri->top_id != data) { + /* + * If this subvolume's parent is not the subvolume containing the path + * given on the command line, prepend "/". This behavior is + * nonsense, but we keep it for backwards compatibility. It was + * introduced by the same change to top level mentioned in + * print_subvolume_column(). + */ + if (ri->info.parent_id != data) { char *tmp; - char p[] = ""; - int add_len = strlen(p); - int len = strlen(ri->full_path); + int ret; - tmp = malloc(len + add_len + 2); - if (!tmp) { - error_msg(ERROR_MSG_MEMORY, NULL); + ret = asprintf(&tmp, "/%s", ri->path); + if (ret == -1) { + error("out of memory"); exit(1); } - memcpy(tmp + add_len + 1, ri->full_path, len); - tmp[len + add_len + 1] = '\0'; - tmp[add_len] = '/'; - memcpy(tmp, p, add_len); - free(ri->full_path); - ri->full_path = tmp; + + free(ri->path); + ri->path = tmp; } return 1; } -static int filter_by_parent(struct root_info *ri, u64 data) +static int filter_remove_path_prefix(struct root_info *ri, u64 data) { - return !uuid_compare(ri->puuid, (u8 *)(unsigned long)data); -} - -static int filter_deleted(struct root_info *ri, u64 data) -{ - return ri->deleted; + /* + * If this subvolume is a descendant of the given path, remove that path + * prefix. Otherwise, leave it alone. This is also nonsense that we keep + * for backwards compatibility. + */ + const char *prefix = (const char *)data; + size_t len = strlen(prefix); + if (strncmp(ri->path, prefix, len) == 0 && ri->path[len] == '/') + memmove(ri->path, &ri->path[len + 1], strlen(ri->path) - len); + return 1; } static btrfs_list_filter_func all_filter_funcs[] = { - [BTRFS_LIST_FILTER_ROOTID] = filter_by_rootid, [BTRFS_LIST_FILTER_SNAPSHOT_ONLY] = filter_snapshot, [BTRFS_LIST_FILTER_FLAGS] = filter_flags, [BTRFS_LIST_FILTER_GEN_MORE] = filter_gen_more, @@ -1028,8 +509,7 @@ static btrfs_list_filter_func all_filter_funcs[] = { [BTRFS_LIST_FILTER_CGEN_EQUAL] = filter_cgen_equal, [BTRFS_LIST_FILTER_TOPID_EQUAL] = filter_topid_equal, [BTRFS_LIST_FILTER_FULL_PATH] = filter_full_path, - [BTRFS_LIST_FILTER_BY_PARENT] = filter_by_parent, - [BTRFS_LIST_FILTER_DELETED] = filter_deleted, + [BTRFS_LIST_FILTER_REMOVE_PATH_PREFIX] = filter_remove_path_prefix, }; /* @@ -1068,9 +548,6 @@ static void btrfs_list_setup_filter(struct btrfs_list_filter_set **filter_set, UASSERT(set->filters[set->nfilters].filter_func == NULL); - if (filter == BTRFS_LIST_FILTER_DELETED) - set->only_deleted = 1; - set->filters[set->nfilters].filter_func = all_filter_funcs[filter]; set->filters[set->nfilters].data = data; set->nfilters++; @@ -1084,12 +561,6 @@ static int filter_root(struct root_info *ri, if (!set) return 1; - if (set->only_deleted && !ri->deleted) - return 0; - - if (!set->only_deleted && ri->deleted) - return 0; - for (i = 0; i < set->nfilters; i++) { if (!set->filters[i].filter_func) break; @@ -1100,44 +571,6 @@ static int filter_root(struct root_info *ri, return 1; } -static void filter_and_sort_subvol(struct rb_root *all_subvols, - struct rb_root *sort_tree, - struct btrfs_list_filter_set *filter_set, - struct btrfs_list_comparer_set *comp_set, - u64 top_id) -{ - struct rb_node *n; - struct root_info *entry; - int ret; - - sort_tree->rb_node = NULL; - - n = rb_last(all_subvols); - while (n) { - entry = to_root_info(n); - - ret = resolve_root(all_subvols, entry, top_id); - if (ret == -ENOENT) { - if (entry->root_id != BTRFS_FS_TREE_OBJECTID) { - entry->full_path = strdup("DELETED"); - entry->deleted = 1; - } else { - /* - * The full path is not supposed to be printed, - * but we don't want to print an empty string, - * in case it appears somewhere. - */ - entry->full_path = strdup("TOPLEVEL"); - entry->deleted = 0; - } - } - ret = filter_root(entry, filter_set); - if (ret) - sort_tree_insert(sort_tree, entry, comp_set); - n = rb_prev(n); - } -} - static void print_subvolume_column(struct root_info *subv, enum btrfs_list_column_enum column) { @@ -1148,77 +581,64 @@ static void print_subvolume_column(struct root_info *subv, switch (column) { case BTRFS_LIST_OBJECTID: - pr_verbose(LOG_DEFAULT, "%llu", subv->root_id); + pr_verbose(LOG_DEFAULT, "%" PRIu64, subv->info.id); break; case BTRFS_LIST_GENERATION: - pr_verbose(LOG_DEFAULT, "%llu", subv->gen); + pr_verbose(LOG_DEFAULT, "%" PRIu64, subv->info.generation); break; case BTRFS_LIST_OGENERATION: - pr_verbose(LOG_DEFAULT, "%llu", subv->ogen); + pr_verbose(LOG_DEFAULT, "%" PRIu64, subv->info.otransid); break; case BTRFS_LIST_PARENT: - pr_verbose(LOG_DEFAULT, "%llu", subv->ref_tree); - break; + /* + * Top level used to mean something else, but since 4f5ebb3ef553 + * ("Btrfs-progs: fix to make list specified directory's subvolumes + * work") it was always set to the parent ID. See + * https://lore.kernel.org/all/bdd9af61-b408-c8d2-6697-84230b0bcf89@gmail.com/. + */ case BTRFS_LIST_TOP_LEVEL: - pr_verbose(LOG_DEFAULT, "%llu", subv->top_id); + pr_verbose(LOG_DEFAULT, "%" PRIu64, subv->info.parent_id); break; case BTRFS_LIST_OTIME: - if (subv->otime) { + if (subv->info.otime.tv_sec) { struct tm tm; - localtime_r(&subv->otime, &tm); + localtime_r(&subv->info.otime.tv_sec, &tm); strftime(tstr, 256, "%Y-%m-%d %X", &tm); } else strcpy(tstr, "-"); pr_verbose(LOG_DEFAULT, "%s", tstr); break; case BTRFS_LIST_UUID: - if (uuid_is_null(subv->uuid)) + if (uuid_is_null(subv->info.uuid)) strcpy(uuidparse, "-"); else - uuid_unparse(subv->uuid, uuidparse); + uuid_unparse(subv->info.uuid, uuidparse); pr_verbose(LOG_DEFAULT, "%-36s", uuidparse); break; case BTRFS_LIST_PUUID: - if (uuid_is_null(subv->puuid)) + if (uuid_is_null(subv->info.parent_uuid)) strcpy(uuidparse, "-"); else - uuid_unparse(subv->puuid, uuidparse); + uuid_unparse(subv->info.parent_uuid, uuidparse); pr_verbose(LOG_DEFAULT, "%-36s", uuidparse); break; case BTRFS_LIST_RUUID: - if (uuid_is_null(subv->ruuid)) + if (uuid_is_null(subv->info.received_uuid)) strcpy(uuidparse, "-"); else - uuid_unparse(subv->ruuid, uuidparse); + uuid_unparse(subv->info.received_uuid, uuidparse); pr_verbose(LOG_DEFAULT, "%-36s", uuidparse); break; case BTRFS_LIST_PATH: - BUG_ON(!subv->full_path); - pr_verbose(LOG_DEFAULT, "%s", subv->full_path); + BUG_ON(!subv->path); + pr_verbose(LOG_DEFAULT, "%s", subv->path); break; default: break; } } -static void print_one_subvol_info_raw(struct root_info *subv, - const char *raw_prefix) -{ - int i; - - for (i = 0; i < BTRFS_LIST_ALL; i++) { - if (!btrfs_list_columns[i].need_print) - continue; - - if (raw_prefix) - pr_verbose(LOG_DEFAULT, "%s",raw_prefix); - - print_subvolume_column(subv, i); - } - pr_verbose(LOG_DEFAULT, "\n"); -} - static void print_one_subvol_info_table(struct root_info *subv) { int i; @@ -1295,35 +715,34 @@ static void print_subvol_json_key(struct format_ctx *fctx, column_name = btrfs_list_columns[column].name; switch (column) { case BTRFS_LIST_OBJECTID: - fmt_print(fctx, column_name, subv->root_id); + fmt_print(fctx, column_name, subv->info.id); break; case BTRFS_LIST_GENERATION: - fmt_print(fctx, column_name, subv->gen); + fmt_print(fctx, column_name, subv->info.generation); break; case BTRFS_LIST_OGENERATION: - fmt_print(fctx, column_name, subv->ogen); + fmt_print(fctx, column_name, subv->info.otransid); break; case BTRFS_LIST_PARENT: - fmt_print(fctx, column_name, subv->ref_tree); - break; + /* See the comment in print_subvolume_column() about top level. */ case BTRFS_LIST_TOP_LEVEL: - fmt_print(fctx, column_name, subv->top_id); + fmt_print(fctx, column_name, subv->info.parent_id); break; case BTRFS_LIST_OTIME: - fmt_print(fctx, column_name, subv->otime); + fmt_print(fctx, column_name, subv->info.otime.tv_sec); break; case BTRFS_LIST_UUID: - fmt_print(fctx, column_name, subv->uuid); + fmt_print(fctx, column_name, subv->info.uuid); break; case BTRFS_LIST_PUUID: - fmt_print(fctx, column_name, subv->puuid); + fmt_print(fctx, column_name, subv->info.parent_uuid); break; case BTRFS_LIST_RUUID: - fmt_print(fctx, column_name, subv->ruuid); + fmt_print(fctx, column_name, subv->info.received_uuid); break; case BTRFS_LIST_PATH: - BUG_ON(!subv->full_path); - fmt_print(fctx, column_name, subv->full_path); + BUG_ON(!subv->path); + fmt_print(fctx, column_name, subv->path); break; default: break; @@ -1348,10 +767,10 @@ static void print_one_subvol_info_json(struct format_ctx *fctx, } -static void print_all_subvol_info(struct rb_root *sorted_tree, - enum btrfs_list_layout layout, const char *raw_prefix) +static void print_all_subvol_info(struct subvol_list *subvols, + enum btrfs_list_layout layout) { - struct rb_node *n; + size_t i; struct root_info *entry; struct format_ctx fctx; @@ -1362,13 +781,8 @@ static void print_all_subvol_info(struct rb_root *sorted_tree, fmt_print_start_group(&fctx, "subvolume-list", JSON_TYPE_ARRAY); } - n = rb_first(sorted_tree); - while (n) { - entry = to_root_info_sorted(n); - - /* The toplevel subvolume is not listed by default */ - if (entry->root_id == BTRFS_FS_TREE_OBJECTID) - goto next; + for (i = 0; i < subvols->num; i++) { + entry = &subvols->subvols[i]; switch (layout) { case BTRFS_LIST_LAYOUT_DEFAULT: @@ -1377,15 +791,10 @@ static void print_all_subvol_info(struct rb_root *sorted_tree, case BTRFS_LIST_LAYOUT_TABLE: print_one_subvol_info_table(entry); break; - case BTRFS_LIST_LAYOUT_RAW: - print_one_subvol_info_raw(entry, raw_prefix); - break; case BTRFS_LIST_LAYOUT_JSON: print_one_subvol_info_json(&fctx, entry); break; } -next: - n = rb_next(n); } if (layout == BTRFS_LIST_LAYOUT_JSON) { @@ -1394,62 +803,186 @@ static void print_all_subvol_info(struct rb_root *sorted_tree, } } -static int btrfs_list_subvols(int fd, struct rb_root *root_lookup) +static void free_subvol_list(struct subvol_list *subvols) +{ + size_t i; + + if (subvols) { + for (i = 0; i < subvols->num; i++) + free(subvols->subvols[i].path); + free(subvols); + } +} + +static struct subvol_list *btrfs_list_deleted_subvols(int fd, + struct btrfs_list_filter_set *filter_set) { - int ret; - struct rb_node *n; + struct subvol_list *subvols = NULL; + uint64_t *ids = NULL; + size_t i, n; + enum btrfs_util_error err; + int ret = -1; + + err = btrfs_util_deleted_subvolumes_fd(fd, &ids, &n); + if (err) { + error_btrfs_util(err); + return NULL; + } + + subvols = malloc(sizeof(*subvols) + n * sizeof(subvols->subvols[0])); + if (!subvols) { + error("out of memory"); + goto out; + } + + subvols->num = 0; + for (i = 0; i < n; i++) { + struct root_info *subvol = &subvols->subvols[subvols->num]; + + err = btrfs_util_subvolume_info_fd(fd, ids[i], &subvol->info); + if (err == BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) { + /* + * The subvolume might have been cleaned up since it was + * returned. + */ + continue; + } else if (err) { + error_btrfs_util(err); + goto out; + } + + subvol->path = strdup("DELETED"); + if (!subvol->path) + goto out; - ret = list_subvol_search(fd, root_lookup); + if (!filter_root(subvol, filter_set)) { + free(subvol->path); + continue; + } + + subvols->num++; + } + + ret = 0; +out: if (ret) { - error("can't perform the search: %m"); - return ret; + free_subvol_list(subvols); + subvols = NULL; } + free(ids); + return subvols; +} - /* - * now we have an rbtree full of root_info objects, but we need to fill - * in their path names within the subvol that is referencing each one. - */ - n = rb_first(root_lookup); - while (n) { - struct root_info *entry; +static struct subvol_list *btrfs_list_subvols(int fd, bool include_top, + bool below, + struct btrfs_list_filter_set *filter_set) +{ + u64 top_id = below ? 0 : BTRFS_FS_TREE_OBJECTID; + struct subvol_list *subvols; + size_t capacity = 4; + struct btrfs_util_subvolume_iterator *iter; + enum btrfs_util_error err; + int ret = -1; + + subvols = malloc(sizeof(*subvols) + + capacity * sizeof(subvols->subvols[0])); + if (!subvols) { + error("out of memory"); + return NULL; + } + subvols->num = 0; - entry = to_root_info(n); - ret = lookup_ino_path(fd, entry); - if (ret && ret != -ENOENT) - return ret; - n = rb_next(n); + err = btrfs_util_create_subvolume_iterator_fd(fd, top_id, 0, &iter); + if (err) { + iter = NULL; + error_btrfs_util(err); + goto out; } - return 0; + if (include_top) { + err = btrfs_util_subvolume_info_fd(fd, top_id, + &subvols->subvols[0].info); + if (err) { + error_btrfs_util(err); + goto out; + } + subvols->subvols[0].path = strdup(""); + if (!subvols->subvols[0].path) { + error("out of memory"); + goto out; + } + subvols->num++; + } + + for (;;) { + struct root_info subvol; + + err = btrfs_util_subvolume_iterator_next_info(iter, + &subvol.path, + &subvol.info); + if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) { + break; + } else if (err) { + error_btrfs_util(err); + goto out; + } + + if (!filter_root(&subvol, filter_set)) { + free(subvol.path); + continue; + } + + if (subvols->num >= capacity) { + struct subvol_list *new_subvols; + size_t new_capacity = max_t(size_t, 1, capacity * 2); + + new_subvols = realloc(subvols, + sizeof(*new_subvols) + + new_capacity * + sizeof(new_subvols->subvols[0])); + if (!new_subvols) { + error("out of memory"); + free(subvol.path); + goto out; + } + + subvols = new_subvols; + capacity = new_capacity; + } + + subvols->subvols[subvols->num] = subvol; + subvols->num++; + } + + ret = 0; +out: + if (iter) + btrfs_util_destroy_subvolume_iterator(iter); + if (ret) { + free_subvol_list(subvols); + subvols = NULL; + } + return subvols; } static int btrfs_list_subvols_print(int fd, struct btrfs_list_filter_set *filter_set, struct btrfs_list_comparer_set *comp_set, - enum btrfs_list_layout layout, int full_path, - const char *raw_prefix) + enum btrfs_list_layout layout, bool include_top, + bool below) { - struct rb_root root_lookup; - struct rb_root root_sort; - int ret = 0; - u64 top_id = 0; - - if (full_path) { - ret = lookup_path_rootid(fd, &top_id); - if (ret) { - errno = -ret; - error("cannot resolve rootid for path: %m"); - return ret; - } - } + struct subvol_list *subvols; - ret = btrfs_list_subvols(fd, &root_lookup); - if (ret) - return ret; - filter_and_sort_subvol(&root_lookup, &root_sort, filter_set, - comp_set, top_id); + if (filter_set->only_deleted) + subvols = btrfs_list_deleted_subvols(fd, filter_set); + else + subvols = btrfs_list_subvols(fd, include_top, below, filter_set); + if (!subvols) + return -1; - print_all_subvol_info(&root_sort, layout, raw_prefix); - rb_free_nodes(&root_lookup, free_root_info); + sort_subvols(comp_set, subvols); + + print_all_subvol_info(subvols, layout); + free_subvol_list(subvols); return 0; } @@ -1584,10 +1117,13 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg u64 flags = 0; int fd = -1; u64 top_id; + char *top_path = NULL; int ret = -1, uerr = 0; char *subvol; + bool is_list_below = false; bool is_list_all = false; - bool is_only_in_path = false; + bool is_old_a_option = false; + bool is_old_o_option = false; enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT; filter_set = btrfs_list_alloc_filter_set(); @@ -1602,7 +1138,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg }; c = getopt_long(argc, argv, - "acdgopqsurRG:C:t", long_options, NULL); + "acdgopqsurRG:C:tOA", long_options, NULL); if (c < 0) break; @@ -1611,21 +1147,19 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg btrfs_list_setup_print_column(BTRFS_LIST_PARENT); break; case 'a': - is_list_all = true; + is_old_a_option = true; break; case 'c': btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION); break; case 'd': - btrfs_list_setup_filter(&filter_set, - BTRFS_LIST_FILTER_DELETED, - 0); + filter_set->only_deleted = 1; break; case 'g': btrfs_list_setup_print_column(BTRFS_LIST_GENERATION); break; case 'o': - is_only_in_path = true; + is_old_o_option = true; break; case 't': layout = BTRFS_LIST_LAYOUT_TABLE; @@ -1678,6 +1212,12 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg goto out; } break; + case 'O': + is_list_below = true; + break; + case 'A': + is_list_all = true; + break; default: uerr = 1; @@ -1688,6 +1228,19 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg if (check_argc_exact(argc - optind, 1)) goto out; + /* + * The path filtering options and -d don't make sense together. For -O + * and -A, we're strict about not combining them with each other or with + * -o, -a, or -d. The -o, -a, and -d options are older and have never + * been restricted, so although they produce dubious results when + * combined, we allow it for backwards compatibility. + */ + if (is_list_below + is_list_all + + (is_old_a_option || is_old_o_option || filter_set->only_deleted) > 1) { + error("-O, -A, -o, -a, and -d are mutually exclusive"); + goto out; + } + subvol = argv[optind]; fd = btrfs_open_dir(subvol); if (fd < 0) { @@ -1707,28 +1260,44 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg goto out; } - if (is_list_all) + if (is_old_a_option) btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FULL_PATH, top_id); - else if (is_only_in_path) + else if (is_old_o_option) btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_TOPID_EQUAL, top_id); + else if (!is_list_below && !is_list_all && !filter_set->only_deleted) { + enum btrfs_util_error err; + + err = btrfs_util_subvolume_get_path_fd(fd, top_id, &top_path); + if (err) { + ret = -1; + error_btrfs_util(err); + goto out; + } + btrfs_list_setup_filter(&filter_set, + BTRFS_LIST_FILTER_REMOVE_PATH_PREFIX, + (u64)top_path); + } /* by default we shall print the following columns*/ btrfs_list_setup_print_column(BTRFS_LIST_OBJECTID); btrfs_list_setup_print_column(BTRFS_LIST_GENERATION); - btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL); + btrfs_list_setup_print_column(is_list_below || is_list_all ? + BTRFS_LIST_PARENT : BTRFS_LIST_TOP_LEVEL); btrfs_list_setup_print_column(BTRFS_LIST_PATH); if (bconf.output_format == CMD_FORMAT_JSON) layout = BTRFS_LIST_LAYOUT_JSON; - ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, - layout, !is_list_all && !is_only_in_path, NULL); + ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, layout, + is_list_below || is_list_all, + is_list_below); out: + free(top_path); close(fd); if (filter_set) free(filter_set); diff --git a/cmds/subvolume.c b/cmds/subvolume.c index 52bc88500e..f34d940097 100644 --- a/cmds/subvolume.c +++ b/cmds/subvolume.c @@ -46,6 +46,7 @@ #include "common/units.h" #include "common/format-output.h" #include "common/tree-search.h" +#include "common/parse-utils.h" #include "cmds/commands.h" #include "cmds/qgroup.h" @@ -140,28 +141,15 @@ static const char * const cmd_subvolume_create_usage[] = { NULL }; -static int create_one_subvolume(const char *dst, struct btrfs_qgroup_inherit *inherit, +static int create_one_subvolume(const char *dst, struct btrfs_util_qgroup_inherit *inherit, bool create_parents) { int ret; - int len; - int fddst = -1; char *dupname = NULL; char *dupdir = NULL; const char *newname; char *dstdir; - - ret = path_is_dir(dst); - if (ret < 0 && ret != -ENOENT) { - errno = -ret; - error("cannot access %s: %m", dst); - goto out; - } - if (ret >= 0) { - error("target path already exists: %s", dst); - ret = -EEXIST; - goto out; - } + enum btrfs_util_error err; dupname = strdup(dst); if (!dupname) { @@ -179,19 +167,6 @@ static int create_one_subvolume(const char *dst, struct btrfs_qgroup_inherit *in } dstdir = path_dirname(dupdir); - if (!test_issubvolname(newname)) { - error("invalid subvolume name: %s", newname); - ret = -EINVAL; - goto out; - } - - len = strlen(newname); - if (len > BTRFS_VOL_NAME_MAX) { - error("subvolume name too long: %s", newname); - ret = -EINVAL; - goto out; - } - if (create_parents) { char p[PATH_MAX] = { 0 }; char dstdir_dup[PATH_MAX]; @@ -223,47 +198,57 @@ static int create_one_subvolume(const char *dst, struct btrfs_qgroup_inherit *in } } - fddst = btrfs_open_dir(dstdir); - if (fddst < 0) { - ret = fddst; + err = btrfs_util_subvolume_create(dst, 0, NULL, inherit); + if (err) { + error_btrfs_util(err); + ret = -errno; goto out; } - if (inherit) { - struct btrfs_ioctl_vol_args_v2 args; + pr_verbose(LOG_DEFAULT, "Create subvolume '%s/%s'\n", dstdir, newname); - memset(&args, 0, sizeof(args)); - strncpy_null(args.name, newname, sizeof(args.name)); - args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT; - args.size = btrfs_qgroup_inherit_size(inherit); - args.qgroup_inherit = inherit; + ret = 0; - ret = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE_V2, &args); - } else { - struct btrfs_ioctl_vol_args args; +out: + free(dupname); + free(dupdir); + + return ret; +} + +static int qgroup_inherit_add_group(struct btrfs_util_qgroup_inherit **inherit, + const char *arg) +{ + enum btrfs_util_error err; + u64 qgroupid; - memset(&args, 0, sizeof(args)); - strncpy_null(args.name, newname, sizeof(args.name)); - ret = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args); + if (!*inherit) { + err = btrfs_util_qgroup_inherit_create(0, inherit); + if (err) { + error_btrfs_util(err); + return -errno; + } } - if (ret < 0) { - error("cannot create subvolume: %m"); - goto out; + qgroupid = parse_qgroupid_or_path(optarg); + if (qgroupid == 0) { + error("invalid qgroup specification, qgroupid must not be 0"); + return -EINVAL; } - pr_verbose(LOG_DEFAULT, "Create subvolume '%s/%s'\n", dstdir, newname); -out: - close(fddst); - free(dupname); - free(dupdir); + err = btrfs_util_qgroup_inherit_add_group(inherit, qgroupid); + if (err) { + error_btrfs_util(err); + return -errno; + } - return ret; + return 0; } + static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **argv) { int retval, ret; - struct btrfs_qgroup_inherit *inherit = NULL; + struct btrfs_util_qgroup_inherit *inherit = NULL; bool has_error = false; bool create_parents = false; @@ -281,7 +266,7 @@ static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **a switch (c) { case 'i': - ret = btrfs_qgroup_inherit_add_group(&inherit, optarg); + ret = qgroup_inherit_add_group(&inherit, optarg); if (ret) { retval = ret; goto out; @@ -310,7 +295,7 @@ static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **a if (!has_error) retval = 0; out: - free(inherit); + btrfs_util_qgroup_inherit_destroy(inherit); return retval; } @@ -347,6 +332,8 @@ static const char * const cmd_subvolume_delete_usage[] = { OPTLINE("-c|--commit-after", "wait for transaction commit at the end of the operation"), OPTLINE("-C|--commit-each", "wait for transaction commit after deleting each subvolume"), OPTLINE("-i|--subvolid", "subvolume id of the to be removed subvolume"), + OPTLINE("-R|--recursive", "delete accessible subvolumes beneath each subvolume recursively, " + "this is not atomic, may need root to delete subvolumes not accessible by the user"), OPTLINE("-v|--verbose", "deprecated, alias for global -v option"), HELPINFO_INSERT_GLOBALS, HELPINFO_INSERT_VERBOSE, @@ -367,6 +354,7 @@ static int cmd_subvolume_delete(const struct cmd_struct *cmd, int argc, char **a char *path = NULL; int commit_mode = 0; bool subvol_path_not_found = false; + int flags = 0; u8 fsid[BTRFS_FSID_SIZE]; u64 subvolid = 0; char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; @@ -383,11 +371,12 @@ static int cmd_subvolume_delete(const struct cmd_struct *cmd, int argc, char **a {"commit-after", no_argument, NULL, 'c'}, {"commit-each", no_argument, NULL, 'C'}, {"subvolid", required_argument, NULL, 'i'}, + {"recursive", no_argument, NULL, 'R'}, {"verbose", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0} }; - c = getopt_long(argc, argv, "cCi:v", long_options, NULL); + c = getopt_long(argc, argv, "cCi:Rv", long_options, NULL); if (c < 0) break; @@ -401,6 +390,9 @@ static int cmd_subvolume_delete(const struct cmd_struct *cmd, int argc, char **a case 'i': subvolid = arg_strtou64(optarg); break; + case 'R': + flags |= BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE; + break; case 'v': bconf_be_verbose(); break; @@ -416,6 +408,11 @@ static int cmd_subvolume_delete(const struct cmd_struct *cmd, int argc, char **a if (subvolid > 0 && check_argc_exact(argc - optind, 1)) return 1; + if (subvolid > 0 && flags & BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE) { + error("option --recursive is not supported with --subvolid"); + return 1; + } + pr_verbose(LOG_INFO, "Transaction commit: %s\n", !commit_mode ? "none (default)" : commit_mode == COMMIT_AFTER ? "at the end" : "after each"); @@ -528,7 +525,7 @@ static int cmd_subvolume_delete(const struct cmd_struct *cmd, int argc, char **a /* Start deleting. */ if (subvolid == 0) - err = btrfs_util_delete_subvolume_fd(fd, vname, 0); + err = btrfs_util_delete_subvolume_fd(fd, vname, flags); else err = btrfs_util_delete_subvolume_by_id_fd(fd, subvolid); if (err) { @@ -638,18 +635,11 @@ static int cmd_subvolume_snapshot(const struct cmd_struct *cmd, int argc, char * { char *subvol, *dst; int res, retval; - int fd = -1, fddst = -1; - int len; - bool readonly = false; - char *dupname = NULL; - char *dupdir = NULL; - const char *newname; - char *dstdir; + char *dstdir = NULL; enum btrfs_util_error err; - struct btrfs_ioctl_vol_args_v2 args; - struct btrfs_qgroup_inherit *inherit = NULL; + struct btrfs_util_qgroup_inherit *inherit = NULL; + int flags = 0; - memset(&args, 0, sizeof(args)); optind = 0; while (1) { int c = getopt(argc, argv, "i:r"); @@ -658,14 +648,14 @@ static int cmd_subvolume_snapshot(const struct cmd_struct *cmd, int argc, char * switch (c) { case 'i': - res = btrfs_qgroup_inherit_add_group(&inherit, optarg); + res = qgroup_inherit_add_group(&inherit, optarg); if (res) { retval = res; goto out; } break; case 'r': - readonly = true; + flags |= BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY; break; default: usage_unknown_option(cmd, argv); @@ -699,72 +689,49 @@ static int cmd_subvolume_snapshot(const struct cmd_struct *cmd, int argc, char * } if (res > 0) { + char *dupname; + const char *newname; + dupname = strdup(subvol); newname = path_basename(dupname); - dstdir = dst; - } else { - dupname = strdup(dst); - newname = path_basename(dupname); - dupdir = strdup(dst); - dstdir = path_dirname(dupdir); - } - if (!test_issubvolname(newname)) { - error("invalid snapshot name '%s'", newname); - goto out; - } - - len = strlen(newname); - if (len > BTRFS_VOL_NAME_MAX) { - error("snapshot name too long '%s'", newname); - goto out; - } - - fddst = btrfs_open_dir(dstdir); - if (fddst < 0) - goto out; - - fd = btrfs_open_dir(subvol); - if (fd < 0) - goto out; + dstdir = malloc(strlen(dst) + 1 + strlen(newname) + 1); + if (!dstdir) { + error_msg(ERROR_MSG_MEMORY, NULL); + free(dupname); + goto out; + } - if (readonly) - args.flags |= BTRFS_SUBVOL_RDONLY; + dstdir[0] = 0; + strcpy(dstdir, dst); + strcat(dstdir, "/"); + strcat(dstdir, newname); - args.fd = fd; - if (inherit) { - args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT; - args.size = btrfs_qgroup_inherit_size(inherit); - args.qgroup_inherit = inherit; + free(dupname); + } else { + dstdir = strdup(dst); } - strncpy_null(args.name, newname, sizeof(args.name)); - res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args); - if (res < 0) { - if (errno == ETXTBSY) - error("cannot snapshot '%s': source subvolume contains an active swapfile (%m)", subvol); - else - error("cannot snapshot '%s': %m", subvol); + err = btrfs_util_subvolume_snapshot(subvol, dstdir, flags, NULL, inherit); + if (err) { + error_btrfs_util(err); goto out; } retval = 0; /* success */ - if (readonly) + if (flags & BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY) pr_verbose(LOG_DEFAULT, - "Create readonly snapshot of '%s' in '%s/%s'\n", - subvol, dstdir, newname); + "Create readonly snapshot of '%s' in '%s'\n", + subvol, dstdir); else pr_verbose(LOG_DEFAULT, - "Create snapshot of '%s' in '%s/%s'\n", - subvol, dstdir, newname); + "Create snapshot of '%s' in '%s'\n", + subvol, dstdir); out: - close(fddst); - close(fd); - free(inherit); - free(dupname); - free(dupdir); + free(dstdir); + btrfs_util_qgroup_inherit_destroy(inherit); return retval; } diff --git a/common/clear-cache.c b/common/clear-cache.c index 960c3466ce..6493866d82 100644 --- a/common/clear-cache.c +++ b/common/clear-cache.c @@ -555,69 +555,86 @@ int truncate_free_ino_items(struct btrfs_root *root) return ret; } -int clear_ino_cache_items(struct btrfs_fs_info *fs_info) +/* + * Find a root item whose key.objectid >= @rootid, and save the found + * key into @found_key. + * + * Return 0 if a root item is found. + * Return >0 if no more root item is found. + * Return <0 for error. + */ +static int find_next_root(struct btrfs_fs_info *fs_info, u64 rootid, + struct btrfs_key *found_key) { - int ret; + struct btrfs_key key = { + .objectid = rootid, + .type = BTRFS_ROOT_ITEM_KEY, + .offset = 0, + }; struct btrfs_path path = { 0 }; - struct btrfs_key key; - - key.objectid = BTRFS_FS_TREE_OBJECTID; - key.type = BTRFS_ROOT_ITEM_KEY; - key.offset = 0; + int ret; ret = btrfs_search_slot(NULL, fs_info->tree_root, &key, &path, 0, 0); if (ret < 0) return ret; + while (true) { + btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]); + if (key.type == BTRFS_ROOT_ITEM_KEY && key.objectid >= rootid) { + memcpy(found_key, &key, sizeof(key)); + ret = 0; + goto out; + } + ret = btrfs_next_item(fs_info->tree_root, &path); + if (ret) + goto out; + } +out: + btrfs_release_path(&path); + return ret; +} - while(1) { - struct btrfs_key found_key; +int clear_ino_cache_items(struct btrfs_fs_info *fs_info) +{ + u64 cur_subvol = BTRFS_FS_TREE_OBJECTID; + int ret; - btrfs_item_key_to_cpu(path.nodes[0], &found_key, path.slots[0]); - if (found_key.type == BTRFS_ROOT_ITEM_KEY && - is_fstree(found_key.objectid)) { - struct btrfs_root *root; + while (1) { + struct btrfs_key key = { 0 }; + struct btrfs_root *root; - found_key.offset = (u64)-1; - root = btrfs_read_fs_root(fs_info, &found_key); - if (IS_ERR(root)) - goto next; - ret = truncate_free_ino_items(root); - if (ret) - goto out; - printf("Successfully cleaned up ino cache for root id: %lld\n", - root->objectid); - } else { - /* If we get a negative tree this means it's the last one */ - if ((s64)found_key.objectid < 0 && - found_key.type == BTRFS_ROOT_ITEM_KEY) - goto out; + ret = find_next_root(fs_info, cur_subvol, &key); + if (ret < 0) { + errno = -ret; + error("failed to find the next root item for rootid %llu: %m", + cur_subvol); + break; } - - /* - * Only fs roots contain an ino cache information - either - * FS_TREE_OBJECTID or subvol id >= BTRFS_FIRST_FREE_OBJECTID - */ -next: - if (key.objectid == BTRFS_FS_TREE_OBJECTID) { - key.objectid = BTRFS_FIRST_FREE_OBJECTID; - btrfs_release_path(&path); - ret = btrfs_search_slot(NULL, fs_info->tree_root, &key, - &path, 0, 0); - if (ret < 0) - return ret; - } else { - ret = btrfs_next_item(fs_info->tree_root, &path); - if (ret < 0) { - goto out; - } else if (ret > 0) { - ret = 0; - goto out; - } + if (ret > 0 || !is_fstree(key.objectid)) { + ret = 0; + break; } - } + root = btrfs_read_fs_root(fs_info, &key); + if (IS_ERR(root)) { + ret = PTR_ERR(root); + errno = -ret; + error("failed to read root %llu: %m", key.objectid); + break; + } + ret = truncate_free_ino_items(root); + if (ret < 0) { + errno = -ret; + error("failed to clean up ino cache for root %llu: %m", + key.objectid); + break; + } + printf("Successfully cleaned up ino cache for root id: %lld\n", + root->objectid); -out: - btrfs_release_path(&path); + if (cur_subvol == BTRFS_FS_TREE_OBJECTID) + cur_subvol = BTRFS_FIRST_FREE_OBJECTID; + else + cur_subvol = root->objectid + 1; + } return ret; } diff --git a/common/filesystem-utils.c b/common/filesystem-utils.c index 7e898a3d19..05451093a9 100644 --- a/common/filesystem-utils.c +++ b/common/filesystem-utils.c @@ -94,7 +94,8 @@ static int set_label_unmounted(const char *dev, const char *label) /* Open the super_block at the default location * and as read-write. */ - root = open_ctree(dev, 0, OPEN_CTREE_WRITES); + root = open_ctree(dev, 0, OPEN_CTREE_WRITES | + OPEN_CTREE_EXCLUSIVE); if (!root) /* errors are printed by open_ctree() */ return -1; diff --git a/common/root-tree-utils.c b/common/root-tree-utils.c index 09b9ada01d..a416fcebbe 100644 --- a/common/root-tree-utils.c +++ b/common/root-tree-utils.c @@ -66,7 +66,8 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans, * The created tree root would have its root_ref as 1. * Thus for subvolumes caller needs to properly add ROOT_BACKREF items. */ -int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid) +int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid, + bool readonly) { struct btrfs_fs_info *fs_info = trans->fs_info; struct btrfs_root *root; @@ -98,6 +99,13 @@ int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid) ret = btrfs_make_root_dir(trans, root, BTRFS_FIRST_FREE_OBJECTID); if (ret < 0) goto error; + + btrfs_set_stack_inode_flags(&root->root_item.inode, + BTRFS_INODE_ROOT_ITEM_INIT); + + if (readonly) + btrfs_set_root_flags(&root->root_item, BTRFS_ROOT_SUBVOL_RDONLY); + ret = btrfs_update_root(trans, fs_info->tree_root, &root->root_key, &root->root_item); if (ret < 0) diff --git a/common/root-tree-utils.h b/common/root-tree-utils.h index 3cb508022e..c4964a3624 100644 --- a/common/root-tree-utils.h +++ b/common/root-tree-utils.h @@ -21,7 +21,8 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid); -int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid); +int btrfs_make_subvolume(struct btrfs_trans_handle *trans, u64 objectid, + bool readonly); int btrfs_link_subvolume(struct btrfs_trans_handle *trans, struct btrfs_root *parent_root, u64 parent_dir, const char *name, diff --git a/convert/main.c b/convert/main.c index aa253781ee..a227cc6fef 100644 --- a/convert/main.c +++ b/convert/main.c @@ -1022,13 +1022,14 @@ static int init_btrfs(struct btrfs_mkfs_config *cfg, struct btrfs_root *root, BTRFS_FIRST_FREE_OBJECTID); /* subvol for fs image file */ - ret = btrfs_make_subvolume(trans, CONV_IMAGE_SUBVOL_OBJECTID); + ret = btrfs_make_subvolume(trans, CONV_IMAGE_SUBVOL_OBJECTID, false); if (ret < 0) { error("failed to create subvolume image root: %d", ret); goto err; } /* subvol for data relocation tree */ - ret = btrfs_make_subvolume(trans, BTRFS_DATA_RELOC_TREE_OBJECTID); + ret = btrfs_make_subvolume(trans, BTRFS_DATA_RELOC_TREE_OBJECTID, + false); if (ret < 0) { error("failed to create DATA_RELOC root: %d", ret); goto err; @@ -1373,7 +1374,8 @@ static int do_convert(const char *devname, u32 convert_flags, u32 nodesize, btrfs_sb_committed = true; root = open_ctree_fd(fd, devname, 0, - OPEN_CTREE_WRITES | OPEN_CTREE_TEMPORARY_SUPER); + OPEN_CTREE_WRITES | OPEN_CTREE_TEMPORARY_SUPER | + OPEN_CTREE_EXCLUSIVE); if (!root) { error("unable to open ctree for finalization"); goto fail; diff --git a/convert/source-ext2.c b/convert/source-ext2.c index acba5db7ee..d06f90a98e 100644 --- a/convert/source-ext2.c +++ b/convert/source-ext2.c @@ -390,6 +390,7 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, ext2_filsys ext2_fs, ext2_ino_t ext2_ino, u32 convert_flags) { + struct btrfs_fs_info *fs_info = trans->fs_info; int ret; char *buffer = NULL; errcode_t err; @@ -397,8 +398,20 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, u32 last_block; u32 sectorsize = root->fs_info->sectorsize; u64 inode_size = btrfs_stack_inode_size(btrfs_inode); + bool meet_inline_size_limit; struct blk_iterate_data data; + if (S_ISLNK(btrfs_stack_inode_mode(btrfs_inode))) { + meet_inline_size_limit = inode_size <= btrfs_symlink_max_size(fs_info); + if (!meet_inline_size_limit) { + error("symlink too large for ext2 inode %u, has %llu max %u", + ext2_ino, inode_size, btrfs_symlink_max_size(fs_info)); + return -ENAMETOOLONG; + } + } else { + meet_inline_size_limit = inode_size <= btrfs_data_inline_max_size(fs_info); + } + init_blk_iterate_data(&data, trans, root, btrfs_inode, objectid, convert_flags & CONVERT_FLAG_DATACSUM); @@ -430,8 +443,7 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, if (ret) goto fail; if ((convert_flags & CONVERT_FLAG_INLINE_DATA) && data.first_block == 0 - && data.num_blocks > 0 && inode_size < sectorsize - && inode_size <= BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info)) { + && data.num_blocks > 0 && meet_inline_size_limit) { u64 num_bytes = data.num_blocks * sectorsize; u64 disk_bytenr = data.disk_block * sectorsize; u64 nbytes; @@ -476,21 +488,26 @@ static int ext2_create_symlink(struct btrfs_trans_handle *trans, int ret; char *pathname; u64 inode_size = btrfs_stack_inode_size(btrfs_inode); + if (ext2fs_inode_data_blocks2(ext2_fs, ext2_inode)) { - btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1); + if (inode_size > btrfs_symlink_max_size(trans->fs_info)) { + error("symlink too large for ext2 inode %u, has %llu max %u", + ext2_ino, inode_size, + btrfs_symlink_max_size(trans->fs_info)); + return -ENAMETOOLONG; + } ret = ext2_create_file_extents(trans, root, objectid, btrfs_inode, ext2_fs, ext2_ino, CONVERT_FLAG_DATACSUM | CONVERT_FLAG_INLINE_DATA); - btrfs_set_stack_inode_size(btrfs_inode, inode_size); return ret; } pathname = (char *)&(ext2_inode->i_block[0]); BUG_ON(pathname[inode_size] != 0); ret = btrfs_insert_inline_extent(trans, root, objectid, 0, - pathname, inode_size + 1); - btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1); + pathname, inode_size); + btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size); return ret; } diff --git a/convert/source-reiserfs.c b/convert/source-reiserfs.c index 3edc72ed08..3475b15277 100644 --- a/convert/source-reiserfs.c +++ b/convert/source-reiserfs.c @@ -537,9 +537,15 @@ static int reiserfs_copy_symlink(struct btrfs_trans_handle *trans, symlink = tp_item_body(&path); len = get_ih_item_len(tp_item_head(&path)); + if (len > btrfs_symlink_max_size(trans->fs_info)) { + error("symlink too large, has %u max %u", + len, btrfs_symlink_max_size(trans->fs_info)); + ret = -ENAMETOOLONG; + goto fail; + } ret = btrfs_insert_inline_extent(trans, root, objectid, 0, - symlink, len + 1); - btrfs_set_stack_inode_nbytes(btrfs_inode, len + 1); + symlink, len); + btrfs_set_stack_inode_nbytes(btrfs_inode, len); fail: pathrelse(&path); return ret; diff --git a/image/image-restore.c b/image/image-restore.c index bed5e78d22..667b981123 100644 --- a/image/image-restore.c +++ b/image/image-restore.c @@ -1790,7 +1790,8 @@ int restore_metadump(const char *input, FILE *out, int old_restore, oca.filename = target; oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_RESTORE | - OPEN_CTREE_PARTIAL | OPEN_CTREE_SKIP_LEAF_ITEM_CHECKS; + OPEN_CTREE_PARTIAL | OPEN_CTREE_SKIP_LEAF_ITEM_CHECKS | + OPEN_CTREE_EXCLUSIVE; info = open_ctree_fs_info(&oca); if (!info) { error("open ctree failed"); @@ -1855,6 +1856,7 @@ int restore_metadump(const char *input, FILE *out, int old_restore, root = open_ctree_fd(fileno(out), target, 0, OPEN_CTREE_PARTIAL | OPEN_CTREE_WRITES | + OPEN_CTREE_EXCLUSIVE | OPEN_CTREE_NO_DEVICES | OPEN_CTREE_ALLOW_TRANSID_MISMATCH | OPEN_CTREE_SKIP_LEAF_ITEM_CHECKS); diff --git a/kernel-shared/file-item.c b/kernel-shared/file-item.c index d2da56e1f5..eb9024022d 100644 --- a/kernel-shared/file-item.c +++ b/kernel-shared/file-item.c @@ -26,6 +26,7 @@ #include "kernel-shared/extent_io.h" #include "kernel-shared/uapi/btrfs.h" #include "common/internal.h" +#include "common/messages.h" #define MAX_CSUM_ITEMS(r, size) ((((BTRFS_LEAF_DATA_SIZE(r->fs_info) - \ sizeof(struct btrfs_item) * 2) / \ @@ -88,6 +89,7 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid, u64 offset, const char *buffer, size_t size) { + struct btrfs_fs_info *fs_info = trans->fs_info; struct btrfs_key key; struct btrfs_path *path; struct extent_buffer *leaf; @@ -97,6 +99,10 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, int err = 0; int ret; + if (size > max(btrfs_symlink_max_size(fs_info), + btrfs_data_inline_max_size(fs_info))) + return -EUCLEAN; + path = btrfs_alloc_path(); if (!path) return -ENOMEM; diff --git a/kernel-shared/file-item.h b/kernel-shared/file-item.h index 0df8f4dfea..2c1e17c990 100644 --- a/kernel-shared/file-item.h +++ b/kernel-shared/file-item.h @@ -92,5 +92,23 @@ int btrfs_csum_file_block(struct btrfs_trans_handle *trans, u64 logical, int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid, u64 offset, const char *buffer, size_t size); +/* + * For symlink we allow up to PATH_MAX - 1 (PATH_MAX includes the terminating NUL, + * but fs doesn't store that terminating NUL). + * + * But for inlined data extents, the up limit is sectorsize - 1 (inclusive), or a + * regular extent should be created instead. + */ +static inline u32 btrfs_symlink_max_size(struct btrfs_fs_info *fs_info) +{ + return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), + PATH_MAX - 1); +} + +static inline u32 btrfs_data_inline_max_size(struct btrfs_fs_info *fs_info) +{ + return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), + fs_info->sectorsize - 1); +} #endif diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c index 1f0f2d2b31..eba1c9a2b6 100644 --- a/libbtrfsutil/subvolume.c +++ b/libbtrfsutil/subvolume.c @@ -32,11 +32,6 @@ #include "btrfsutil_internal.h" -static bool is_root(void) -{ - return geteuid() == 0; -} - /* * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening * a file descriptor and calling it, because fstat() and fstatfs() don't accept @@ -451,8 +446,10 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id, if (err) return err; - if (!is_root()) - return get_subvolume_info_unprivileged(fd, subvol); + err = get_subvolume_info_unprivileged(fd, subvol); + if (err != BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED || + errno != ENOTTY) + return err; err = btrfs_util_subvolume_id_fd(fd, &id); if (err) @@ -805,7 +802,11 @@ struct search_stack_entry { }; struct btrfs_util_subvolume_iterator { - bool use_tree_search; + /* + * 1 if using tree search, 0 if using unprivileged ioctls, -1 if not + * determined yet. + */ + int use_tree_search; int fd; /* cur_fd is only used for subvolume_iterator_next_unprivileged(). */ int cur_fd; @@ -1007,14 +1008,14 @@ PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd, { struct btrfs_util_subvolume_iterator *iter; enum btrfs_util_error err; - bool use_tree_search; + int use_tree_search; if (flags & ~BTRFS_UTIL_SUBVOLUME_ITERATOR_MASK) { errno = EINVAL; return BTRFS_UTIL_ERROR_INVALID_ARGUMENT; } - use_tree_search = top != 0 || is_root(); + use_tree_search = top == 0 ? -1 : 1; if (top == 0) { err = btrfs_util_is_subvolume_fd(fd); if (err) @@ -1664,13 +1665,29 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_uti char **path_ret, uint64_t *id_ret) { + /* + * On the first iteration, iter->use_tree_search < 0. In that case, we + * try a tree search, and if it fails with a permission error, we fall + * back to the unprivileged ioctls. + */ if (iter->use_tree_search) { - return subvolume_iterator_next_tree_search(iter, path_ret, - id_ret); - } else { - return subvolume_iterator_next_unprivileged(iter, path_ret, - id_ret); + enum btrfs_util_error err; + struct search_stack_entry *entry; + + err = subvolume_iterator_next_tree_search(iter, path_ret, + id_ret); + if (iter->use_tree_search > 0) + return err; + + if (err != BTRFS_UTIL_ERROR_SEARCH_FAILED || errno != EPERM) { + iter->use_tree_search = 1; + return err; + } + entry = iter->search_stack; + entry->id = entry->search.key.min_objectid; + iter->use_tree_search = 0; } + return subvolume_iterator_next_unprivileged(iter, path_ret, id_ret); } PUBLIC enum btrfs_util_error btrfs_util_subvolume_iter_next(struct btrfs_util_subvolume_iterator *iter, char **path_ret, diff --git a/mkfs/main.c b/mkfs/main.c index b24b148dfe..06cc2484e6 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -440,6 +440,7 @@ static const char * const mkfs_usage[] = { "Creation:", OPTLINE("-b|--byte-count SIZE", "set size of each device to SIZE (filesystem size is sum of all device sizes)"), OPTLINE("-r|--rootdir DIR", "copy files from DIR to the image root directory"), + OPTLINE("-u|--subvol SUBDIR:FLAGS", "create SUBDIR as subvolume rather than normal directory, can be specified multiple times"), OPTLINE("--shrink", "(with --rootdir) shrink the filled filesystem to minimal size"), OPTLINE("-K|--nodiscard", "do not perform whole device TRIM"), OPTLINE("-f|--force", "force overwrite of existing filesystem"), @@ -1014,6 +1015,48 @@ static void *prepare_one_device(void *ctx) return NULL; } +static int parse_subvol_flags(struct rootdir_subvol *subvol, const char *flags) +{ + char *buf, *orig_buf; + int ret; + + buf = orig_buf = strdup(flags); + + if (!buf) { + error_msg(ERROR_MSG_MEMORY, NULL); + ret = -ENOMEM; + goto out; + } + + while (true) { + char *comma = strstr(buf, ","); + + if (comma) + *comma = 0; + + if (!strcmp(buf, "default")) { + subvol->is_default = true; + } else if (!strcmp(buf, "ro")) { + subvol->readonly = true; + } else if (buf[0] != 0) { + error("unrecognized subvol flag \"%s\"", buf); + ret = 1; + goto out; + } + + if (comma) + buf = comma + 1; + else + break; + } + + ret = 0; + +out: + free(orig_buf); + return ret; +} + int BOX_MAIN(mkfs)(int argc, char **argv) { char *file; @@ -1055,6 +1098,10 @@ int BOX_MAIN(mkfs)(int argc, char **argv) char *label = NULL; int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN); char *source_dir = NULL; + size_t source_dir_len = 0; + struct rootdir_subvol *rds; + bool has_default_subvol = false; + LIST_HEAD(subvols); cpu_detect_flags(); hash_init_accel(); @@ -1085,6 +1132,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) { "data", required_argument, NULL, 'd' }, { "version", no_argument, NULL, 'V' }, { "rootdir", required_argument, NULL, 'r' }, + { "subvol", required_argument, NULL, 'u' }, { "nodiscard", no_argument, NULL, 'K' }, { "features", required_argument, NULL, 'O' }, { "runtime-features", required_argument, NULL, 'R' }, @@ -1102,7 +1150,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) { NULL, 0, NULL, 0} }; - c = getopt_long(argc, argv, "A:b:fl:n:s:m:d:L:R:O:r:U:VvMKq", + c = getopt_long(argc, argv, "A:b:fl:n:s:m:d:L:R:O:r:U:VvMKqu:", long_options, NULL); if (c < 0) break; @@ -1208,6 +1256,55 @@ int BOX_MAIN(mkfs)(int argc, char **argv) free(source_dir); source_dir = strdup(optarg); break; + case 'u': { + struct rootdir_subvol *subvol; + char *colon; + + subvol = calloc(1, sizeof(struct rootdir_subvol)); + if (!subvol) { + error_msg(ERROR_MSG_MEMORY, NULL); + ret = 1; + goto error; + } + + colon = strstr(optarg, ":"); + + if (colon) { + /* Make sure we choose the last colon in + * optarg, in case the subvol name + * itself contains a colon. */ + do { + char *colon2; + + colon2 = strstr(colon + 1, ":"); + + if (colon2) + colon = colon2; + else + break; + } while (true); + + subvol->dir = strndup(optarg, colon - optarg); + if (parse_subvol_flags(subvol, colon + 1)) { + ret = 1; + goto error; + } + } else { + subvol->dir = strdup(optarg); + } + + if (subvol->is_default) { + if (has_default_subvol) { + error("subvol default flag can only be specified once"); + ret = 1; + goto error; + } + has_default_subvol = true; + } + + list_add_tail(&subvol->list, &subvols); + break; + } case 'U': strncpy_null(fs_uuid, optarg, BTRFS_UUID_UNPARSED_SIZE); break; @@ -1272,6 +1369,89 @@ int BOX_MAIN(mkfs)(int argc, char **argv) ret = 1; goto error; } + if (!list_empty(&subvols) && source_dir == NULL) { + error("option --subvol must be used with --rootdir"); + ret = 1; + goto error; + } + + if (source_dir) { + char *canonical = realpath(source_dir, NULL); + + if (!canonical) { + error("could not get canonical path to %s", source_dir); + ret = 1; + goto error; + } + + free(source_dir); + source_dir = canonical; + source_dir_len = strlen(source_dir); + } + + list_for_each_entry(rds, &subvols, list) { + char *path, *canonical; + struct rootdir_subvol *rds2; + size_t dir_len; + + dir_len = strlen(rds->dir); + + path = malloc(source_dir_len + 1 + dir_len + 1); + if (!path) { + error_msg(ERROR_MSG_MEMORY, NULL); + ret = 1; + goto error; + } + + memcpy(path, source_dir, source_dir_len); + path[source_dir_len] = '/'; + memcpy(path + source_dir_len + 1, rds->dir, dir_len + 1); + + canonical = realpath(path, NULL); + if (!canonical) { + error("could not get canonical path to %s", rds->dir); + free(path); + ret = 1; + goto error; + } + + free(path); + path = canonical; + + if (!path_exists(path)) { + error("subvolume %s does not exist", rds->dir); + free(path); + ret = 1; + goto error; + } + + if (!path_is_dir(path)) { + error("subvolume %s is not a directory", rds->dir); + free(path); + ret = 1; + goto error; + } + + rds->full_path = path; + + if (strlen(path) < source_dir_len + 1 || + memcmp(path, source_dir, source_dir_len) != 0 || + path[source_dir_len] != '/') { + error("subvolume %s is not a child of %s", rds->dir, source_dir); + ret = 1; + goto error; + } + + for (rds2 = list_first_entry(&subvols, struct rootdir_subvol, list); + rds2 != rds; + rds2 = list_next_entry(rds2, list)) { + if (strcmp(rds2->full_path, path) == 0) { + error("subvolume %s specified more than once", rds->dir); + ret = 1; + goto error; + } + } + } if (*fs_uuid) { uuid_t dummy_uuid; @@ -1666,7 +1846,8 @@ int BOX_MAIN(mkfs)(int argc, char **argv) } oca.filename = file; - oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_TEMPORARY_SUPER; + oca.flags = OPEN_CTREE_WRITES | OPEN_CTREE_TEMPORARY_SUPER | + OPEN_CTREE_EXCLUSIVE; fs_info = open_ctree_fs_info(&oca); if (!fs_info) { error("open ctree failed"); @@ -1809,7 +1990,8 @@ int BOX_MAIN(mkfs)(int argc, char **argv) goto out; } - ret = btrfs_make_subvolume(trans, BTRFS_DATA_RELOC_TREE_OBJECTID); + ret = btrfs_make_subvolume(trans, BTRFS_DATA_RELOC_TREE_OBJECTID, + false); if (ret) { error("unable to create data reloc tree: %d", ret); goto out; @@ -1821,24 +2003,37 @@ int BOX_MAIN(mkfs)(int argc, char **argv) error_msg(ERROR_MSG_START_TRANS, "%m"); goto out; } - ret = btrfs_rebuild_uuid_tree(fs_info); - if (ret < 0) - goto out; - - ret = cleanup_temp_chunks(fs_info, &allocation, data_profile, - metadata_profile, metadata_profile); - if (ret < 0) { - error("failed to cleanup temporary chunks: %d", ret); - goto out; - } if (source_dir) { pr_verbose(LOG_DEFAULT, "Rootdir from: %s\n", source_dir); - ret = btrfs_mkfs_fill_dir(source_dir, root); + + trans = btrfs_start_transaction(root, 1); + if (IS_ERR(trans)) { + errno = -PTR_ERR(trans); + error_msg(ERROR_MSG_START_TRANS, "%m"); + goto out; + } + + ret = btrfs_mkfs_fill_dir(trans, source_dir, root, + &subvols); if (ret) { error("error while filling filesystem: %d", ret); + btrfs_abort_transaction(trans, ret); + goto out; + } + + ret = btrfs_commit_transaction(trans, root); + if (ret) { + errno = -ret; + error_msg(ERROR_MSG_COMMIT_TRANS, "%m"); goto out; } + + list_for_each_entry(rds, &subvols, list) { + pr_verbose(LOG_DEFAULT, " Subvolume: %s\n", + rds->full_path); + } + if (shrink_rootdir) { pr_verbose(LOG_DEFAULT, " Shrink: yes\n"); ret = btrfs_mkfs_shrink_fs(fs_info, &shrink_size, @@ -1853,6 +2048,17 @@ int BOX_MAIN(mkfs)(int argc, char **argv) } } + ret = btrfs_rebuild_uuid_tree(fs_info); + if (ret < 0) + goto out; + + ret = cleanup_temp_chunks(fs_info, &allocation, data_profile, + metadata_profile, metadata_profile); + if (ret < 0) { + error("failed to cleanup temporary chunks: %d", ret); + goto out; + } + if (features.runtime_flags & BTRFS_FEATURE_RUNTIME_QUOTA || features.incompat_flags & BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA) { ret = setup_quota_root(fs_info); @@ -1946,6 +2152,16 @@ int BOX_MAIN(mkfs)(int argc, char **argv) free(label); free(source_dir); + while (!list_empty(&subvols)) { + struct rootdir_subvol *head; + + head = list_entry(subvols.next, struct rootdir_subvol, list); + free(head->dir); + free(head->full_path); + list_del(&head->list); + free(head); + } + return !!ret; success: diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index 41bc7f2570..70cf0f84de 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -40,6 +40,9 @@ #include "common/messages.h" #include "common/utils.h" #include "common/extent-tree-utils.h" +#include "common/root-tree-utils.h" +#include "common/path-utils.h" +#include "common/rbtree-utils.h" #include "mkfs/rootdir.h" static u32 fs_block_size; @@ -68,9 +71,56 @@ static u64 ftw_data_size; struct inode_entry { /* The inode number inside btrfs. */ u64 ino; + struct btrfs_root *root; struct list_head list; }; +/* + * Record all the hard links we found for a specific file inside + * rootdir. + * + * The search is based on (root, st_dev, st_ino). + * The reason for @root as a search index is, for hard links separated by + * subvolume boundaries: + * + * rootdir/ + * |- foobar_hardlink1 + * |- foobar_hardlink2 + * |- subv/ <- Will be created as a subvolume + * |- foobar_hardlink3. + * + * Since all the 3 hard links are inside the same rootdir and the same + * filesystem, on the host fs they are all hard links to the same inode. + * + * But for the btrfs we are building, only hardlink1 and hardlink2 can be + * created as hardlinks. Since we cannot create hardlink across subvolume. + * So we need @root as a search index to handle such case. + */ +struct hardlink_entry { + struct rb_node node; + /* + * The following three members are reported from the stat() of the + * host filesystem. + * + * For st_nlink we cannot trust it unconditionally, as + * some hard links may be out of rootdir. + * If @found_nlink reached @st_nlink, we know we have created all + * the hard links and can remove the entry. + */ + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + + /* The following two are inside the new btrfs. */ + struct btrfs_root *root; + u64 btrfs_ino; + + /* How many hard links we have created. */ + nlink_t found_nlink; +}; + +static struct rb_root hardlink_root = RB_ROOT; + /* * The path towards the rootdir. * @@ -90,10 +140,10 @@ static struct rootdir_path current_path = { .level = 0, }; -/* Track if a hardlink was found and a warning was printed. */ -static bool g_hardlink_warning; -static u64 g_hardlink_count; static struct btrfs_trans_handle *g_trans = NULL; +static struct list_head *g_subvols; +static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID; +static u64 default_subvol_id; static inline struct inode_entry *rootdir_path_last(struct rootdir_path *path) { @@ -114,19 +164,96 @@ static void rootdir_path_pop(struct rootdir_path *path) free(last); } -static int rootdir_path_push(struct rootdir_path *path, u64 ino) +static int rootdir_path_push(struct rootdir_path *path, struct btrfs_root *root, u64 ino) { struct inode_entry *new; new = malloc(sizeof(*new)); if (!new) return -ENOMEM; + new->root = root; new->ino = ino; list_add_tail(&new->list, &path->inode_list); path->level++; return 0; } +static int hardlink_compare_nodes(const struct rb_node *node1, + const struct rb_node *node2) +{ + const struct hardlink_entry *entry1; + const struct hardlink_entry *entry2; + + entry1 = rb_entry(node1, struct hardlink_entry, node); + entry2 = rb_entry(node2, struct hardlink_entry, node); + UASSERT(entry1->root); + UASSERT(entry2->root); + + if (entry1->st_dev < entry2->st_dev) + return -1; + if (entry1->st_dev > entry2->st_dev) + return 1; + if (entry1->st_ino < entry2->st_ino) + return -1; + if (entry1->st_ino > entry2->st_ino) + return 1; + if (entry1->root < entry2->root) + return -1; + if (entry1->root > entry2->root) + return 1; + return 0; +} + +static struct hardlink_entry *find_hard_link(struct btrfs_root *root, + const struct stat *st) +{ + struct rb_node *node; + const struct hardlink_entry tmp = { + .st_dev = st->st_dev, + .st_ino = st->st_ino, + .root = root, + }; + + node = rb_search(&hardlink_root, &tmp, + (rb_compare_keys)hardlink_compare_nodes, NULL); + if (node) + return rb_entry(node, struct hardlink_entry, node); + return NULL; +} + +static int add_hard_link(struct btrfs_root *root, u64 btrfs_ino, + const struct stat *st) +{ + struct hardlink_entry *new; + int ret; + + UASSERT(st->st_nlink > 1); + + new = calloc(1, sizeof(*new)); + if (!new) + return -ENOMEM; + + new->root = root; + new->btrfs_ino = btrfs_ino; + new->found_nlink = 1; + new->st_dev = st->st_dev; + new->st_ino = st->st_ino; + new->st_nlink = st->st_nlink; + ret = rb_insert(&hardlink_root, &new->node, hardlink_compare_nodes); + if (ret) { + free(new); + return -EEXIST; + } + return 0; +} + +static void free_one_hardlink(struct rb_node *node) +{ + struct hardlink_entry *entry = rb_entry(node, struct hardlink_entry, node); + + free(entry); +} + static void stat_to_inode_item(struct btrfs_inode_item *dst, const struct stat *st) { /* @@ -410,39 +537,101 @@ static u8 ftype_to_btrfs_type(mode_t ftype) return BTRFS_FT_UNKNOWN; } +static int ftw_add_subvol(const char *full_path, const struct stat *st, + int typeflag, struct FTW *ftwbuf, + struct rootdir_subvol *subvol) +{ + int ret; + struct btrfs_key key; + struct btrfs_root *new_root; + struct inode_entry *parent; + struct btrfs_inode_item inode_item = { 0 }; + u64 subvol_id, ino; + + subvol_id = next_subvol_id++; + + ret = btrfs_make_subvolume(g_trans, subvol_id, subvol->readonly); + if (ret < 0) { + errno = -ret; + error("failed to create subvolume: %m"); + return ret; + } + + if (subvol->is_default) + default_subvol_id = subvol_id; + + key.objectid = subvol_id; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + + new_root = btrfs_read_fs_root(g_trans->fs_info, &key); + if (IS_ERR(new_root)) { + ret = PTR_ERR(new_root); + errno = -ret; + error("unable to read fs root id %llu: %m", subvol_id); + return ret; + } + + parent = rootdir_path_last(¤t_path); + + ret = btrfs_link_subvolume(g_trans, parent->root, parent->ino, + path_basename(subvol->full_path), + strlen(path_basename(subvol->full_path)), + new_root); + if (ret) { + errno = -ret; + error("unable to link subvolume %s: %m", path_basename(subvol->full_path)); + return ret; + } + + ino = btrfs_root_dirid(&new_root->root_item); + + ret = add_xattr_item(g_trans, new_root, ino, full_path); + if (ret < 0) { + errno = -ret; + error("failed to add xattr item for the top level inode in subvol %llu: %m", + subvol_id); + return ret; + } + stat_to_inode_item(&inode_item, st); + + btrfs_set_stack_inode_nlink(&inode_item, 1); + ret = update_inode_item(g_trans, new_root, &inode_item, ino); + if (ret < 0) { + errno = -ret; + error("failed to update root dir for root %llu: %m", subvol_id); + return ret; + } + + ret = rootdir_path_push(¤t_path, new_root, ino); + if (ret < 0) { + errno = -ret; + error("failed to allocate new entry for subvolume %llu ('%s'): %m", + subvol_id, full_path); + return ret; + } + + return 0; +} + static int ftw_add_inode(const char *full_path, const struct stat *st, int typeflag, struct FTW *ftwbuf) { struct btrfs_fs_info *fs_info = g_trans->fs_info; - struct btrfs_root *root = fs_info->fs_root; + struct btrfs_root *root; struct btrfs_inode_item inode_item = { 0 }; struct inode_entry *parent; + struct rootdir_subvol *rds; + const bool have_hard_links = (!S_ISDIR(st->st_mode) && st->st_nlink > 1); u64 ino; int ret; - /* - * Hard link needs extra detection code, not supported for now, but - * it's not to break anything but splitting the hard links into new - * inodes. And we do not even know if the hard links are inside the - * rootdir. - * - * So here we only need to do extra warning. - * - * On most filesystems st_nlink of a directory is the number of - * subdirs, including "." and "..", so skip directory inodes. - */ - if (unlikely(!S_ISDIR(st->st_mode) && st->st_nlink > 1)) { - if (!g_hardlink_warning) { - warning("'%s' has extra hardlinks, they will be converted into new inodes", - full_path); - g_hardlink_warning = true; - } - g_hardlink_count++; - } - /* The rootdir itself. */ if (unlikely(ftwbuf->level == 0)) { - u64 root_ino = btrfs_root_dirid(&root->root_item); + u64 root_ino; + + root = fs_info->fs_root; + root_ino = btrfs_root_dirid(&root->root_item); UASSERT(S_ISDIR(st->st_mode)); UASSERT(current_path.level == 0); @@ -468,7 +657,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, } /* Push (and initialize) the rootdir directory into the stack. */ - ret = rootdir_path_push(¤t_path, btrfs_root_dirid(&root->root_item)); + ret = rootdir_path_push(¤t_path, root, btrfs_root_dirid(&root->root_item)); if (ret < 0) { errno = -ret; error_msg(ERROR_MSG_MEMORY, "push path for rootdir: %m"); @@ -516,6 +705,57 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, while (current_path.level > ftwbuf->level) rootdir_path_pop(¤t_path); + if (S_ISDIR(st->st_mode)) { + list_for_each_entry(rds, g_subvols, list) { + if (!strcmp(full_path, rds->full_path)) { + ret = ftw_add_subvol(full_path, st, typeflag, + ftwbuf, rds); + + free(rds->dir); + free(rds->full_path); + + list_del(&rds->list); + free(rds); + + return ret; + } + } + } + + parent = rootdir_path_last(¤t_path); + root = parent->root; + + /* Check if there is already a hard link record for this. */ + if (have_hard_links) { + struct hardlink_entry *found; + + found = find_hard_link(root, st); + /* + * Can only add the hard link if it doesn't cross subvolume + * boundary. + */ + if (found && found->root == root) { + ret = btrfs_add_link(g_trans, root, found->btrfs_ino, + parent->ino, full_path + ftwbuf->base, + strlen(full_path) - ftwbuf->base, + ftype_to_btrfs_type(st->st_mode), + NULL, 1, 0); + if (ret < 0) { + errno = -ret; + error( + "failed to add link for hard link ('%s'): %m", full_path); + return ret; + } + found->found_nlink++; + /* We found all hard links for it. Can remove the entry. */ + if (found->found_nlink >= found->st_nlink) { + rb_erase(&found->node, &hardlink_root); + free(found); + } + return 0; + } + } + ret = btrfs_find_free_objectid(g_trans, root, BTRFS_FIRST_FREE_OBJECTID, &ino); if (ret < 0) { @@ -532,7 +772,6 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, return ret; } - parent = rootdir_path_last(¤t_path); ret = btrfs_add_link(g_trans, root, ino, parent->ino, full_path + ftwbuf->base, strlen(full_path) - ftwbuf->base, @@ -543,6 +782,19 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, error("failed to add link for inode %llu ('%s'): %m", ino, full_path); return ret; } + + /* Record this new hard link. */ + if (have_hard_links) { + ret = add_hard_link(root, ino, st); + if (ret < 0) { + errno = -ret; + error("failed to add hard link record for '%s': %m", + full_path); + return ret; + } + ret = 0; + } + /* * btrfs_add_link() has increased the nlink to 1 in the metadata. * Also update the value in case we need to update the inode item @@ -557,7 +809,7 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, return ret; } if (S_ISDIR(st->st_mode)) { - ret = rootdir_path_push(¤t_path, ino); + ret = rootdir_path_push(¤t_path, root, ino); if (ret < 0) { errno = -ret; error("failed to allocate new entry for inode %llu ('%s'): %m", @@ -598,56 +850,82 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, return 0; }; -int btrfs_mkfs_fill_dir(const char *source_dir, struct btrfs_root *root) +static int set_default_subvolume(struct btrfs_trans_handle *trans) +{ + struct btrfs_path path = { 0 }; + struct btrfs_dir_item *di; + struct btrfs_key location; + struct extent_buffer *leaf; + struct btrfs_disk_key disk_key; + u64 features; + + di = btrfs_lookup_dir_item(trans, trans->fs_info->tree_root, &path, + btrfs_super_root_dir(trans->fs_info->super_copy), + "default", 7, 1); + if (IS_ERR_OR_NULL(di)) { + btrfs_release_path(&path); + + if (di) + return PTR_ERR(di); + else + return -ENOENT; + } + + leaf = path.nodes[0]; + + location.objectid = default_subvol_id; + location.type = BTRFS_ROOT_ITEM_KEY; + location.offset = 0; + + btrfs_cpu_key_to_disk(&disk_key, &location); + btrfs_set_dir_item_key(leaf, di, &disk_key); + + btrfs_mark_buffer_dirty(leaf); + + btrfs_release_path(&path); + + features = btrfs_super_incompat_flags(trans->fs_info->super_copy); + features |= BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL; + btrfs_set_super_incompat_flags(trans->fs_info->super_copy, features); + + return 0; +} + +int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, + struct btrfs_root *root, struct list_head *subvols) { int ret; - struct btrfs_trans_handle *trans; struct stat root_st; ret = lstat(source_dir, &root_st); if (ret) { error("unable to lstat %s: %m", source_dir); - ret = -errno; - goto out; - } - - trans = btrfs_start_transaction(root, 1); - if (IS_ERR(trans)) { - ret = PTR_ERR(trans); - errno = -ret; - error_msg(ERROR_MSG_START_TRANS, "%m"); - goto fail; + return -errno; } g_trans = trans; - g_hardlink_warning = false; - g_hardlink_count = 0; + g_subvols = subvols; INIT_LIST_HEAD(¤t_path.inode_list); ret = nftw(source_dir, ftw_add_inode, 32, FTW_PHYS); if (ret) { error("unable to traverse directory %s: %d", source_dir, ret); - goto fail; - } - ret = btrfs_commit_transaction(trans, root); - if (ret) { - errno = -ret; - error_msg(ERROR_MSG_COMMIT_TRANS, "%m"); - goto out; + return ret; } - if (g_hardlink_warning) - warning("%llu hardlinks were detected in %s, all converted to new inodes", - g_hardlink_count, source_dir); - while (current_path.level > 0) rootdir_path_pop(¤t_path); + if (default_subvol_id != 0) { + ret = set_default_subvolume(trans); + if (ret < 0) { + error("error setting default subvolume: %d", ret); + return ret; + } + } + + rb_free_nodes(&hardlink_root, free_one_hardlink); return 0; -fail: - btrfs_abort_transaction(trans, ret); -out: - return ret; } static int ftw_add_entry_size(const char *fpath, const struct stat *st, diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h index 4233431a9a..44817374ed 100644 --- a/mkfs/rootdir.h +++ b/mkfs/rootdir.h @@ -28,7 +28,16 @@ struct btrfs_fs_info; struct btrfs_root; -int btrfs_mkfs_fill_dir(const char *source_dir, struct btrfs_root *root); +struct rootdir_subvol { + struct list_head list; + char *dir; + char *full_path; + bool is_default; + bool readonly; +}; + +int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, + struct btrfs_root *root, struct list_head *subvols); u64 btrfs_mkfs_size_dir(const char *dir_name, u32 sectorsize, u64 min_dev_size, u64 meta_profile, u64 data_profile); int btrfs_mkfs_shrink_fs(struct btrfs_fs_info *fs_info, u64 *new_size_ret, diff --git a/tests/cli-tests/026-subvolume-list-path-filtering/test.sh b/tests/cli-tests/026-subvolume-list-path-filtering/test.sh new file mode 100755 index 0000000000..a65ba91f9b --- /dev/null +++ b/tests/cli-tests/026-subvolume-list-path-filtering/test.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# Test how btrfs subvolume list prints paths, including all of the weird +# accidental behavior. + +source "$TEST_TOP/common" || exit + +check_prereq btrfs +check_prereq mkfs.btrfs + +setup_root_helper +prepare_test_dev + +run_check_mkfs_test_dev +run_check_mount_test_dev + +cd "$TEST_MNT" + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "a" +run_check $SUDO_HELPER mkdir "a/b" +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "a/b/c" +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "a/b/c/d" +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "a/e" + +subvol_list_paths() { + run_check_stdout $SUDO_HELPER "$TOP/btrfs" subvolume list "$@" | sed 's/.*path //' +} + +expect_subvol_list_paths() { + diff -u - <(subvol_list_paths "$@") || _fail "wrong output from btrfs subvolume list $*" +} + +### No options ### + +# Paths are relative to the given subvolume if they are beneath it and relative +# to the root of the filesystem otherwise. +expect_subvol_list_paths . << EOF +a +a/b/c +a/b/c/d +a/e +EOF + +expect_subvol_list_paths a << EOF +a +b/c +b/c/d +e +EOF + +expect_subvol_list_paths a/b/c << EOF +a +a/b/c +d +a/e +EOF + +# If passed a directory, it's treated as the containing subvolume. +expect_subvol_list_paths a/b << EOF +a +b/c +b/c/d +e +EOF + +### -a ### + +# Paths are relative to the root of the filesystem. Subvolumes that are not an +# immediate child of the passed subvolume are prefixed with /. +expect_subvol_list_paths -a . << EOF +a +/a/b/c +/a/b/c/d +/a/e +EOF + +expect_subvol_list_paths -a a << EOF +/a +a/b/c +/a/b/c/d +a/e +EOF + +# If passed a directory, it's treated as the containing subvolume. +expect_subvol_list_paths -a a/b << EOF +/a +a/b/c +/a/b/c/d +a/e +EOF + +### -o ### + +# Only immediate children of the passed subvolume are printed, and they are +# printed relative to the root of the filesystem. +expect_subvol_list_paths -o . << EOF +a +EOF + +expect_subvol_list_paths -o a << EOF +a/b/c +a/e +EOF + +# If passed a directory, it's treated as the containing subvolume. +expect_subvol_list_paths -o a/b << EOF +a/b/c +a/e +EOF + +expect_subvol_list_paths -o a/b/c << EOF +a/b/c/d +EOF + +expect_subvol_list_paths -o a/b/c/d << EOF +EOF + +### -A ### + +# Paths are always relative to the root of the filesystem. +for path in . a/b a/b/c; do + expect_subvol_list_paths -A "$path" << EOF + +a +a/b/c +a/b/c/d +a/e +EOF +done + +### -O ### + +# Paths are relative to the given path. +expect_subvol_list_paths -O . << EOF + +a +a/b/c +a/b/c/d +a/e +EOF + +expect_subvol_list_paths -O a << EOF + +b/c +b/c/d +e +EOF + +expect_subvol_list_paths -O a/e << EOF + +EOF + +run_mustfail "btrfs subvol list -O allowed non-subvolume" \ + $SUDO_HELPER "$TOP/btrfs" subvolume list -O a/b + +cd .. +run_check_umount_test_dev diff --git a/tests/convert-tests/027-large-symbol-link/test.sh b/tests/convert-tests/027-large-symbol-link/test.sh new file mode 100755 index 0000000000..2a001424df --- /dev/null +++ b/tests/convert-tests/027-large-symbol-link/test.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Make sure btrfs-convert can handle a symbol link which is 4095 bytes large + +source "$TEST_TOP/common" || exit +source "$TEST_TOP/common.convert" || exit + +setup_root_helper +prepare_test_dev 1G +check_global_prereq mkfs.ext4 + +# This is at the symbolic link size limit (PATH_MAX includes the terminating NUL). +link_target=$(printf "%0.sb" {1..4095}) + +convert_test_prep_fs ext4 mke2fs -t ext4 -b 4096 +run_check $SUDO_HELPER ln -s "$link_target" "$TEST_MNT/symbol_link" +run_check_umount_test_dev + +# For unpatched btrfs-convert, it will always append one byte to the +# link target, causing above 4095 target to be 4096, exactly one sector, +# resulting a regular file extent. +convert_test_do_convert + +run_check_mount_test_dev +# If the unpatched btrfs-convert created a regular extent, and the kernel is +# newer enough, such readlink will be rejected by kernel. +run_check $SUDO_HELPER readlink "$TEST_MNT/symbol_link" +run_check_umount_test_dev diff --git a/tests/fsck-tests/063-log-missing-csum/default.img.xz b/tests/fsck-tests/063-log-missing-csum/default.img.xz new file mode 100644 index 0000000000..c9b4f420ac Binary files /dev/null and b/tests/fsck-tests/063-log-missing-csum/default.img.xz differ diff --git a/tests/fsck-tests/063-log-missing-csum/test.sh b/tests/fsck-tests/063-log-missing-csum/test.sh new file mode 100755 index 0000000000..40a4850827 --- /dev/null +++ b/tests/fsck-tests/063-log-missing-csum/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Verify that check can detect missing log csum items. + +source "$TEST_TOP/common" || exit + +check_prereq btrfs + +check_image() { + run_mustfail "missing log csum items not detected" \ + "$TOP/btrfs" check "$1" +} + +check_all_images diff --git a/tests/fsck-tests/064-deprecated-inode-cache/4k_nodesize.img.xz b/tests/fsck-tests/064-deprecated-inode-cache/4k_nodesize.img.xz new file mode 100644 index 0000000000..da8314bbe3 Binary files /dev/null and b/tests/fsck-tests/064-deprecated-inode-cache/4k_nodesize.img.xz differ diff --git a/tests/fsck-tests/064-deprecated-inode-cache/test.sh b/tests/fsck-tests/064-deprecated-inode-cache/test.sh new file mode 100755 index 0000000000..ca3bfa7182 --- /dev/null +++ b/tests/fsck-tests/064-deprecated-inode-cache/test.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Verify that check can report deprecated inode cache as an error + +source "$TEST_TOP/common" || exit + +check_prereq btrfs + +check_image() { + run_mustfail "deprecated inode cache not reported as an error" \ + "$TOP/btrfs" check "$1" +} + +check_all_images diff --git a/tests/mkfs-tests/036-rootdir-subvol/test.sh b/tests/mkfs-tests/036-rootdir-subvol/test.sh new file mode 100755 index 0000000000..e4ae604ed0 --- /dev/null +++ b/tests/mkfs-tests/036-rootdir-subvol/test.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# Basic test for mkfs.btrfs --subvol option + +source "$TEST_TOP/common" || exit + +check_prereq mkfs.btrfs +check_prereq btrfs + +setup_root_helper +prepare_test_dev + +tmp=$(_mktemp_dir mkfs-rootdir) + +basic() +{ + run_check touch "$tmp/foo" + run_check mkdir "$tmp/dir" + run_check mkdir "$tmp/dir/subvol" + run_check touch "$tmp/dir/subvol/bar" + + run_check_mkfs_test_dev --rootdir "$tmp" --subvol dir/subvol + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + + run_check_mount_test_dev + run_check_stdout $SUDO_HELPER "$TOP/btrfs" subvolume list "$TEST_MNT" | \ + cut -d\ -f9 > "$tmp/output" + run_check_umount_test_dev + + result=$(cat "$tmp/output") + + if [ "$result" != "dir/subvol" ]; then + _fail "dir/subvol not in subvolume list" + fi + rm -rf -- "$tmp/foo" "$tmp/dir" +} + +basic_hardlinks() +{ + run_check touch "$tmp/hl1" + run_check ln "$tmp/hl1" "$tmp/hl2" + run_check mkdir "$tmp/dir" + run_check ln "$tmp/hl1" "$tmp/dir/hl3" + + run_check_mkfs_test_dev --rootdir "$tmp" + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + + run_check_mount_test_dev + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/hl1") + + if [ "$nr_hardlink" -ne 3 ]; then + _fail "hard link number incorrect, has ${nr_hardlink} expect 3" + fi + run_check_umount_test_dev + rm -rf -- "$tmp/hl1" "$tmp/hl2" "$tmp/dir" +} + +split_by_subvolume_hardlinks() +{ + run_check touch "$tmp/hl1" + run_check ln "$tmp/hl1" "$tmp/hl2" + run_check mkdir "$tmp/subv" + run_check ln "$tmp/hl1" "$tmp/subv/hl3" + + run_check_mkfs_test_dev --rootdir "$tmp" --subvol subv + run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV" + + run_check_mount_test_dev + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/hl1") + + if [ "$nr_hardlink" -ne 2 ]; then + _fail "hard link number incorrect for hl1, has ${nr_hardlink} expect 2" + fi + + nr_hardlink=$(run_check_stdout $SUDO_HELPER stat -c "%h" "$TEST_MNT/subv/hl3") + if [ "$nr_hardlink" -ne 1 ]; then + _fail "hard link number incorrect for subv/hl3, has ${nr_hardlink} expect 1" + fi + run_check_umount_test_dev + rm -rf -- "$tmp/hl1" "$tmp/hl2" "$tmp/dir" +} + +basic +basic_hardlinks +split_by_subvolume_hardlinks +rm -rf -- "$tmp" diff --git a/tune/main.c b/tune/main.c index b0509cf131..d246b970e8 100644 --- a/tune/main.c +++ b/tune/main.c @@ -185,7 +185,7 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[]) { struct btrfs_root *root; struct btrfs_fs_info *fs_info; - unsigned ctree_flags = OPEN_CTREE_WRITES; + unsigned ctree_flags = OPEN_CTREE_WRITES | OPEN_CTREE_EXCLUSIVE; int seeding_flag = 0; u64 seeding_value = 0; int random_fsid = 0;