summaryrefslogtreecommitdiff
path: root/libbtrfsutil
diff options
context:
space:
mode:
authorDimitri John Ledkov <xnox@ubuntu.com>2018-05-08 14:17:29 -0700
committerDimitri John Ledkov <xnox@ubuntu.com>2018-05-08 14:17:29 -0700
commitd00c9550da1801a0eaff5cedf4312e24691b31ea (patch)
tree3881ca1764ef792259e1b70f12c884a3ac0c0715 /libbtrfsutil
parentdab6d2181f1f194ec3a76d900cf2c6533379cbea (diff)
New upstream release.
Diffstat (limited to 'libbtrfsutil')
-rw-r--r--libbtrfsutil/COPYING674
-rw-r--r--libbtrfsutil/COPYING.LESSER165
-rw-r--r--libbtrfsutil/README.md38
-rw-r--r--libbtrfsutil/btrfs.h840
-rw-r--r--libbtrfsutil/btrfs_tree.h970
-rw-r--r--libbtrfsutil/btrfsutil.h650
-rw-r--r--libbtrfsutil/btrfsutil_internal.h42
-rw-r--r--libbtrfsutil/errors.c55
-rw-r--r--libbtrfsutil/filesystem.c102
-rw-r--r--libbtrfsutil/python/.gitignore7
-rw-r--r--libbtrfsutil/python/btrfsutilpy.h84
-rw-r--r--libbtrfsutil/python/error.c202
-rw-r--r--libbtrfsutil/python/filesystem.c94
-rw-r--r--libbtrfsutil/python/module.c321
-rw-r--r--libbtrfsutil/python/qgroup.c141
-rwxr-xr-xlibbtrfsutil/python/setup.py111
-rw-r--r--libbtrfsutil/python/subvolume.c667
-rw-r--r--libbtrfsutil/python/tests/__init__.py70
-rw-r--r--libbtrfsutil/python/tests/test_filesystem.py73
-rw-r--r--libbtrfsutil/python/tests/test_qgroup.py57
-rw-r--r--libbtrfsutil/python/tests/test_subvolume.py385
-rw-r--r--libbtrfsutil/qgroup.c82
-rw-r--r--libbtrfsutil/stubs.c35
-rw-r--r--libbtrfsutil/stubs.h22
-rw-r--r--libbtrfsutil/subvolume.c1382
25 files changed, 7269 insertions, 0 deletions
diff --git a/libbtrfsutil/COPYING b/libbtrfsutil/COPYING
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/libbtrfsutil/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/libbtrfsutil/COPYING.LESSER b/libbtrfsutil/COPYING.LESSER
new file mode 100644
index 00000000..0a041280
--- /dev/null
+++ b/libbtrfsutil/COPYING.LESSER
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/libbtrfsutil/README.md b/libbtrfsutil/README.md
new file mode 100644
index 00000000..0c8eba44
--- /dev/null
+++ b/libbtrfsutil/README.md
@@ -0,0 +1,38 @@
+libbtrfsutil
+============
+
+libbtrfsutil is a library for managing Btrfs filesystems. It is licensed under
+the LGPL. libbtrfsutil provides interfaces for a subset of the operations
+offered by the `btrfs` command line utility. It also includes official Python
+bindings (Python 3 only).
+
+Development
+-----------
+
+The [development process for btrfs-progs](../README.md#development) applies.
+
+libbtrfsutil only includes operations that are done through the filesystem and
+ioctl interface, not operations that modify the filesystem directly (e.g., mkfs
+or fsck). This is by design but also a legal necessity, as the filesystem
+implementation is GPL but libbtrfsutil is LGPL. That is also why the
+libbtrfsutil code is a reimplementation of the btrfs-progs code rather than a
+refactoring. Be wary of this when porting functionality.
+
+libbtrfsutil is semantically versioned separately from btrfs-progs. It is the
+maintainers' responsibility to bump the version as needed (at most once per
+release of btrfs-progs).
+
+A few guidelines:
+
+* All interfaces must be documented in `btrfsutil.h` using the kernel-doc style
+* Error codes should be specific about what _exactly_ failed
+* Functions should have a path and an fd variant whenever possible
+* Spell out terms in function names, etc. rather than abbreviating whenever
+ possible
+* Don't require the Btrfs UAPI headers for any interfaces (e.g., instead of
+ directly exposing a type from `linux/btrfs_tree.h`, abstract it away in a
+ type specific to `libbtrfsutil`)
+* Preserve API and ABI compatability at all times (i.e., we don't want to bump
+ the library major version if we don't have to)
+* Include Python bindings for all interfaces
+* Write tests for all interfaces
diff --git a/libbtrfsutil/btrfs.h b/libbtrfsutil/btrfs.h
new file mode 100644
index 00000000..c293f6bf
--- /dev/null
+++ b/libbtrfsutil/btrfs.h
@@ -0,0 +1,840 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2007 Oracle. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#ifndef _LINUX_BTRFS_H
+#define _LINUX_BTRFS_H
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_VOL_NAME_MAX 255
+#define BTRFS_LABEL_SIZE 256
+
+/* this should be 4k */
+#define BTRFS_PATH_NAME_MAX 4087
+struct btrfs_ioctl_vol_args {
+ __s64 fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+#define BTRFS_DEVICE_PATH_NAME_MAX 1024
+
+#define BTRFS_DEVICE_SPEC_BY_ID (1ULL << 3)
+
+#define BTRFS_VOL_ARG_V2_FLAGS_SUPPORTED \
+ (BTRFS_SUBVOL_CREATE_ASYNC | \
+ BTRFS_SUBVOL_RDONLY | \
+ BTRFS_SUBVOL_QGROUP_INHERIT | \
+ BTRFS_DEVICE_SPEC_BY_ID)
+
+#define BTRFS_FSID_SIZE 16
+#define BTRFS_UUID_SIZE 16
+#define BTRFS_UUID_UNPARSED_SIZE 37
+
+/*
+ * flags definition for qgroup limits
+ *
+ * Used by:
+ * struct btrfs_qgroup_limit.flags
+ * struct btrfs_qgroup_limit_item.flags
+ */
+#define BTRFS_QGROUP_LIMIT_MAX_RFER (1ULL << 0)
+#define BTRFS_QGROUP_LIMIT_MAX_EXCL (1ULL << 1)
+#define BTRFS_QGROUP_LIMIT_RSV_RFER (1ULL << 2)
+#define BTRFS_QGROUP_LIMIT_RSV_EXCL (1ULL << 3)
+#define BTRFS_QGROUP_LIMIT_RFER_CMPR (1ULL << 4)
+#define BTRFS_QGROUP_LIMIT_EXCL_CMPR (1ULL << 5)
+
+struct btrfs_qgroup_limit {
+ __u64 flags;
+ __u64 max_rfer;
+ __u64 max_excl;
+ __u64 rsv_rfer;
+ __u64 rsv_excl;
+};
+
+/*
+ * flags definition for qgroup inheritance
+ *
+ * Used by:
+ * struct btrfs_qgroup_inherit.flags
+ */
+#define BTRFS_QGROUP_INHERIT_SET_LIMITS (1ULL << 0)
+
+struct btrfs_qgroup_inherit {
+ __u64 flags;
+ __u64 num_qgroups;
+ __u64 num_ref_copies;
+ __u64 num_excl_copies;
+ struct btrfs_qgroup_limit lim;
+ __u64 qgroups[0];
+};
+
+struct btrfs_ioctl_qgroup_limit_args {
+ __u64 qgroupid;
+ struct btrfs_qgroup_limit lim;
+};
+
+/*
+ * flags for subvolumes
+ *
+ * Used by:
+ * struct btrfs_ioctl_vol_args_v2.flags
+ *
+ * BTRFS_SUBVOL_RDONLY is also provided/consumed by the following ioctls:
+ * - BTRFS_IOC_SUBVOL_GETFLAGS
+ * - BTRFS_IOC_SUBVOL_SETFLAGS
+ */
+#define BTRFS_SUBVOL_CREATE_ASYNC (1ULL << 0)
+#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
+#define BTRFS_SUBVOL_QGROUP_INHERIT (1ULL << 2)
+
+#define BTRFS_SUBVOL_NAME_MAX 4039
+struct btrfs_ioctl_vol_args_v2 {
+ __s64 fd;
+ __u64 transid;
+ __u64 flags;
+ union {
+ struct {
+ __u64 size;
+ struct btrfs_qgroup_inherit *qgroup_inherit;
+ };
+ __u64 unused[4];
+ };
+ union {
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+ __u64 devid;
+ };
+};
+
+/*
+ * structure to report errors and progress to userspace, either as a
+ * result of a finished scrub, a canceled scrub or a progress inquiry
+ */
+struct btrfs_scrub_progress {
+ __u64 data_extents_scrubbed; /* # of data extents scrubbed */
+ __u64 tree_extents_scrubbed; /* # of tree extents scrubbed */
+ __u64 data_bytes_scrubbed; /* # of data bytes scrubbed */
+ __u64 tree_bytes_scrubbed; /* # of tree bytes scrubbed */
+ __u64 read_errors; /* # of read errors encountered (EIO) */
+ __u64 csum_errors; /* # of failed csum checks */
+ __u64 verify_errors; /* # of occurences, where the metadata
+ * of a tree block did not match the
+ * expected values, like generation or
+ * logical */
+ __u64 no_csum; /* # of 4k data block for which no csum
+ * is present, probably the result of
+ * data written with nodatasum */
+ __u64 csum_discards; /* # of csum for which no data was found
+ * in the extent tree. */
+ __u64 super_errors; /* # of bad super blocks encountered */
+ __u64 malloc_errors; /* # of internal kmalloc errors. These
+ * will likely cause an incomplete
+ * scrub */
+ __u64 uncorrectable_errors; /* # of errors where either no intact
+ * copy was found or the writeback
+ * failed */
+ __u64 corrected_errors; /* # of errors corrected */
+ __u64 last_physical; /* last physical address scrubbed. In
+ * case a scrub was aborted, this can
+ * be used to restart the scrub */
+ __u64 unverified_errors; /* # of occurences where a read for a
+ * full (64k) bio failed, but the re-
+ * check succeeded for each 4k piece.
+ * Intermittent error. */
+};
+
+#define BTRFS_SCRUB_READONLY 1
+struct btrfs_ioctl_scrub_args {
+ __u64 devid; /* in */
+ __u64 start; /* in */
+ __u64 end; /* in */
+ __u64 flags; /* in */
+ struct btrfs_scrub_progress progress; /* out */
+ /* pad to 1k */
+ __u64 unused[(1024-32-sizeof(struct btrfs_scrub_progress))/8];
+};
+
+#define BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS 0
+#define BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID 1
+struct btrfs_ioctl_dev_replace_start_params {
+ __u64 srcdevid; /* in, if 0, use srcdev_name instead */
+ __u64 cont_reading_from_srcdev_mode; /* in, see #define
+ * above */
+ __u8 srcdev_name[BTRFS_DEVICE_PATH_NAME_MAX + 1]; /* in */
+ __u8 tgtdev_name[BTRFS_DEVICE_PATH_NAME_MAX + 1]; /* in */
+};
+
+#define BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED 0
+#define BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED 1
+#define BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED 2
+#define BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED 3
+#define BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED 4
+struct btrfs_ioctl_dev_replace_status_params {
+ __u64 replace_state; /* out, see #define above */
+ __u64 progress_1000; /* out, 0 <= x <= 1000 */
+ __u64 time_started; /* out, seconds since 1-Jan-1970 */
+ __u64 time_stopped; /* out, seconds since 1-Jan-1970 */
+ __u64 num_write_errors; /* out */
+ __u64 num_uncorrectable_read_errors; /* out */
+};
+
+#define BTRFS_IOCTL_DEV_REPLACE_CMD_START 0
+#define BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS 1
+#define BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL 2
+#define BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR 0
+#define BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED 1
+#define BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED 2
+#define BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS 3
+struct btrfs_ioctl_dev_replace_args {
+ __u64 cmd; /* in */
+ __u64 result; /* out */
+
+ union {
+ struct btrfs_ioctl_dev_replace_start_params start;
+ struct btrfs_ioctl_dev_replace_status_params status;
+ }; /* in/out */
+
+ __u64 spare[64];
+};
+
+struct btrfs_ioctl_dev_info_args {
+ __u64 devid; /* in/out */
+ __u8 uuid[BTRFS_UUID_SIZE]; /* in/out */
+ __u64 bytes_used; /* out */
+ __u64 total_bytes; /* out */
+ __u64 unused[379]; /* pad to 4k */
+ __u8 path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */
+};
+
+struct btrfs_ioctl_fs_info_args {
+ __u64 max_id; /* out */
+ __u64 num_devices; /* out */
+ __u8 fsid[BTRFS_FSID_SIZE]; /* out */
+ __u32 nodesize; /* out */
+ __u32 sectorsize; /* out */
+ __u32 clone_alignment; /* out */
+ __u32 reserved32;
+ __u64 reserved[122]; /* pad to 1k */
+};
+
+/*
+ * feature flags
+ *
+ * Used by:
+ * struct btrfs_ioctl_feature_flags
+ */
+#define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE (1ULL << 0)
+/*
+ * Older kernels (< 4.9) on big-endian systems produced broken free space tree
+ * bitmaps, and btrfs-progs also used to corrupt the free space tree (versions
+ * < 4.7.3). If this bit is clear, then the free space tree cannot be trusted.
+ * btrfs-progs can also intentionally clear this bit to ask the kernel to
+ * rebuild the free space tree, however this might not work on older kernels
+ * that do not know about this bit. If not sure, clear the cache manually on
+ * first mount when booting older kernel versions.
+ */
+#define BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID (1ULL << 1)
+
+#define BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF (1ULL << 0)
+#define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL (1ULL << 1)
+#define BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS (1ULL << 2)
+#define BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO (1ULL << 3)
+#define BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD (1ULL << 4)
+
+/*
+ * older kernels tried to do bigger metadata blocks, but the
+ * code was pretty buggy. Lets not let them try anymore.
+ */
+#define BTRFS_FEATURE_INCOMPAT_BIG_METADATA (1ULL << 5)
+
+#define BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF (1ULL << 6)
+#define BTRFS_FEATURE_INCOMPAT_RAID56 (1ULL << 7)
+#define BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA (1ULL << 8)
+#define BTRFS_FEATURE_INCOMPAT_NO_HOLES (1ULL << 9)
+
+struct btrfs_ioctl_feature_flags {
+ __u64 compat_flags;
+ __u64 compat_ro_flags;
+ __u64 incompat_flags;
+};
+
+/* balance control ioctl modes */
+#define BTRFS_BALANCE_CTL_PAUSE 1
+#define BTRFS_BALANCE_CTL_CANCEL 2
+
+/*
+ * this is packed, because it should be exactly the same as its disk
+ * byte order counterpart (struct btrfs_disk_balance_args)
+ */
+struct btrfs_balance_args {
+ __u64 profiles;
+ union {
+ __u64 usage;
+ struct {
+ __u32 usage_min;
+ __u32 usage_max;
+ };
+ };
+ __u64 devid;
+ __u64 pstart;
+ __u64 pend;
+ __u64 vstart;
+ __u64 vend;
+
+ __u64 target;
+
+ __u64 flags;
+
+ /*
+ * BTRFS_BALANCE_ARGS_LIMIT with value 'limit'
+ * BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum
+ * and maximum
+ */
+ union {
+ __u64 limit; /* limit number of processed chunks */
+ struct {
+ __u32 limit_min;
+ __u32 limit_max;
+ };
+ };
+
+ /*
+ * Process chunks that cross stripes_min..stripes_max devices,
+ * BTRFS_BALANCE_ARGS_STRIPES_RANGE
+ */
+ __u32 stripes_min;
+ __u32 stripes_max;
+
+ __u64 unused[6];
+} __attribute__ ((__packed__));
+
+/* report balance progress to userspace */
+struct btrfs_balance_progress {
+ __u64 expected; /* estimated # of chunks that will be
+ * relocated to fulfill the request */
+ __u64 considered; /* # of chunks we have considered so far */
+ __u64 completed; /* # of chunks relocated so far */
+};
+
+/*
+ * flags definition for balance
+ *
+ * Restriper's general type filter
+ *
+ * Used by:
+ * btrfs_ioctl_balance_args.flags
+ * btrfs_balance_control.flags (internal)
+ */
+#define BTRFS_BALANCE_DATA (1ULL << 0)
+#define BTRFS_BALANCE_SYSTEM (1ULL << 1)
+#define BTRFS_BALANCE_METADATA (1ULL << 2)
+
+#define BTRFS_BALANCE_TYPE_MASK (BTRFS_BALANCE_DATA | \
+ BTRFS_BALANCE_SYSTEM | \
+ BTRFS_BALANCE_METADATA)
+
+#define BTRFS_BALANCE_FORCE (1ULL << 3)
+#define BTRFS_BALANCE_RESUME (1ULL << 4)
+
+/*
+ * flags definitions for per-type balance args
+ *
+ * Balance filters
+ *
+ * Used by:
+ * struct btrfs_balance_args
+ */
+#define BTRFS_BALANCE_ARGS_PROFILES (1ULL << 0)
+#define BTRFS_BALANCE_ARGS_USAGE (1ULL << 1)
+#define BTRFS_BALANCE_ARGS_DEVID (1ULL << 2)
+#define BTRFS_BALANCE_ARGS_DRANGE (1ULL << 3)
+#define BTRFS_BALANCE_ARGS_VRANGE (1ULL << 4)
+#define BTRFS_BALANCE_ARGS_LIMIT (1ULL << 5)
+#define BTRFS_BALANCE_ARGS_LIMIT_RANGE (1ULL << 6)
+#define BTRFS_BALANCE_ARGS_STRIPES_RANGE (1ULL << 7)
+#define BTRFS_BALANCE_ARGS_USAGE_RANGE (1ULL << 10)
+
+#define BTRFS_BALANCE_ARGS_MASK \
+ (BTRFS_BALANCE_ARGS_PROFILES | \
+ BTRFS_BALANCE_ARGS_USAGE | \
+ BTRFS_BALANCE_ARGS_DEVID | \
+ BTRFS_BALANCE_ARGS_DRANGE | \
+ BTRFS_BALANCE_ARGS_VRANGE | \
+ BTRFS_BALANCE_ARGS_LIMIT | \
+ BTRFS_BALANCE_ARGS_LIMIT_RANGE | \
+ BTRFS_BALANCE_ARGS_STRIPES_RANGE | \
+ BTRFS_BALANCE_ARGS_USAGE_RANGE)
+
+/*
+ * Profile changing flags. When SOFT is set we won't relocate chunk if
+ * it already has the target profile (even though it may be
+ * half-filled).
+ */
+#define BTRFS_BALANCE_ARGS_CONVERT (1ULL << 8)
+#define BTRFS_BALANCE_ARGS_SOFT (1ULL << 9)
+
+
+/*
+ * flags definition for balance state
+ *
+ * Used by:
+ * struct btrfs_ioctl_balance_args.state
+ */
+#define BTRFS_BALANCE_STATE_RUNNING (1ULL << 0)
+#define BTRFS_BALANCE_STATE_PAUSE_REQ (1ULL << 1)
+#define BTRFS_BALANCE_STATE_CANCEL_REQ (1ULL << 2)
+
+struct btrfs_ioctl_balance_args {
+ __u64 flags; /* in/out */
+ __u64 state; /* out */
+
+ struct btrfs_balance_args data; /* in/out */
+ struct btrfs_balance_args meta; /* in/out */
+ struct btrfs_balance_args sys; /* in/out */
+
+ struct btrfs_balance_progress stat; /* out */
+
+ __u64 unused[72]; /* pad to 1k */
+};
+
+#define BTRFS_INO_LOOKUP_PATH_MAX 4080
+struct btrfs_ioctl_ino_lookup_args {
+ __u64 treeid;
+ __u64 objectid;
+ char name[BTRFS_INO_LOOKUP_PATH_MAX];
+};
+
+/* Search criteria for the btrfs SEARCH ioctl family. */
+struct btrfs_ioctl_search_key {
+ /*
+ * The tree we're searching in. 1 is the tree of tree roots, 2 is the
+ * extent tree, etc...
+ *
+ * A special tree_id value of 0 will cause a search in the subvolume
+ * tree that the inode which is passed to the ioctl is part of.
+ */
+ __u64 tree_id; /* in */
+
+ /*
+ * When doing a tree search, we're actually taking a slice from a
+ * linear search space of 136-bit keys.
+ *
+ * A full 136-bit tree key is composed as:
+ * (objectid << 72) + (type << 64) + offset
+ *
+ * The individual min and max values for objectid, type and offset
+ * define the min_key and max_key values for the search range. All
+ * metadata items with a key in the interval [min_key, max_key] will be
+ * returned.
+ *
+ * Additionally, we can filter the items returned on transaction id of
+ * the metadata block they're stored in by specifying a transid range.
+ * Be aware that this transaction id only denotes when the metadata
+ * page that currently contains the item got written the last time as
+ * result of a COW operation. The number does not have any meaning
+ * related to the transaction in which an individual item that is being
+ * returned was created or changed.
+ */
+ __u64 min_objectid; /* in */
+ __u64 max_objectid; /* in */
+ __u64 min_offset; /* in */
+ __u64 max_offset; /* in */
+ __u64 min_transid; /* in */
+ __u64 max_transid; /* in */
+ __u32 min_type; /* in */
+ __u32 max_type; /* in */
+
+ /*
+ * input: The maximum amount of results desired.
+ * output: The actual amount of items returned, restricted by any of:
+ * - reaching the upper bound of the search range
+ * - reaching the input nr_items amount of items
+ * - completely filling the supplied memory buffer
+ */
+ __u32 nr_items; /* in/out */
+
+ /* align to 64 bits */
+ __u32 unused;
+
+ /* some extra for later */
+ __u64 unused1;
+ __u64 unused2;
+ __u64 unused3;
+ __u64 unused4;
+};
+
+struct btrfs_ioctl_search_header {
+ __u64 transid;
+ __u64 objectid;
+ __u64 offset;
+ __u32 type;
+ __u32 len;
+};
+
+#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key))
+/*
+ * the buf is an array of search headers where
+ * each header is followed by the actual item
+ * the type field is expanded to 32 bits for alignment
+ */
+struct btrfs_ioctl_search_args {
+ struct btrfs_ioctl_search_key key;
+ char buf[BTRFS_SEARCH_ARGS_BUFSIZE];
+};
+
+struct btrfs_ioctl_search_args_v2 {
+ struct btrfs_ioctl_search_key key; /* in/out - search parameters */
+ __u64 buf_size; /* in - size of buffer
+ * out - on EOVERFLOW: needed size
+ * to store item */
+ __u64 buf[0]; /* out - found items */
+};
+
+struct btrfs_ioctl_clone_range_args {
+ __s64 src_fd;
+ __u64 src_offset, src_length;
+ __u64 dest_offset;
+};
+
+/*
+ * flags definition for the defrag range ioctl
+ *
+ * Used by:
+ * struct btrfs_ioctl_defrag_range_args.flags
+ */
+#define BTRFS_DEFRAG_RANGE_COMPRESS 1
+#define BTRFS_DEFRAG_RANGE_START_IO 2
+struct btrfs_ioctl_defrag_range_args {
+ /* start of the defrag operation */
+ __u64 start;
+
+ /* number of bytes to defrag, use (u64)-1 to say all */
+ __u64 len;
+
+ /*
+ * flags for the operation, which can include turning
+ * on compression for this one defrag
+ */
+ __u64 flags;
+
+ /*
+ * any extent bigger than this will be considered
+ * already defragged. Use 0 to take the kernel default
+ * Use 1 to say every single extent must be rewritten
+ */
+ __u32 extent_thresh;
+
+ /*
+ * which compression method to use if turning on compression
+ * for this defrag operation. If unspecified, zlib will
+ * be used
+ */
+ __u32 compress_type;
+
+ /* spare for later */
+ __u32 unused[4];
+};
+
+
+#define BTRFS_SAME_DATA_DIFFERS 1
+/* For extent-same ioctl */
+struct btrfs_ioctl_same_extent_info {
+ __s64 fd; /* in - destination file */
+ __u64 logical_offset; /* in - start of extent in destination */
+ __u64 bytes_deduped; /* out - total # of bytes we were able
+ * to dedupe from this file */
+ /* status of this dedupe operation:
+ * 0 if dedup succeeds
+ * < 0 for error
+ * == BTRFS_SAME_DATA_DIFFERS if data differs
+ */
+ __s32 status; /* out - see above description */
+ __u32 reserved;
+};
+
+struct btrfs_ioctl_same_args {
+ __u64 logical_offset; /* in - start of extent in source */
+ __u64 length; /* in - length of extent */
+ __u16 dest_count; /* in - total elements in info array */
+ __u16 reserved1;
+ __u32 reserved2;
+ struct btrfs_ioctl_same_extent_info info[0];
+};
+
+struct btrfs_ioctl_space_info {
+ __u64 flags;
+ __u64 total_bytes;
+ __u64 used_bytes;
+};
+
+struct btrfs_ioctl_space_args {
+ __u64 space_slots;
+ __u64 total_spaces;
+ struct btrfs_ioctl_space_info spaces[0];
+};
+
+struct btrfs_data_container {
+ __u32 bytes_left; /* out -- bytes not needed to deliver output */
+ __u32 bytes_missing; /* out -- additional bytes needed for result */
+ __u32 elem_cnt; /* out */
+ __u32 elem_missed; /* out */
+ __u64 val[0]; /* out */
+};
+
+struct btrfs_ioctl_ino_path_args {
+ __u64 inum; /* in */
+ __u64 size; /* in */
+ __u64 reserved[4];
+ /* struct btrfs_data_container *fspath; out */
+ __u64 fspath; /* out */
+};
+
+struct btrfs_ioctl_logical_ino_args {
+ __u64 logical; /* in */
+ __u64 size; /* in */
+ __u64 reserved[4];
+ /* struct btrfs_data_container *inodes; out */
+ __u64 inodes;
+};
+
+enum btrfs_dev_stat_values {
+ /* disk I/O failure stats */
+ BTRFS_DEV_STAT_WRITE_ERRS, /* EIO or EREMOTEIO from lower layers */
+ BTRFS_DEV_STAT_READ_ERRS, /* EIO or EREMOTEIO from lower layers */
+ BTRFS_DEV_STAT_FLUSH_ERRS, /* EIO or EREMOTEIO from lower layers */
+
+ /* stats for indirect indications for I/O failures */
+ BTRFS_DEV_STAT_CORRUPTION_ERRS, /* checksum error, bytenr error or
+ * contents is illegal: this is an
+ * indication that the block was damaged
+ * during read or write, or written to
+ * wrong location or read from wrong
+ * location */
+ BTRFS_DEV_STAT_GENERATION_ERRS, /* an indication that blocks have not
+ * been written */
+
+ BTRFS_DEV_STAT_VALUES_MAX
+};
+
+/* Reset statistics after reading; needs SYS_ADMIN capability */
+#define BTRFS_DEV_STATS_RESET (1ULL << 0)
+
+struct btrfs_ioctl_get_dev_stats {
+ __u64 devid; /* in */
+ __u64 nr_items; /* in/out */
+ __u64 flags; /* in/out */
+
+ /* out values: */
+ __u64 values[BTRFS_DEV_STAT_VALUES_MAX];
+
+ __u64 unused[128 - 2 - BTRFS_DEV_STAT_VALUES_MAX]; /* pad to 1k */
+};
+
+#define BTRFS_QUOTA_CTL_ENABLE 1
+#define BTRFS_QUOTA_CTL_DISABLE 2
+#define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3
+struct btrfs_ioctl_quota_ctl_args {
+ __u64 cmd;
+ __u64 status;
+};
+
+struct btrfs_ioctl_quota_rescan_args {
+ __u64 flags;
+ __u64 progress;
+ __u64 reserved[6];
+};
+
+struct btrfs_ioctl_qgroup_assign_args {
+ __u64 assign;
+ __u64 src;
+ __u64 dst;
+};
+
+struct btrfs_ioctl_qgroup_create_args {
+ __u64 create;
+ __u64 qgroupid;
+};
+struct btrfs_ioctl_timespec {
+ __u64 sec;
+ __u32 nsec;
+};
+
+struct btrfs_ioctl_received_subvol_args {
+ char uuid[BTRFS_UUID_SIZE]; /* in */
+ __u64 stransid; /* in */
+ __u64 rtransid; /* out */
+ struct btrfs_ioctl_timespec stime; /* in */
+ struct btrfs_ioctl_timespec rtime; /* out */
+ __u64 flags; /* in */
+ __u64 reserved[16]; /* in */
+};
+
+/*
+ * Caller doesn't want file data in the send stream, even if the
+ * search of clone sources doesn't find an extent. UPDATE_EXTENT
+ * commands will be sent instead of WRITE commands.
+ */
+#define BTRFS_SEND_FLAG_NO_FILE_DATA 0x1
+
+/*
+ * Do not add the leading stream header. Used when multiple snapshots
+ * are sent back to back.
+ */
+#define BTRFS_SEND_FLAG_OMIT_STREAM_HEADER 0x2
+
+/*
+ * Omit the command at the end of the stream that indicated the end
+ * of the stream. This option is used when multiple snapshots are
+ * sent back to back.
+ */
+#define BTRFS_SEND_FLAG_OMIT_END_CMD 0x4
+
+#define BTRFS_SEND_FLAG_MASK \
+ (BTRFS_SEND_FLAG_NO_FILE_DATA | \
+ BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
+ BTRFS_SEND_FLAG_OMIT_END_CMD)
+
+struct btrfs_ioctl_send_args {
+ __s64 send_fd; /* in */
+ __u64 clone_sources_count; /* in */
+ __u64 *clone_sources; /* in */
+ __u64 parent_root; /* in */
+ __u64 flags; /* in */
+ __u64 reserved[4]; /* in */
+};
+
+/* Error codes as returned by the kernel */
+enum btrfs_err_code {
+ BTRFS_ERROR_DEV_RAID1_MIN_NOT_MET = 1,
+ BTRFS_ERROR_DEV_RAID10_MIN_NOT_MET,
+ BTRFS_ERROR_DEV_RAID5_MIN_NOT_MET,
+ BTRFS_ERROR_DEV_RAID6_MIN_NOT_MET,
+ BTRFS_ERROR_DEV_TGT_REPLACE,
+ BTRFS_ERROR_DEV_MISSING_NOT_FOUND,
+ BTRFS_ERROR_DEV_ONLY_WRITABLE,
+ BTRFS_ERROR_DEV_EXCL_RUN_IN_PROGRESS
+};
+
+#define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SCAN_DEV _IOW(BTRFS_IOCTL_MAGIC, 4, \
+ struct btrfs_ioctl_vol_args)
+/* trans start and trans end are dangerous, and only for
+ * use by applications that know how to avoid the
+ * resulting deadlocks
+ */
+#define BTRFS_IOC_TRANS_START _IO(BTRFS_IOCTL_MAGIC, 6)
+#define BTRFS_IOC_TRANS_END _IO(BTRFS_IOCTL_MAGIC, 7)
+#define BTRFS_IOC_SYNC _IO(BTRFS_IOCTL_MAGIC, 8)
+
+#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
+#define BTRFS_IOC_ADD_DEV _IOW(BTRFS_IOCTL_MAGIC, 10, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_RM_DEV _IOW(BTRFS_IOCTL_MAGIC, 11, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_BALANCE _IOW(BTRFS_IOCTL_MAGIC, 12, \
+ struct btrfs_ioctl_vol_args)
+
+#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
+ struct btrfs_ioctl_clone_range_args)
+
+#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_DEFRAG_RANGE _IOW(BTRFS_IOCTL_MAGIC, 16, \
+ struct btrfs_ioctl_defrag_range_args)
+#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \
+ struct btrfs_ioctl_search_args)
+#define BTRFS_IOC_TREE_SEARCH_V2 _IOWR(BTRFS_IOCTL_MAGIC, 17, \
+ struct btrfs_ioctl_search_args_v2)
+#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \
+ struct btrfs_ioctl_ino_lookup_args)
+#define BTRFS_IOC_DEFAULT_SUBVOL _IOW(BTRFS_IOCTL_MAGIC, 19, __u64)
+#define BTRFS_IOC_SPACE_INFO _IOWR(BTRFS_IOCTL_MAGIC, 20, \
+ struct btrfs_ioctl_space_args)
+#define BTRFS_IOC_START_SYNC _IOR(BTRFS_IOCTL_MAGIC, 24, __u64)
+#define BTRFS_IOC_WAIT_SYNC _IOW(BTRFS_IOCTL_MAGIC, 22, __u64)
+#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \
+ struct btrfs_ioctl_vol_args_v2)
+#define BTRFS_IOC_SUBVOL_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 24, \
+ struct btrfs_ioctl_vol_args_v2)
+#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64)
+#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64)
+#define BTRFS_IOC_SCRUB _IOWR(BTRFS_IOCTL_MAGIC, 27, \
+ struct btrfs_ioctl_scrub_args)
+#define BTRFS_IOC_SCRUB_CANCEL _IO(BTRFS_IOCTL_MAGIC, 28)
+#define BTRFS_IOC_SCRUB_PROGRESS _IOWR(BTRFS_IOCTL_MAGIC, 29, \
+ struct btrfs_ioctl_scrub_args)
+#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \
+ struct btrfs_ioctl_dev_info_args)
+#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \
+ struct btrfs_ioctl_fs_info_args)
+#define BTRFS_IOC_BALANCE_V2 _IOWR(BTRFS_IOCTL_MAGIC, 32, \
+ struct btrfs_ioctl_balance_args)
+#define BTRFS_IOC_BALANCE_CTL _IOW(BTRFS_IOCTL_MAGIC, 33, int)
+#define BTRFS_IOC_BALANCE_PROGRESS _IOR(BTRFS_IOCTL_MAGIC, 34, \
+ struct btrfs_ioctl_balance_args)
+#define BTRFS_IOC_INO_PATHS _IOWR(BTRFS_IOCTL_MAGIC, 35, \
+ struct btrfs_ioctl_ino_path_args)
+#define BTRFS_IOC_LOGICAL_INO _IOWR(BTRFS_IOCTL_MAGIC, 36, \
+ struct btrfs_ioctl_logical_ino_args)
+#define BTRFS_IOC_SET_RECEIVED_SUBVOL _IOWR(BTRFS_IOCTL_MAGIC, 37, \
+ struct btrfs_ioctl_received_subvol_args)
+#define BTRFS_IOC_SEND _IOW(BTRFS_IOCTL_MAGIC, 38, struct btrfs_ioctl_send_args)
+#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_QUOTA_CTL _IOWR(BTRFS_IOCTL_MAGIC, 40, \
+ struct btrfs_ioctl_quota_ctl_args)
+#define BTRFS_IOC_QGROUP_ASSIGN _IOW(BTRFS_IOCTL_MAGIC, 41, \
+ struct btrfs_ioctl_qgroup_assign_args)
+#define BTRFS_IOC_QGROUP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 42, \
+ struct btrfs_ioctl_qgroup_create_args)
+#define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \
+ struct btrfs_ioctl_qgroup_limit_args)
+#define BTRFS_IOC_QUOTA_RESCAN _IOW(BTRFS_IOCTL_MAGIC, 44, \
+ struct btrfs_ioctl_quota_rescan_args)
+#define BTRFS_IOC_QUOTA_RESCAN_STATUS _IOR(BTRFS_IOCTL_MAGIC, 45, \
+ struct btrfs_ioctl_quota_rescan_args)
+#define BTRFS_IOC_QUOTA_RESCAN_WAIT _IO(BTRFS_IOCTL_MAGIC, 46)
+#define BTRFS_IOC_GET_FSLABEL _IOR(BTRFS_IOCTL_MAGIC, 49, \
+ char[BTRFS_LABEL_SIZE])
+#define BTRFS_IOC_SET_FSLABEL _IOW(BTRFS_IOCTL_MAGIC, 50, \
+ char[BTRFS_LABEL_SIZE])
+#define BTRFS_IOC_GET_DEV_STATS _IOWR(BTRFS_IOCTL_MAGIC, 52, \
+ struct btrfs_ioctl_get_dev_stats)
+#define BTRFS_IOC_DEV_REPLACE _IOWR(BTRFS_IOCTL_MAGIC, 53, \
+ struct btrfs_ioctl_dev_replace_args)
+#define BTRFS_IOC_FILE_EXTENT_SAME _IOWR(BTRFS_IOCTL_MAGIC, 54, \
+ struct btrfs_ioctl_same_args)
+#define BTRFS_IOC_GET_FEATURES _IOR(BTRFS_IOCTL_MAGIC, 57, \
+ struct btrfs_ioctl_feature_flags)
+#define BTRFS_IOC_SET_FEATURES _IOW(BTRFS_IOCTL_MAGIC, 57, \
+ struct btrfs_ioctl_feature_flags[2])
+#define BTRFS_IOC_GET_SUPPORTED_FEATURES _IOR(BTRFS_IOCTL_MAGIC, 57, \
+ struct btrfs_ioctl_feature_flags[3])
+#define BTRFS_IOC_RM_DEV_V2 _IOW(BTRFS_IOCTL_MAGIC, 58, \
+ struct btrfs_ioctl_vol_args_v2)
+
+#endif /* _LINUX_BTRFS_H */
diff --git a/libbtrfsutil/btrfs_tree.h b/libbtrfsutil/btrfs_tree.h
new file mode 100644
index 00000000..f2ac0267
--- /dev/null
+++ b/libbtrfsutil/btrfs_tree.h
@@ -0,0 +1,970 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _BTRFS_CTREE_H_
+#define _BTRFS_CTREE_H_
+
+#include "btrfs.h"
+#include <linux/types.h>
+
+/*
+ * This header contains the structure definitions and constants used
+ * by file system objects that can be retrieved using
+ * the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that
+ * is needed to describe a leaf node's key or item contents.
+ */
+
+/* holds pointers to all of the tree roots */
+#define BTRFS_ROOT_TREE_OBJECTID 1ULL
+
+/* stores information about which extents are in use, and reference counts */
+#define BTRFS_EXTENT_TREE_OBJECTID 2ULL
+
+/*
+ * chunk tree stores translations from logical -> physical block numbering
+ * the super block points to the chunk tree
+ */
+#define BTRFS_CHUNK_TREE_OBJECTID 3ULL
+
+/*
+ * stores information about which areas of a given device are in use.
+ * one per device. The tree of tree roots points to the device tree
+ */
+#define BTRFS_DEV_TREE_OBJECTID 4ULL
+
+/* one per subvolume, storing files and directories */
+#define BTRFS_FS_TREE_OBJECTID 5ULL
+
+/* directory objectid inside the root tree */
+#define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL
+
+/* holds checksums of all the data extents */
+#define BTRFS_CSUM_TREE_OBJECTID 7ULL
+
+/* holds quota configuration and tracking */
+#define BTRFS_QUOTA_TREE_OBJECTID 8ULL
+
+/* for storing items that use the BTRFS_UUID_KEY* types */
+#define BTRFS_UUID_TREE_OBJECTID 9ULL
+
+/* tracks free space in block groups. */
+#define BTRFS_FREE_SPACE_TREE_OBJECTID 10ULL
+
+/* device stats in the device tree */
+#define BTRFS_DEV_STATS_OBJECTID 0ULL
+
+/* for storing balance parameters in the root tree */
+#define BTRFS_BALANCE_OBJECTID -4ULL
+
+/* orhpan objectid for tracking unlinked/truncated files */
+#define BTRFS_ORPHAN_OBJECTID -5ULL
+
+/* does write ahead logging to speed up fsyncs */
+#define BTRFS_TREE_LOG_OBJECTID -6ULL
+#define BTRFS_TREE_LOG_FIXUP_OBJECTID -7ULL
+
+/* for space balancing */
+#define BTRFS_TREE_RELOC_OBJECTID -8ULL
+#define BTRFS_DATA_RELOC_TREE_OBJECTID -9ULL
+
+/*
+ * extent checksums all have this objectid
+ * this allows them to share the logging tree
+ * for fsyncs
+ */
+#define BTRFS_EXTENT_CSUM_OBJECTID -10ULL
+
+/* For storing free space cache */
+#define BTRFS_FREE_SPACE_OBJECTID -11ULL
+
+/*
+ * The inode number assigned to the special inode for storing
+ * free ino cache
+ */
+#define BTRFS_FREE_INO_OBJECTID -12ULL
+
+/* dummy objectid represents multiple objectids */
+#define BTRFS_MULTIPLE_OBJECTIDS -255ULL
+
+/*
+ * All files have objectids in this range.
+ */
+#define BTRFS_FIRST_FREE_OBJECTID 256ULL
+#define BTRFS_LAST_FREE_OBJECTID -256ULL
+#define BTRFS_FIRST_CHUNK_TREE_OBJECTID 256ULL
+
+
+/*
+ * the device items go into the chunk tree. The key is in the form
+ * [ 1 BTRFS_DEV_ITEM_KEY device_id ]
+ */
+#define BTRFS_DEV_ITEMS_OBJECTID 1ULL
+
+#define BTRFS_BTREE_INODE_OBJECTID 1
+
+#define BTRFS_EMPTY_SUBVOL_DIR_OBJECTID 2
+
+#define BTRFS_DEV_REPLACE_DEVID 0ULL
+
+/*
+ * inode items have the data typically returned from stat and store other
+ * info about object characteristics. There is one for every file and dir in
+ * the FS
+ */
+#define BTRFS_INODE_ITEM_KEY 1
+#define BTRFS_INODE_REF_KEY 12
+#define BTRFS_INODE_EXTREF_KEY 13
+#define BTRFS_XATTR_ITEM_KEY 24
+#define BTRFS_ORPHAN_ITEM_KEY 48
+/* reserve 2-15 close to the inode for later flexibility */
+
+/*
+ * dir items are the name -> inode pointers in a directory. There is one
+ * for every name in a directory.
+ */
+#define BTRFS_DIR_LOG_ITEM_KEY 60
+#define BTRFS_DIR_LOG_INDEX_KEY 72
+#define BTRFS_DIR_ITEM_KEY 84
+#define BTRFS_DIR_INDEX_KEY 96
+/*
+ * extent data is for file data
+ */
+#define BTRFS_EXTENT_DATA_KEY 108
+
+/*
+ * extent csums are stored in a separate tree and hold csums for
+ * an entire extent on disk.
+ */
+#define BTRFS_EXTENT_CSUM_KEY 128
+
+/*
+ * root items point to tree roots. They are typically in the root
+ * tree used by the super block to find all the other trees
+ */
+#define BTRFS_ROOT_ITEM_KEY 132
+
+/*
+ * root backrefs tie subvols and snapshots to the directory entries that
+ * reference them
+ */
+#define BTRFS_ROOT_BACKREF_KEY 144
+
+/*
+ * root refs make a fast index for listing all of the snapshots and
+ * subvolumes referenced by a given root. They point directly to the
+ * directory item in the root that references the subvol
+ */
+#define BTRFS_ROOT_REF_KEY 156
+
+/*
+ * extent items are in the extent map tree. These record which blocks
+ * are used, and how many references there are to each block
+ */
+#define BTRFS_EXTENT_ITEM_KEY 168
+
+/*
+ * The same as the BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know
+ * the length, so we save the level in key->offset instead of the length.
+ */
+#define BTRFS_METADATA_ITEM_KEY 169
+
+#define BTRFS_TREE_BLOCK_REF_KEY 176
+
+#define BTRFS_EXTENT_DATA_REF_KEY 178
+
+#define BTRFS_EXTENT_REF_V0_KEY 180
+
+#define BTRFS_SHARED_BLOCK_REF_KEY 182
+
+#define BTRFS_SHARED_DATA_REF_KEY 184
+
+/*
+ * block groups give us hints into the extent allocation trees. Which
+ * blocks are free etc etc
+ */
+#define BTRFS_BLOCK_GROUP_ITEM_KEY 192
+
+/*
+ * Every block group is represented in the free space tree by a free space info
+ * item, which stores some accounting information. It is keyed on
+ * (block_group_start, FREE_SPACE_INFO, block_group_length).
+ */
+#define BTRFS_FREE_SPACE_INFO_KEY 198
+
+/*
+ * A free space extent tracks an extent of space that is free in a block group.
+ * It is keyed on (start, FREE_SPACE_EXTENT, length).
+ */
+#define BTRFS_FREE_SPACE_EXTENT_KEY 199
+
+/*
+ * When a block group becomes very fragmented, we convert it to use bitmaps
+ * instead of extents. A free space bitmap is keyed on
+ * (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with
+ * (length / sectorsize) bits.
+ */
+#define BTRFS_FREE_SPACE_BITMAP_KEY 200
+
+#define BTRFS_DEV_EXTENT_KEY 204
+#define BTRFS_DEV_ITEM_KEY 216
+#define BTRFS_CHUNK_ITEM_KEY 228
+
+/*
+ * Records the overall state of the qgroups.
+ * There's only one instance of this key present,
+ * (0, BTRFS_QGROUP_STATUS_KEY, 0)
+ */
+#define BTRFS_QGROUP_STATUS_KEY 240
+/*
+ * Records the currently used space of the qgroup.
+ * One key per qgroup, (0, BTRFS_QGROUP_INFO_KEY, qgroupid).
+ */
+#define BTRFS_QGROUP_INFO_KEY 242
+/*
+ * Contains the user configured limits for the qgroup.
+ * One key per qgroup, (0, BTRFS_QGROUP_LIMIT_KEY, qgroupid).
+ */
+#define BTRFS_QGROUP_LIMIT_KEY 244
+/*
+ * Records the child-parent relationship of qgroups. For
+ * each relation, 2 keys are present:
+ * (childid, BTRFS_QGROUP_RELATION_KEY, parentid)
+ * (parentid, BTRFS_QGROUP_RELATION_KEY, childid)
+ */
+#define BTRFS_QGROUP_RELATION_KEY 246
+
+/*
+ * Obsolete name, see BTRFS_TEMPORARY_ITEM_KEY.
+ */
+#define BTRFS_BALANCE_ITEM_KEY 248
+
+/*
+ * The key type for tree items that are stored persistently, but do not need to
+ * exist for extended period of time. The items can exist in any tree.
+ *
+ * [subtype, BTRFS_TEMPORARY_ITEM_KEY, data]
+ *
+ * Existing items:
+ *
+ * - balance status item
+ * (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0)
+ */
+#define BTRFS_TEMPORARY_ITEM_KEY 248
+
+/*
+ * Obsolete name, see BTRFS_PERSISTENT_ITEM_KEY
+ */
+#define BTRFS_DEV_STATS_KEY 249
+
+/*
+ * The key type for tree items that are stored persistently and usually exist
+ * for a long period, eg. filesystem lifetime. The item kinds can be status
+ * information, stats or preference values. The item can exist in any tree.
+ *
+ * [subtype, BTRFS_PERSISTENT_ITEM_KEY, data]
+ *
+ * Existing items:
+ *
+ * - device statistics, store IO stats in the device tree, one key for all
+ * stats
+ * (BTRFS_DEV_STATS_OBJECTID, BTRFS_DEV_STATS_KEY, 0)
+ */
+#define BTRFS_PERSISTENT_ITEM_KEY 249
+
+/*
+ * Persistantly stores the device replace state in the device tree.
+ * The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0).
+ */
+#define BTRFS_DEV_REPLACE_KEY 250
+
+/*
+ * Stores items that allow to quickly map UUIDs to something else.
+ * These items are part of the filesystem UUID tree.
+ * The key is built like this:
+ * (UUID_upper_64_bits, BTRFS_UUID_KEY*, UUID_lower_64_bits).
+ */
+#if BTRFS_UUID_SIZE != 16
+#error "UUID items require BTRFS_UUID_SIZE == 16!"
+#endif
+#define BTRFS_UUID_KEY_SUBVOL 251 /* for UUIDs assigned to subvols */
+#define BTRFS_UUID_KEY_RECEIVED_SUBVOL 252 /* for UUIDs assigned to
+ * received subvols */
+
+/*
+ * string items are for debugging. They just store a short string of
+ * data in the FS
+ */
+#define BTRFS_STRING_ITEM_KEY 253
+
+
+
+/* 32 bytes in various csum fields */
+#define BTRFS_CSUM_SIZE 32
+
+/* csum types */
+#define BTRFS_CSUM_TYPE_CRC32 0
+
+/*
+ * flags definitions for directory entry item type
+ *
+ * Used by:
+ * struct btrfs_dir_item.type
+ */
+#define BTRFS_FT_UNKNOWN 0
+#define BTRFS_FT_REG_FILE 1
+#define BTRFS_FT_DIR 2
+#define BTRFS_FT_CHRDEV 3
+#define BTRFS_FT_BLKDEV 4
+#define BTRFS_FT_FIFO 5
+#define BTRFS_FT_SOCK 6
+#define BTRFS_FT_SYMLINK 7
+#define BTRFS_FT_XATTR 8
+#define BTRFS_FT_MAX 9
+
+/*
+ * The key defines the order in the tree, and so it also defines (optimal)
+ * block layout.
+ *
+ * objectid corresponds to the inode number.
+ *
+ * type tells us things about the object, and is a kind of stream selector.
+ * so for a given inode, keys with type of 1 might refer to the inode data,
+ * type of 2 may point to file data in the btree and type == 3 may point to
+ * extents.
+ *
+ * offset is the starting byte offset for this key in the stream.
+ *
+ * btrfs_disk_key is in disk byte order. struct btrfs_key is always
+ * in cpu native order. Otherwise they are identical and their sizes
+ * should be the same (ie both packed)
+ */
+struct btrfs_disk_key {
+ __le64 objectid;
+ __u8 type;
+ __le64 offset;
+} __attribute__ ((__packed__));
+
+struct btrfs_key {
+ __u64 objectid;
+ __u8 type;
+ __u64 offset;
+} __attribute__ ((__packed__));
+
+struct btrfs_dev_item {
+ /* the internal btrfs device id */
+ __le64 devid;
+
+ /* size of the device */
+ __le64 total_bytes;
+
+ /* bytes used */
+ __le64 bytes_used;
+
+ /* optimal io alignment for this device */
+ __le32 io_align;
+
+ /* optimal io width for this device */
+ __le32 io_width;
+
+ /* minimal io size for this device */
+ __le32 sector_size;
+
+ /* type and info about this device */
+ __le64 type;
+
+ /* expected generation for this device */
+ __le64 generation;
+
+ /*
+ * starting byte of this partition on the device,
+ * to allow for stripe alignment in the future
+ */
+ __le64 start_offset;
+
+ /* grouping information for allocation decisions */
+ __le32 dev_group;
+
+ /* seek speed 0-100 where 100 is fastest */
+ __u8 seek_speed;
+
+ /* bandwidth 0-100 where 100 is fastest */
+ __u8 bandwidth;
+
+ /* btrfs generated uuid for this device */
+ __u8 uuid[BTRFS_UUID_SIZE];
+
+ /* uuid of FS who owns this device */
+ __u8 fsid[BTRFS_UUID_SIZE];
+} __attribute__ ((__packed__));
+
+struct btrfs_stripe {
+ __le64 devid;
+ __le64 offset;
+ __u8 dev_uuid[BTRFS_UUID_SIZE];
+} __attribute__ ((__packed__));
+
+struct btrfs_chunk {
+ /* size of this chunk in bytes */
+ __le64 length;
+
+ /* objectid of the root referencing this chunk */
+ __le64 owner;
+
+ __le64 stripe_len;
+ __le64 type;
+
+ /* optimal io alignment for this chunk */
+ __le32 io_align;
+
+ /* optimal io width for this chunk */
+ __le32 io_width;
+
+ /* minimal io size for this chunk */
+ __le32 sector_size;
+
+ /* 2^16 stripes is quite a lot, a second limit is the size of a single
+ * item in the btree
+ */
+ __le16 num_stripes;
+
+ /* sub stripes only matter for raid10 */
+ __le16 sub_stripes;
+ struct btrfs_stripe stripe;
+ /* additional stripes go here */
+} __attribute__ ((__packed__));
+
+#define BTRFS_FREE_SPACE_EXTENT 1
+#define BTRFS_FREE_SPACE_BITMAP 2
+
+struct btrfs_free_space_entry {
+ __le64 offset;
+ __le64 bytes;
+ __u8 type;
+} __attribute__ ((__packed__));
+
+struct btrfs_free_space_header {
+ struct btrfs_disk_key location;
+ __le64 generation;
+ __le64 num_entries;
+ __le64 num_bitmaps;
+} __attribute__ ((__packed__));
+
+#define BTRFS_HEADER_FLAG_WRITTEN (1ULL << 0)
+#define BTRFS_HEADER_FLAG_RELOC (1ULL << 1)
+
+/* Super block flags */
+/* Errors detected */
+#define BTRFS_SUPER_FLAG_ERROR (1ULL << 2)
+
+#define BTRFS_SUPER_FLAG_SEEDING (1ULL << 32)
+#define BTRFS_SUPER_FLAG_METADUMP (1ULL << 33)
+
+
+/*
+ * items in the extent btree are used to record the objectid of the
+ * owner of the block and the number of references
+ */
+
+struct btrfs_extent_item {
+ __le64 refs;
+ __le64 generation;
+ __le64 flags;
+} __attribute__ ((__packed__));
+
+struct btrfs_extent_item_v0 {
+ __le32 refs;
+} __attribute__ ((__packed__));
+
+
+#define BTRFS_EXTENT_FLAG_DATA (1ULL << 0)
+#define BTRFS_EXTENT_FLAG_TREE_BLOCK (1ULL << 1)
+
+/* following flags only apply to tree blocks */
+
+/* use full backrefs for extent pointers in the block */
+#define BTRFS_BLOCK_FLAG_FULL_BACKREF (1ULL << 8)
+
+/*
+ * this flag is only used internally by scrub and may be changed at any time
+ * it is only declared here to avoid collisions
+ */
+#define BTRFS_EXTENT_FLAG_SUPER (1ULL << 48)
+
+struct btrfs_tree_block_info {
+ struct btrfs_disk_key key;
+ __u8 level;
+} __attribute__ ((__packed__));
+
+struct btrfs_extent_data_ref {
+ __le64 root;
+ __le64 objectid;
+ __le64 offset;
+ __le32 count;
+} __attribute__ ((__packed__));
+
+struct btrfs_shared_data_ref {
+ __le32 count;
+} __attribute__ ((__packed__));
+
+struct btrfs_extent_inline_ref {
+ __u8 type;
+ __le64 offset;
+} __attribute__ ((__packed__));
+
+/* old style backrefs item */
+struct btrfs_extent_ref_v0 {
+ __le64 root;
+ __le64 generation;
+ __le64 objectid;
+ __le32 count;
+} __attribute__ ((__packed__));
+
+
+/* dev extents record free space on individual devices. The owner
+ * field points back to the chunk allocation mapping tree that allocated
+ * the extent. The chunk tree uuid field is a way to double check the owner
+ */
+struct btrfs_dev_extent {
+ __le64 chunk_tree;
+ __le64 chunk_objectid;
+ __le64 chunk_offset;
+ __le64 length;
+ __u8 chunk_tree_uuid[BTRFS_UUID_SIZE];
+} __attribute__ ((__packed__));
+
+struct btrfs_inode_ref {
+ __le64 index;
+ __le16 name_len;
+ /* name goes here */
+} __attribute__ ((__packed__));
+
+struct btrfs_inode_extref {
+ __le64 parent_objectid;
+ __le64 index;
+ __le16 name_len;
+ __u8 name[0];
+ /* name goes here */
+} __attribute__ ((__packed__));
+
+struct btrfs_timespec {
+ __le64 sec;
+ __le32 nsec;
+} __attribute__ ((__packed__));
+
+struct btrfs_inode_item {
+ /* nfs style generation number */
+ __le64 generation;
+ /* transid that last touched this inode */
+ __le64 transid;
+ __le64 size;
+ __le64 nbytes;
+ __le64 block_group;
+ __le32 nlink;
+ __le32 uid;
+ __le32 gid;
+ __le32 mode;
+ __le64 rdev;
+ __le64 flags;
+
+ /* modification sequence number for NFS */
+ __le64 sequence;
+
+ /*
+ * a little future expansion, for more than this we can
+ * just grow the inode item and version it
+ */
+ __le64 reserved[4];
+ struct btrfs_timespec atime;
+ struct btrfs_timespec ctime;
+ struct btrfs_timespec mtime;
+ struct btrfs_timespec otime;
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_log_item {
+ __le64 end;
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+ struct btrfs_disk_key location;
+ __le64 transid;
+ __le16 data_len;
+ __le16 name_len;
+ __u8 type;
+} __attribute__ ((__packed__));
+
+#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0)
+
+/*
+ * Internal in-memory flag that a subvolume has been marked for deletion but
+ * still visible as a directory
+ */
+#define BTRFS_ROOT_SUBVOL_DEAD (1ULL << 48)
+
+struct btrfs_root_item {
+ struct btrfs_inode_item inode;
+ __le64 generation;
+ __le64 root_dirid;
+ __le64 bytenr;
+ __le64 byte_limit;
+ __le64 bytes_used;
+ __le64 last_snapshot;
+ __le64 flags;
+ __le32 refs;
+ struct btrfs_disk_key drop_progress;
+ __u8 drop_level;
+ __u8 level;
+
+ /*
+ * The following fields appear after subvol_uuids+subvol_times
+ * were introduced.
+ */
+
+ /*
+ * This generation number is used to test if the new fields are valid
+ * and up to date while reading the root item. Every time the root item
+ * is written out, the "generation" field is copied into this field. If
+ * anyone ever mounted the fs with an older kernel, we will have
+ * mismatching generation values here and thus must invalidate the
+ * new fields. See btrfs_update_root and btrfs_find_last_root for
+ * details.
+ * the offset of generation_v2 is also used as the start for the memset
+ * when invalidating the fields.
+ */
+ __le64 generation_v2;
+ __u8 uuid[BTRFS_UUID_SIZE];
+ __u8 parent_uuid[BTRFS_UUID_SIZE];
+ __u8 received_uuid[BTRFS_UUID_SIZE];
+ __le64 ctransid; /* updated when an inode changes */
+ __le64 otransid; /* trans when created */
+ __le64 stransid; /* trans when sent. non-zero for received subvol */
+ __le64 rtransid; /* trans when received. non-zero for received subvol */
+ struct btrfs_timespec ctime;
+ struct btrfs_timespec otime;
+ struct btrfs_timespec stime;
+ struct btrfs_timespec rtime;
+ __le64 reserved[8]; /* for future */
+} __attribute__ ((__packed__));
+
+/*
+ * this is used for both forward and backward root refs
+ */
+struct btrfs_root_ref {
+ __le64 dirid;
+ __le64 sequence;
+ __le16 name_len;
+} __attribute__ ((__packed__));
+
+struct btrfs_disk_balance_args {
+ /*
+ * profiles to operate on, single is denoted by
+ * BTRFS_AVAIL_ALLOC_BIT_SINGLE
+ */
+ __le64 profiles;
+
+ /*
+ * usage filter
+ * BTRFS_BALANCE_ARGS_USAGE with a single value means '0..N'
+ * BTRFS_BALANCE_ARGS_USAGE_RANGE - range syntax, min..max
+ */
+ union {
+ __le64 usage;
+ struct {
+ __le32 usage_min;
+ __le32 usage_max;
+ };
+ };
+
+ /* devid filter */
+ __le64 devid;
+
+ /* devid subset filter [pstart..pend) */
+ __le64 pstart;
+ __le64 pend;
+
+ /* btrfs virtual address space subset filter [vstart..vend) */
+ __le64 vstart;
+ __le64 vend;
+
+ /*
+ * profile to convert to, single is denoted by
+ * BTRFS_AVAIL_ALLOC_BIT_SINGLE
+ */
+ __le64 target;
+
+ /* BTRFS_BALANCE_ARGS_* */
+ __le64 flags;
+
+ /*
+ * BTRFS_BALANCE_ARGS_LIMIT with value 'limit'
+ * BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum
+ * and maximum
+ */
+ union {
+ __le64 limit;
+ struct {
+ __le32 limit_min;
+ __le32 limit_max;
+ };
+ };
+
+ /*
+ * Process chunks that cross stripes_min..stripes_max devices,
+ * BTRFS_BALANCE_ARGS_STRIPES_RANGE
+ */
+ __le32 stripes_min;
+ __le32 stripes_max;
+
+ __le64 unused[6];
+} __attribute__ ((__packed__));
+
+/*
+ * store balance parameters to disk so that balance can be properly
+ * resumed after crash or unmount
+ */
+struct btrfs_balance_item {
+ /* BTRFS_BALANCE_* */
+ __le64 flags;
+
+ struct btrfs_disk_balance_args data;
+ struct btrfs_disk_balance_args meta;
+ struct btrfs_disk_balance_args sys;
+
+ __le64 unused[4];
+} __attribute__ ((__packed__));
+
+#define BTRFS_FILE_EXTENT_INLINE 0
+#define BTRFS_FILE_EXTENT_REG 1
+#define BTRFS_FILE_EXTENT_PREALLOC 2
+
+struct btrfs_file_extent_item {
+ /*
+ * transaction id that created this extent
+ */
+ __le64 generation;
+ /*
+ * max number of bytes to hold this extent in ram
+ * when we split a compressed extent we can't know how big
+ * each of the resulting pieces will be. So, this is
+ * an upper limit on the size of the extent in ram instead of
+ * an exact limit.
+ */
+ __le64 ram_bytes;
+
+ /*
+ * 32 bits for the various ways we might encode the data,
+ * including compression and encryption. If any of these
+ * are set to something a given disk format doesn't understand
+ * it is treated like an incompat flag for reading and writing,
+ * but not for stat.
+ */
+ __u8 compression;
+ __u8 encryption;
+ __le16 other_encoding; /* spare for later use */
+
+ /* are we __inline__ data or a real extent? */
+ __u8 type;
+
+ /*
+ * disk space consumed by the extent, checksum blocks are included
+ * in these numbers
+ *
+ * At this offset in the structure, the __inline__ extent data start.
+ */
+ __le64 disk_bytenr;
+ __le64 disk_num_bytes;
+ /*
+ * the logical offset in file blocks (no csums)
+ * this extent record is for. This allows a file extent to point
+ * into the middle of an existing extent on disk, sharing it
+ * between two snapshots (useful if some bytes in the middle of the
+ * extent have changed
+ */
+ __le64 offset;
+ /*
+ * the logical number of file blocks (no csums included). This
+ * always reflects the size uncompressed and without encoding.
+ */
+ __le64 num_bytes;
+
+} __attribute__ ((__packed__));
+
+struct btrfs_csum_item {
+ __u8 csum;
+} __attribute__ ((__packed__));
+
+struct btrfs_dev_stats_item {
+ /*
+ * grow this item struct at the end for future enhancements and keep
+ * the existing values unchanged
+ */
+ __le64 values[BTRFS_DEV_STAT_VALUES_MAX];
+} __attribute__ ((__packed__));
+
+#define BTRFS_DEV_REPLACE_ITEM_CONT_READING_FROM_SRCDEV_MODE_ALWAYS 0
+#define BTRFS_DEV_REPLACE_ITEM_CONT_READING_FROM_SRCDEV_MODE_AVOID 1
+#define BTRFS_DEV_REPLACE_ITEM_STATE_NEVER_STARTED 0
+#define BTRFS_DEV_REPLACE_ITEM_STATE_STARTED 1
+#define BTRFS_DEV_REPLACE_ITEM_STATE_SUSPENDED 2
+#define BTRFS_DEV_REPLACE_ITEM_STATE_FINISHED 3
+#define BTRFS_DEV_REPLACE_ITEM_STATE_CANCELED 4
+
+struct btrfs_dev_replace_item {
+ /*
+ * grow this item struct at the end for future enhancements and keep
+ * the existing values unchanged
+ */
+ __le64 src_devid;
+ __le64 cursor_left;
+ __le64 cursor_right;
+ __le64 cont_reading_from_srcdev_mode;
+
+ __le64 replace_state;
+ __le64 time_started;
+ __le64 time_stopped;
+ __le64 num_write_errors;
+ __le64 num_uncorrectable_read_errors;
+} __attribute__ ((__packed__));
+
+/* different types of block groups (and chunks) */
+#define BTRFS_BLOCK_GROUP_DATA (1ULL << 0)
+#define BTRFS_BLOCK_GROUP_SYSTEM (1ULL << 1)
+#define BTRFS_BLOCK_GROUP_METADATA (1ULL << 2)
+#define BTRFS_BLOCK_GROUP_RAID0 (1ULL << 3)
+#define BTRFS_BLOCK_GROUP_RAID1 (1ULL << 4)
+#define BTRFS_BLOCK_GROUP_DUP (1ULL << 5)
+#define BTRFS_BLOCK_GROUP_RAID10 (1ULL << 6)
+#define BTRFS_BLOCK_GROUP_RAID5 (1ULL << 7)
+#define BTRFS_BLOCK_GROUP_RAID6 (1ULL << 8)
+#define BTRFS_BLOCK_GROUP_RESERVED (BTRFS_AVAIL_ALLOC_BIT_SINGLE | \
+ BTRFS_SPACE_INFO_GLOBAL_RSV)
+
+enum btrfs_raid_types {
+ BTRFS_RAID_RAID10,
+ BTRFS_RAID_RAID1,
+ BTRFS_RAID_DUP,
+ BTRFS_RAID_RAID0,
+ BTRFS_RAID_SINGLE,
+ BTRFS_RAID_RAID5,
+ BTRFS_RAID_RAID6,
+ BTRFS_NR_RAID_TYPES
+};
+
+#define BTRFS_BLOCK_GROUP_TYPE_MASK (BTRFS_BLOCK_GROUP_DATA | \
+ BTRFS_BLOCK_GROUP_SYSTEM | \
+ BTRFS_BLOCK_GROUP_METADATA)
+
+#define BTRFS_BLOCK_GROUP_PROFILE_MASK (BTRFS_BLOCK_GROUP_RAID0 | \
+ BTRFS_BLOCK_GROUP_RAID1 | \
+ BTRFS_BLOCK_GROUP_RAID5 | \
+ BTRFS_BLOCK_GROUP_RAID6 | \
+ BTRFS_BLOCK_GROUP_DUP | \
+ BTRFS_BLOCK_GROUP_RAID10)
+#define BTRFS_BLOCK_GROUP_RAID56_MASK (BTRFS_BLOCK_GROUP_RAID5 | \
+ BTRFS_BLOCK_GROUP_RAID6)
+
+/*
+ * We need a bit for restriper to be able to tell when chunks of type
+ * SINGLE are available. This "extended" profile format is used in
+ * fs_info->avail_*_alloc_bits (in-memory) and balance item fields
+ * (on-disk). The corresponding on-disk bit in chunk.type is reserved
+ * to avoid remappings between two formats in future.
+ */
+#define BTRFS_AVAIL_ALLOC_BIT_SINGLE (1ULL << 48)
+
+/*
+ * A fake block group type that is used to communicate global block reserve
+ * size to userspace via the SPACE_INFO ioctl.
+ */
+#define BTRFS_SPACE_INFO_GLOBAL_RSV (1ULL << 49)
+
+#define BTRFS_EXTENDED_PROFILE_MASK (BTRFS_BLOCK_GROUP_PROFILE_MASK | \
+ BTRFS_AVAIL_ALLOC_BIT_SINGLE)
+
+static __inline__ __u64 chunk_to_extended(__u64 flags)
+{
+ if ((flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0)
+ flags |= BTRFS_AVAIL_ALLOC_BIT_SINGLE;
+
+ return flags;
+}
+static __inline__ __u64 extended_to_chunk(__u64 flags)
+{
+ return flags & ~BTRFS_AVAIL_ALLOC_BIT_SINGLE;
+}
+
+struct btrfs_block_group_item {
+ __le64 used;
+ __le64 chunk_objectid;
+ __le64 flags;
+} __attribute__ ((__packed__));
+
+struct btrfs_free_space_info {
+ __le32 extent_count;
+ __le32 flags;
+} __attribute__ ((__packed__));
+
+#define BTRFS_FREE_SPACE_USING_BITMAPS (1ULL << 0)
+
+#define BTRFS_QGROUP_LEVEL_SHIFT 48
+static __inline__ __u64 btrfs_qgroup_level(__u64 qgroupid)
+{
+ return qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT;
+}
+
+/*
+ * is subvolume quota turned on?
+ */
+#define BTRFS_QGROUP_STATUS_FLAG_ON (1ULL << 0)
+/*
+ * RESCAN is set during the initialization phase
+ */
+#define BTRFS_QGROUP_STATUS_FLAG_RESCAN (1ULL << 1)
+/*
+ * Some qgroup entries are known to be out of date,
+ * either because the configuration has changed in a way that
+ * makes a rescan necessary, or because the fs has been mounted
+ * with a non-qgroup-aware version.
+ * Turning qouta off and on again makes it inconsistent, too.
+ */
+#define BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT (1ULL << 2)
+
+#define BTRFS_QGROUP_STATUS_VERSION 1
+
+struct btrfs_qgroup_status_item {
+ __le64 version;
+ /*
+ * the generation is updated during every commit. As older
+ * versions of btrfs are not aware of qgroups, it will be
+ * possible to detect inconsistencies by checking the
+ * generation on mount time
+ */
+ __le64 generation;
+
+ /* flag definitions see above */
+ __le64 flags;
+
+ /*
+ * only used during scanning to record the progress
+ * of the scan. It contains a logical address
+ */
+ __le64 rescan;
+} __attribute__ ((__packed__));
+
+struct btrfs_qgroup_info_item {
+ __le64 generation;
+ __le64 rfer;
+ __le64 rfer_cmpr;
+ __le64 excl;
+ __le64 excl_cmpr;
+} __attribute__ ((__packed__));
+
+struct btrfs_qgroup_limit_item {
+ /*
+ * only updated when any of the other values change
+ */
+ __le64 flags;
+ __le64 max_rfer;
+ __le64 max_excl;
+ __le64 rsv_rfer;
+ __le64 rsv_excl;
+} __attribute__ ((__packed__));
+
+#endif /* _BTRFS_CTREE_H_ */
diff --git a/libbtrfsutil/btrfsutil.h b/libbtrfsutil/btrfsutil.h
new file mode 100644
index 00000000..6d655f49
--- /dev/null
+++ b/libbtrfsutil/btrfsutil.h
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BTRFS_UTIL_H
+#define BTRFS_UTIL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/time.h>
+
+#define BTRFS_UTIL_VERSION_MAJOR 1
+#define BTRFS_UTIL_VERSION_MINOR 0
+#define BTRFS_UTIL_VERSION_PATCH 0
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * enum btrfs_util_error - libbtrfsutil error codes.
+ *
+ * All functions in libbtrfsutil that can return an error return this type and
+ * set errno.
+ */
+enum btrfs_util_error {
+ BTRFS_UTIL_OK,
+ BTRFS_UTIL_ERROR_STOP_ITERATION,
+ BTRFS_UTIL_ERROR_NO_MEMORY,
+ BTRFS_UTIL_ERROR_INVALID_ARGUMENT,
+ BTRFS_UTIL_ERROR_NOT_BTRFS,
+ BTRFS_UTIL_ERROR_NOT_SUBVOLUME,
+ BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND,
+ BTRFS_UTIL_ERROR_OPEN_FAILED,
+ BTRFS_UTIL_ERROR_RMDIR_FAILED,
+ BTRFS_UTIL_ERROR_UNLINK_FAILED,
+ BTRFS_UTIL_ERROR_STAT_FAILED,
+ BTRFS_UTIL_ERROR_STATFS_FAILED,
+ BTRFS_UTIL_ERROR_SEARCH_FAILED,
+ BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED,
+ BTRFS_UTIL_ERROR_SUBVOL_GETFLAGS_FAILED,
+ BTRFS_UTIL_ERROR_SUBVOL_SETFLAGS_FAILED,
+ BTRFS_UTIL_ERROR_SUBVOL_CREATE_FAILED,
+ BTRFS_UTIL_ERROR_SNAP_CREATE_FAILED,
+ BTRFS_UTIL_ERROR_SNAP_DESTROY_FAILED,
+ BTRFS_UTIL_ERROR_DEFAULT_SUBVOL_FAILED,
+ BTRFS_UTIL_ERROR_SYNC_FAILED,
+ BTRFS_UTIL_ERROR_START_SYNC_FAILED,
+ BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED,
+};
+
+/**
+ * btrfs_util_strerror() - Convert a libtrfsutil error code to a string
+ * description.
+ * @err: The error to convert.
+ *
+ * Return: Error description.
+ */
+const char *btrfs_util_strerror(enum btrfs_util_error err);
+
+/**
+ * btrfs_util_sync() - Force a sync on a specific Btrfs filesystem.
+ * @path: Path on a Btrfs filesystem.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_sync(const char *path);
+
+/**
+ * btrfs_util_sync_fd() - See btrfs_util_sync().
+ */
+enum btrfs_util_error btrfs_util_sync_fd(int fd);
+
+/**
+ * btrfs_util_start_sync() - Start a sync on a specific Btrfs filesystem but
+ * don't wait for it.
+ * @path: Path on a Btrfs filesystem.
+ * @transid: Returned transaction ID which can be waited on with
+ * btrfs_util_wait_sync(). This can be %NULL.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_start_sync(const char *path,
+ uint64_t *transid);
+
+/**
+ * btrfs_util_start_sync_fd() - See btrfs_util_start_sync().
+ */
+enum btrfs_util_error btrfs_util_start_sync_fd(int fd, uint64_t *transid);
+
+/**
+ * btrfs_util_wait_sync() - Wait for a transaction with a given ID to sync.
+ * @path: Path on a Btrfs filesystem.
+ * @transid: Transaction ID to wait for, or zero for the current transaction.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_wait_sync(const char *path, uint64_t transid);
+
+/**
+ * btrfs_util_wait_sync_fd() - See btrfs_util_wait_sync().
+ */
+enum btrfs_util_error btrfs_util_wait_sync_fd(int fd, uint64_t transid);
+
+/**
+ * btrfs_util_is_subvolume() - Return whether a given path is a Btrfs subvolume.
+ * @path: Path to check.
+ *
+ * Return: %BTRFS_UTIL_OK if @path is a Btrfs subvolume,
+ * %BTRFS_UTIL_ERROR_NOT_BTRFS if @path is not on a Btrfs filesystem,
+ * %BTRFS_UTIL_ERROR_NOT_SUBVOLUME if @path is not a subvolume, non-zero error
+ * code on any other failure.
+ */
+enum btrfs_util_error btrfs_util_is_subvolume(const char *path);
+
+/**
+ * btrfs_util_is_subvolume_fd() - See btrfs_util_is_subvolume().
+ */
+enum btrfs_util_error btrfs_util_is_subvolume_fd(int fd);
+
+/**
+ * btrfs_util_subvolume_id() - Get the ID of the subvolume containing a path.
+ * @path: Path on a Btrfs filesystem.
+ * @id_ret: Returned subvolume ID.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_id(const char *path,
+ uint64_t *id_ret);
+
+/**
+ * btrfs_util_subvolume_id_fd() - See btrfs_util_subvolume_id().
+ */
+enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, uint64_t *id_ret);
+
+/**
+ * btrfs_util_subvolume_path() - Get the path of the subvolume with a given ID
+ * relative to the filesystem root.
+ * @path: Path on a Btrfs filesystem.
+ * @id: ID of subvolume to set as the default. If zero is given, the subvolume
+ * ID of @path is used.
+ * @path_ret: Returned path.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_path(const char *path, uint64_t id,
+ char **path_ret);
+
+/**
+ * btrfs_util_subvolume_path_fd() - See btrfs_util_subvolume_path().
+ */
+enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
+ char **path_ret);
+
+/**
+ * struct btrfs_util_subvolume_info - Information about a Btrfs subvolume.
+ */
+struct btrfs_util_subvolume_info {
+ /** @id: ID of this subvolume, unique across the filesystem. */
+ uint64_t id;
+
+ /**
+ * @parent_id: ID of the subvolume which contains this subvolume, or
+ * zero for the root subvolume (BTRFS_FS_TREE_OBJECTID) or orphaned
+ * subvolumes (i.e., subvolumes which have been deleted but not yet
+ * cleaned up).
+ */
+ uint64_t parent_id;
+
+ /**
+ * @dir_id: Inode number of the directory containing this subvolume in
+ * the parent subvolume, or zero for the root subvolume
+ * (BTRFS_FS_TREE_OBJECTID) or orphaned subvolumes.
+ */
+ uint64_t dir_id;
+
+ /** @flags: On-disk root item flags. */
+ uint64_t flags;
+
+ /** @uuid: UUID of this subvolume. */
+ uint8_t uuid[16];
+
+ /**
+ * @parent_uuid: UUID of the subvolume this subvolume is a snapshot of,
+ * or all zeroes if this subvolume is not a snapshot.
+ */
+ uint8_t parent_uuid[16];
+
+ /**
+ * @received_uuid: UUID of the subvolume this subvolume was received
+ * from, or all zeroes if this subvolume was not received. Note that
+ * this field, @stransid, @rtransid, @stime, and @rtime are set manually
+ * by userspace after a subvolume is received.
+ */
+ uint8_t received_uuid[16];
+
+ /** @generation: Transaction ID of the subvolume root. */
+ uint64_t generation;
+
+ /**
+ * @ctransid: Transaction ID when an inode in this subvolume was last
+ * changed.
+ */
+ uint64_t ctransid;
+
+ /** @otransid: Transaction ID when this subvolume was created. */
+ uint64_t otransid;
+
+ /**
+ * @stransid: Transaction ID of the sent subvolume this subvolume was
+ * received from, or zero if this subvolume was not received. See the
+ * note on @received_uuid.
+ */
+ uint64_t stransid;
+
+ /**
+ * @rtransid: Transaction ID when this subvolume was received, or zero
+ * if this subvolume was not received. See the note on @received_uuid.
+ */
+ uint64_t rtransid;
+
+ /** @ctime: Time when an inode in this subvolume was last changed. */
+ struct timespec ctime;
+
+ /** @otime: Time when this subvolume was created. */
+ struct timespec otime;
+
+ /**
+ * @stime: Not well-defined, usually zero unless it was set otherwise.
+ * See the note on @received_uuid.
+ */
+ struct timespec stime;
+
+ /**
+ * @rtime: Time when this subvolume was received, or zero if this
+ * subvolume was not received. See the note on @received_uuid.
+ */
+ struct timespec rtime;
+};
+
+/**
+ * btrfs_util_subvolume_info() - Get information about a subvolume.
+ * @path: Path in a Btrfs filesystem. This may be any path in the filesystem; it
+ * does not have to refer to a subvolume unless @id is zero.
+ * @id: ID of subvolume to get information about. If zero is given, the
+ * subvolume ID of @path is used.
+ * @subvol: Returned subvolume information. This can be %NULL if you just want
+ * to check whether the subvolume exists; %BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND
+ * will be returned if it does not.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_info(const char *path, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol);
+
+/**
+ * btrfs_util_subvolume_info_fd() - See btrfs_util_subvolume_info().
+ */
+enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol);
+
+/**
+ * btrfs_util_get_subvolume_read_only() - Get whether a subvolume is read-only.
+ * @path: Subvolume path.
+ * @ret: Returned read-only flag.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_get_subvolume_read_only(const char *path,
+ bool *ret);
+
+/**
+ * btrfs_util_get_subvolume_read_only_fd() - See
+ * btrfs_util_get_subvolume_read_only().
+ */
+enum btrfs_util_error btrfs_util_get_subvolume_read_only_fd(int fd, bool *ret);
+
+/**
+ * btrfs_util_set_subvolume_read_only() - Set whether a subvolume is read-only.
+ * @path: Subvolume path.
+ * @read_only: New value of read-only flag.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_set_subvolume_read_only(const char *path,
+ bool read_only);
+
+/**
+ * btrfs_util_set_subvolume_read_only_fd() - See
+ * btrfs_util_set_subvolume_read_only().
+ */
+enum btrfs_util_error btrfs_util_set_subvolume_read_only_fd(int fd,
+ bool read_only);
+
+/**
+ * btrfs_util_get_default_subvolume() - Get the default subvolume for a
+ * filesystem.
+ * @path: Path on a Btrfs filesystem.
+ * @id_ret: Returned subvolume ID.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_get_default_subvolume(const char *path,
+ uint64_t *id_ret);
+
+/**
+ * btrfs_util_get_default_subvolume_fd() - See
+ * btrfs_util_get_default_subvolume().
+ */
+enum btrfs_util_error btrfs_util_get_default_subvolume_fd(int fd,
+ uint64_t *id_ret);
+
+/**
+ * btrfs_util_set_default_subvolume() - Set the default subvolume for a
+ * filesystem.
+ * @path: Path in a Btrfs filesystem. This may be any path in the filesystem; it
+ * does not have to refer to a subvolume unless @id is zero.
+ * @id: ID of subvolume to set as the default. If zero is given, the subvolume
+ * ID of @path is used.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_set_default_subvolume(const char *path,
+ uint64_t id);
+
+/**
+ * btrfs_util_set_default_subvolume_fd() - See
+ * btrfs_util_set_default_subvolume().
+ */
+enum btrfs_util_error btrfs_util_set_default_subvolume_fd(int fd, uint64_t id);
+
+struct btrfs_util_qgroup_inherit;
+
+/**
+ * btrfs_util_create_subvolume() - Create a new subvolume.
+ * @path: Where to create the subvolume.
+ * @flags: Must be zero.
+ * @async_transid: If not NULL, create the subvolume asynchronously (i.e.,
+ * without waiting for it to commit it to disk) and return the transaction ID
+ * that it was created in. This transaction ID can be waited on with
+ * btrfs_util_wait_sync().
+ * @qgroup_inherit: Qgroups to inherit from, or NULL.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_create_subvolume(const char *path, int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit);
+
+/**
+ * btrfs_util_create_subvolume_fd() - Create a new subvolume given its parent
+ * and name.
+ * @parent_fd: File descriptor of the parent directory where the subvolume
+ * should be created.
+ * @name: Name of the subvolume to create.
+ * @flags: See btrfs_util_create_subvolume().
+ * @async_transid: See btrfs_util_create_subvolume().
+ * @qgroup_inherit: See btrfs_util_create_subvolume().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_create_subvolume_fd(int parent_fd,
+ const char *name,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit);
+
+/**
+ * BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE - Also snapshot subvolumes beneath the
+ * source subvolume onto the same location on the new snapshot.
+ *
+ * Note that this is currently implemented in userspace non-atomically. Because
+ * it modifies the newly-created snapshot, it cannot be combined with
+ * %BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY. It requires appropriate privilege
+ * (CAP_SYS_ADMIN).
+ */
+#define BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE (1 << 0)
+/**
+ * BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY - Create a read-only snapshot.
+ */
+#define BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY (1 << 1)
+#define BTRFS_UTIL_CREATE_SNAPSHOT_MASK ((1 << 2) - 1)
+
+/**
+ * btrfs_util_create_snapshot() - Create a new snapshot from a source subvolume
+ * path.
+ * @source: Path of the existing subvolume to snapshot.
+ * @path: Where to create the snapshot.
+ * @flags: Bitmask of BTRFS_UTIL_CREATE_SNAPSHOT_* flags.
+ * @async_transid: See btrfs_util_create_subvolume(). If
+ * %BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE was in @flags, then this will contain
+ * the largest transaction ID of all created subvolumes.
+ * @qgroup_inherit: See btrfs_util_create_subvolume().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_create_snapshot(const char *source,
+ const char *path, int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit);
+
+/**
+ * btrfs_util_create_snapshot_fd() - See btrfs_util_create_snapshot().
+ */
+enum btrfs_util_error btrfs_util_create_snapshot_fd(int fd, const char *path,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit);
+
+/**
+ * btrfs_util_create_snapshot_fd2() - Create a new snapshot from a source
+ * subvolume file descriptor and a target parent file descriptor and name.
+ * @fd: File descriptor of the existing subvolume to snapshot.
+ * @parent_fd: File descriptor of the parent directory where the snapshot should
+ * be created.
+ * @name: Name of the snapshot to create.
+ * @flags: See btrfs_util_create_snapshot().
+ * @async_transid: See btrfs_util_create_snapshot().
+ * @qgroup_inherit: See btrfs_util_create_snapshot().
+ */
+enum btrfs_util_error btrfs_util_create_snapshot_fd2(int fd, int parent_fd,
+ const char *name,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit);
+
+/**
+ * BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE - Delete subvolumes beneath the given
+ * subvolume before attempting to delete the given subvolume.
+ *
+ * If this flag is not used, deleting a subvolume with child subvolumes is an
+ * error. Note that this is currently implemented in userspace non-atomically.
+ * It requires appropriate privilege (CAP_SYS_ADMIN).
+ */
+#define BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE (1 << 0)
+#define BTRFS_UTIL_DELETE_SUBVOLUME_MASK ((1 << 1) - 1)
+
+/**
+ * btrfs_util_delete_subvolume() - Delete a subvolume or snapshot.
+ * @path: Path of the subvolume to delete.
+ * @flags: Bitmask of BTRFS_UTIL_DELETE_SUBVOLUME_* flags.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_delete_subvolume(const char *path, int flags);
+
+/**
+ * btrfs_util_delete_subvolume_fd() - Delete a subvolume or snapshot given its
+ * parent and name.
+ * @parent_fd: File descriptor of the subvolume's parent directory.
+ * @name: Name of the subvolume.
+ * @flags: See btrfs_util_delete_subvolume().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_delete_subvolume_fd(int parent_fd,
+ const char *name,
+ int flags);
+
+struct btrfs_util_subvolume_iterator;
+
+/**
+ * BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER - Iterate post-order. The default
+ * behavior is pre-order, e.g., foo will be yielded before foo/bar. If this flag
+ * is specified, foo/bar will be yielded before foo.
+ */
+#define BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER (1 << 0)
+#define BTRFS_UTIL_SUBVOLUME_ITERATOR_MASK ((1 << 1) - 1)
+
+/**
+ * btrfs_util_create_subvolume_iterator() - Create an iterator over subvolumes
+ * in a Btrfs filesystem.
+ * @path: Path in a Btrfs filesystem. This may be any path in the filesystem; it
+ * does not have to refer to a subvolume unless @top is zero.
+ * @top: List subvolumes beneath (but not including) the subvolume with this ID.
+ * If zero is given, the subvolume ID of @path is used. To list all subvolumes,
+ * pass %BTRFS_FS_TREE_OBJECTID (i.e., 5). The returned paths are relative to
+ * the subvolume with this ID.
+ * @flags: Bitmask of BTRFS_UTIL_SUBVOLUME_ITERATOR_* flags.
+ * @ret: Returned iterator.
+ *
+ * The returned iterator must be freed with
+ * btrfs_util_destroy_subvolume_iterator().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_create_subvolume_iterator(const char *path,
+ uint64_t top,
+ int flags,
+ struct btrfs_util_subvolume_iterator **ret);
+
+/**
+ * btrfs_util_create_subvolume_iterator_fd() - See
+ * btrfs_util_create_subvolume_iterator().
+ */
+enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd,
+ uint64_t top,
+ int flags,
+ struct btrfs_util_subvolume_iterator **ret);
+
+/**
+ * btrfs_util_destroy_subvolume_iterator() - Destroy a subvolume iterator
+ * previously created by btrfs_util_create_subvolume_iterator().
+ * @iter: Iterator to destroy.
+ */
+void btrfs_util_destroy_subvolume_iterator(struct btrfs_util_subvolume_iterator *iter);
+
+/**
+ * btrfs_util_subvolume_iterator_fd() - Get the file descriptor associated with
+ * a subvolume iterator.
+ * @iter: Iterator to get.
+ *
+ * This can be used to get the file descriptor opened by
+ * btrfs_util_create_subvolume_iterator() in order to use it for other
+ * functions.
+ *
+ * Return: File descriptor.
+ */
+int btrfs_util_subvolume_iterator_fd(const struct btrfs_util_subvolume_iterator *iter);
+
+/**
+ * btrfs_util_subvolume_iterator_next() - Get the next subvolume from a
+ * subvolume iterator.
+ * @iter: Subvolume iterator.
+ * @path_ret: Returned subvolume path, relative to the subvolume ID used to
+ * create the iterator. May be %NULL.
+ * Must be freed with free().
+ * @id_ret: Returned subvolume ID. May be %NULL.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, %BTRFS_UTIL_ERROR_STOP_ITERATION if there
+ * are no more subvolumes, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ uint64_t *id_ret);
+
+/**
+ * btrfs_util_subvolume_iterator_next_info() - Get information about the next
+ * subvolume for a subvolume iterator.
+ * @iter: Subvolume iterator.
+ * @path_ret: See btrfs_util_subvolume_iterator_next().
+ * @subvol: Returned subvolume information.
+ *
+ * This convenience function basically combines
+ * btrfs_util_subvolume_iterator_next() and btrfs_util_subvolume_info().
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: See btrfs_util_subvolume_iterator_next().
+ */
+enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ struct btrfs_util_subvolume_info *subvol);
+
+/**
+ * btrfs_util_deleted_subvolumes() - Get a list of subvolume which have been
+ * deleted but not yet cleaned up.
+ * @path: Path on a Btrfs filesystem.
+ * @ids: Returned array of subvolume IDs.
+ * @n: Returned number of IDs in the @ids array.
+ *
+ * This requires appropriate privilege (CAP_SYS_ADMIN).
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path,
+ uint64_t **ids,
+ size_t *n);
+
+/**
+ * btrfs_util_deleted_subvolumes_fd() - See btrfs_util_deleted_subvolumes().
+ */
+enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd, uint64_t **ids,
+ size_t *n);
+
+/**
+ * btrfs_util_create_qgroup_inherit() - Create a qgroup inheritance specifier
+ * for btrfs_util_create_subvolume() or btrfs_util_create_snapshot().
+ * @flags: Must be zero.
+ * @ret: Returned qgroup inheritance specifier.
+ *
+ * The returned structure must be freed with
+ * btrfs_util_destroy_qgroup_inherit().
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_create_qgroup_inherit(int flags,
+ struct btrfs_util_qgroup_inherit **ret);
+
+/**
+ * btrfs_util_destroy_qgroup_inherit() - Destroy a qgroup inheritance specifier
+ * previously created with btrfs_util_create_qgroup_inherit().
+ * @inherit: Specifier to destroy.
+ */
+void btrfs_util_destroy_qgroup_inherit(struct btrfs_util_qgroup_inherit *inherit);
+
+/**
+ * btrfs_util_qgroup_inherit_add_group() - Add inheritance from a qgroup to a
+ * qgroup inheritance specifier.
+ * @inherit: Specifier to modify. May be reallocated.
+ * @qgroupid: ID of qgroup to inherit from.
+ *
+ * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure.
+ */
+enum btrfs_util_error btrfs_util_qgroup_inherit_add_group(struct btrfs_util_qgroup_inherit **inherit,
+ uint64_t qgroupid);
+
+/**
+ * btrfs_util_qgroup_inherit_get_groups() - Get the qgroups a qgroup inheritance
+ * specifier contains.
+ * @inherit: Qgroup inheritance specifier.
+ * @groups: Returned array of qgroup IDs.
+ * @n: Returned number of entries in the @groups array.
+ */
+void btrfs_util_qgroup_inherit_get_groups(const struct btrfs_util_qgroup_inherit *inherit,
+ const uint64_t **groups, size_t *n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BTRFS_UTIL_H */
diff --git a/libbtrfsutil/btrfsutil_internal.h b/libbtrfsutil/btrfsutil_internal.h
new file mode 100644
index 00000000..8bbf01f4
--- /dev/null
+++ b/libbtrfsutil/btrfsutil_internal.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BTRFS_UTIL_INTERNAL_H
+#define BTRFS_UTIL_INTERNAL_H
+
+#include <asm/byteorder.h>
+
+#include "btrfsutil.h"
+#include "btrfs.h"
+#include "btrfs_tree.h"
+
+#define PUBLIC __attribute__((visibility("default")))
+
+#define le16_to_cpu __le16_to_cpu
+#define le32_to_cpu __le32_to_cpu
+#define le64_to_cpu __le64_to_cpu
+
+#define SAVE_ERRNO_AND_CLOSE(fd) do { \
+ int saved_errno = errno; \
+ \
+ close(fd); \
+ errno = saved_errno; \
+} while (0)
+
+#endif /* BTRFS_UTIL_INTERNAL_H */
diff --git a/libbtrfsutil/errors.c b/libbtrfsutil/errors.c
new file mode 100644
index 00000000..634edc65
--- /dev/null
+++ b/libbtrfsutil/errors.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+
+#include "btrfsutil_internal.h"
+
+static const char * const error_messages[] = {
+ [BTRFS_UTIL_OK] = "Success",
+ [BTRFS_UTIL_ERROR_STOP_ITERATION] = "Stop iteration",
+ [BTRFS_UTIL_ERROR_NO_MEMORY] = "Cannot allocate memory",
+ [BTRFS_UTIL_ERROR_INVALID_ARGUMENT] = "Invalid argument",
+ [BTRFS_UTIL_ERROR_NOT_BTRFS] = "Not a Btrfs filesystem",
+ [BTRFS_UTIL_ERROR_NOT_SUBVOLUME] = "Not a Btrfs subvolume",
+ [BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND] = "Subvolume not found",
+ [BTRFS_UTIL_ERROR_OPEN_FAILED] = "Could not open",
+ [BTRFS_UTIL_ERROR_RMDIR_FAILED] = "Could not rmdir",
+ [BTRFS_UTIL_ERROR_UNLINK_FAILED] = "Could not unlink",
+ [BTRFS_UTIL_ERROR_STAT_FAILED] = "Could not stat",
+ [BTRFS_UTIL_ERROR_STATFS_FAILED] = "Could not statfs",
+ [BTRFS_UTIL_ERROR_SEARCH_FAILED] = "Could not search B-tree",
+ [BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED] = "Could not lookup inode",
+ [BTRFS_UTIL_ERROR_SUBVOL_GETFLAGS_FAILED] = "Could not get subvolume flags",
+ [BTRFS_UTIL_ERROR_SUBVOL_SETFLAGS_FAILED] = "Could not set subvolume flags",
+ [BTRFS_UTIL_ERROR_SUBVOL_CREATE_FAILED] = "Could not create subvolume",
+ [BTRFS_UTIL_ERROR_SNAP_CREATE_FAILED] = "Could not create snapshot",
+ [BTRFS_UTIL_ERROR_SNAP_DESTROY_FAILED] = "Could not destroy subvolume/snapshot",
+ [BTRFS_UTIL_ERROR_DEFAULT_SUBVOL_FAILED] = "Could not set default subvolume",
+ [BTRFS_UTIL_ERROR_SYNC_FAILED] = "Could not sync filesystem",
+ [BTRFS_UTIL_ERROR_START_SYNC_FAILED] = "Could not start filesystem sync",
+ [BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED] = "Could not wait for filesystem sync",
+};
+
+PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err)
+{
+ if (err < 0 || err >= sizeof(error_messages) / sizeof(error_messages[0]))
+ return NULL;
+ return error_messages[err];
+}
diff --git a/libbtrfsutil/filesystem.c b/libbtrfsutil/filesystem.c
new file mode 100644
index 00000000..9c02b124
--- /dev/null
+++ b/libbtrfsutil/filesystem.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "btrfsutil_internal.h"
+
+PUBLIC enum btrfs_util_error btrfs_util_sync(const char *path)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_sync_fd(fd);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_sync_fd(int fd)
+{
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_SYNC, NULL);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SYNC_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_start_sync(const char *path,
+ uint64_t *transid)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_start_sync_fd(fd, transid);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_start_sync_fd(int fd, uint64_t *transid)
+{
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_START_SYNC, transid);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_START_SYNC_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_wait_sync(const char *path,
+ uint64_t transid)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_wait_sync_fd(fd, transid);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_wait_sync_fd(int fd, uint64_t transid)
+{
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_WAIT_SYNC, &transid);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
diff --git a/libbtrfsutil/python/.gitignore b/libbtrfsutil/python/.gitignore
new file mode 100644
index 00000000..d050ff7c
--- /dev/null
+++ b/libbtrfsutil/python/.gitignore
@@ -0,0 +1,7 @@
+__pycache__
+*.pyc
+/btrfsutil.egg-info
+/btrfsutil*.so
+/build
+/constants.c
+/dist
diff --git a/libbtrfsutil/python/btrfsutilpy.h b/libbtrfsutil/python/btrfsutilpy.h
new file mode 100644
index 00000000..af3a6edf
--- /dev/null
+++ b/libbtrfsutil/python/btrfsutilpy.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BTRFSUTILPY_H
+#define BTRFSUTILPY_H
+
+#define PY_SSIZE_T_CLEAN
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <Python.h>
+#include "structmember.h"
+
+#include <btrfsutil.h>
+
+typedef struct {
+ PyObject_HEAD
+ struct btrfs_util_qgroup_inherit *inherit;
+} QgroupInherit;
+
+extern PyTypeObject BtrfsUtilError_type;
+extern PyStructSequence_Desc SubvolumeInfo_desc;
+extern PyTypeObject SubvolumeInfo_type;
+extern PyTypeObject SubvolumeIterator_type;
+extern PyTypeObject QgroupInherit_type;
+
+/*
+ * Helpers for path arguments based on posixmodule.c in CPython.
+ */
+struct path_arg {
+ bool allow_fd;
+ char *path;
+ int fd;
+ Py_ssize_t length;
+ PyObject *object;
+ PyObject *cleanup;
+};
+int path_converter(PyObject *o, void *p);
+void path_cleanup(struct path_arg *path);
+
+PyObject *list_from_uint64_array(const uint64_t *arr, size_t n);
+
+void SetFromBtrfsUtilError(enum btrfs_util_error err);
+void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
+ struct path_arg *path);
+void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
+ struct path_arg *path1,
+ struct path_arg *path2);
+
+PyObject *filesystem_sync(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *start_sync(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *wait_sync(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *subvolume_info(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *get_subvolume_read_only(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *set_subvolume_read_only(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *get_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *set_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *delete_subvolume(PyObject *self, PyObject *args, PyObject *kwds);
+PyObject *deleted_subvolumes(PyObject *self, PyObject *args, PyObject *kwds);
+
+void add_module_constants(PyObject *m);
+
+#endif /* BTRFSUTILPY_H */
diff --git a/libbtrfsutil/python/error.c b/libbtrfsutil/python/error.c
new file mode 100644
index 00000000..0876c9b4
--- /dev/null
+++ b/libbtrfsutil/python/error.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "btrfsutilpy.h"
+
+typedef struct {
+ PyOSErrorObject os_error;
+ PyObject *btrfsutilerror;
+} BtrfsUtilError;
+
+void SetFromBtrfsUtilError(enum btrfs_util_error err)
+{
+ SetFromBtrfsUtilErrorWithPaths(err, NULL, NULL);
+}
+
+void SetFromBtrfsUtilErrorWithPath(enum btrfs_util_error err,
+ struct path_arg *path1)
+{
+ SetFromBtrfsUtilErrorWithPaths(err, path1, NULL);
+}
+
+void SetFromBtrfsUtilErrorWithPaths(enum btrfs_util_error err,
+ struct path_arg *path1,
+ struct path_arg *path2)
+{
+ PyObject *strobj, *args, *exc;
+ int i = errno;
+ const char *str1 = btrfs_util_strerror(err), *str2 = strerror(i);
+
+ if (str1 && str2 && strcmp(str1, str2) != 0) {
+ strobj = PyUnicode_FromFormat("%s: %s", str1, str2);
+ } else if (str1) {
+ strobj = PyUnicode_FromString(str1);
+ } else if (str2) {
+ strobj = PyUnicode_FromString(str2);
+ } else {
+ Py_INCREF(Py_None);
+ strobj = Py_None;
+ }
+ if (strobj == NULL)
+ return;
+
+ args = Py_BuildValue("iOOOOi", i, strobj,
+ path1 ? path1->object : Py_None, Py_None,
+ path2 ? path2->object : Py_None, (int)err);
+ Py_DECREF(strobj);
+ if (args == NULL)
+ return;
+
+ exc = PyObject_CallObject((PyObject *)&BtrfsUtilError_type, args);
+ Py_DECREF(args);
+ if (exc == NULL)
+ return;
+
+ PyErr_SetObject((PyObject *)&BtrfsUtilError_type, exc);
+ Py_DECREF(exc);
+}
+
+static int BtrfsUtilError_clear(BtrfsUtilError *self)
+{
+ Py_CLEAR(self->btrfsutilerror);
+ return Py_TYPE(self)->tp_base->tp_clear((PyObject *)self);
+}
+
+static void BtrfsUtilError_dealloc(BtrfsUtilError *self)
+{
+ PyObject_GC_UnTrack(self);
+ BtrfsUtilError_clear(self);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static int BtrfsUtilError_traverse(BtrfsUtilError *self, visitproc visit,
+ void *arg)
+{
+ Py_VISIT(self->btrfsutilerror);
+ return Py_TYPE(self)->tp_base->tp_traverse((PyObject *)self, visit, arg);
+}
+
+static PyObject *BtrfsUtilError_new(PyTypeObject *type, PyObject *args,
+ PyObject *kwds)
+{
+ BtrfsUtilError *self;
+ PyObject *oserror_args = args;
+
+ if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
+ oserror_args = PyTuple_GetSlice(args, 0, 5);
+ if (oserror_args == NULL)
+ return NULL;
+ }
+
+ self = (BtrfsUtilError *)type->tp_base->tp_new(type, oserror_args,
+ kwds);
+ if (oserror_args != args)
+ Py_DECREF(oserror_args);
+ if (self == NULL)
+ return NULL;
+
+ if (PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 6) {
+ self->btrfsutilerror = PyTuple_GET_ITEM(args, 5);
+ Py_INCREF(self->btrfsutilerror);
+ }
+
+ return (PyObject *)self;
+}
+
+static PyObject *BtrfsUtilError_str(BtrfsUtilError *self)
+{
+#define OR_NONE(x) ((x) ? (x) : Py_None)
+ if (self->btrfsutilerror) {
+ if (self->os_error.filename) {
+ if (self->os_error.filename2) {
+ return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R -> %R",
+ OR_NONE(self->btrfsutilerror),
+ OR_NONE(self->os_error.myerrno),
+ OR_NONE(self->os_error.strerror),
+ self->os_error.filename,
+ self->os_error.filename2);
+ } else {
+ return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S: %R",
+ OR_NONE(self->btrfsutilerror),
+ OR_NONE(self->os_error.myerrno),
+ OR_NONE(self->os_error.strerror),
+ self->os_error.filename);
+ }
+ }
+ if (self->os_error.myerrno && self->os_error.strerror) {
+ return PyUnicode_FromFormat("[BtrfsUtilError %S Errno %S] %S",
+ self->btrfsutilerror,
+ self->os_error.myerrno,
+ self->os_error.strerror);
+ }
+ }
+ return Py_TYPE(self)->tp_base->tp_str((PyObject *)self);
+#undef OR_NONE
+}
+
+static PyMemberDef BtrfsUtilError_members[] = {
+ {"btrfsutilerror", T_OBJECT,
+ offsetof(BtrfsUtilError, btrfsutilerror), 0,
+ "btrfsutil error code"},
+ {},
+};
+
+#define BtrfsUtilError_DOC \
+ "Btrfs operation error."
+
+PyTypeObject BtrfsUtilError_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "btrfsutil.BtrfsUtilError", /* tp_name */
+ sizeof(BtrfsUtilError), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)BtrfsUtilError_dealloc, /* tp_dealloc */
+ NULL, /* tp_print */
+ NULL, /* tp_getattr */
+ NULL, /* tp_setattr */
+ NULL, /* tp_as_async */
+ NULL, /* tp_repr */
+ NULL, /* tp_as_number */
+ NULL, /* tp_as_sequence */
+ NULL, /* tp_as_mapping */
+ NULL, /* tp_hash */
+ NULL, /* tp_call */
+ (reprfunc)BtrfsUtilError_str, /* tp_str */
+ NULL, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ BtrfsUtilError_DOC, /* tp_doc */
+ (traverseproc)BtrfsUtilError_traverse, /* tp_traverse */
+ (inquiry)BtrfsUtilError_clear, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ NULL, /* tp_methods */
+ BtrfsUtilError_members, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ offsetof(BtrfsUtilError, os_error.dict), /* tp_dictoffset */
+ NULL, /* tp_init */
+ NULL, /* tp_alloc */
+ BtrfsUtilError_new, /* tp_new */
+};
diff --git a/libbtrfsutil/python/filesystem.c b/libbtrfsutil/python/filesystem.c
new file mode 100644
index 00000000..627ed193
--- /dev/null
+++ b/libbtrfsutil/python/filesystem.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "btrfsutilpy.h"
+
+PyObject *filesystem_sync(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:sync", keywords,
+ &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_sync(path.path);
+ else
+ err = btrfs_util_sync_fd(path.fd);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ Py_RETURN_NONE;
+}
+
+PyObject *start_sync(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ uint64_t transid;
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:start_sync", keywords,
+ &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_start_sync(path.path, &transid);
+ else
+ err = btrfs_util_start_sync_fd(path.fd, &transid);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ return PyLong_FromUnsignedLongLong(transid);
+}
+
+PyObject *wait_sync(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "transid", NULL};
+ struct path_arg path = {.allow_fd = true};
+ unsigned long long transid = 0;
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:wait_sync", keywords,
+ &path_converter, &path, &transid))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_wait_sync(path.path, transid);
+ else
+ err = btrfs_util_wait_sync_fd(path.fd, transid);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ Py_RETURN_NONE;
+}
diff --git a/libbtrfsutil/python/module.c b/libbtrfsutil/python/module.c
new file mode 100644
index 00000000..2dbdc7be
--- /dev/null
+++ b/libbtrfsutil/python/module.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "btrfsutilpy.h"
+
+static int fd_converter(PyObject *o, void *p)
+{
+ int *fd = p;
+ long tmp;
+ int overflow;
+
+ tmp = PyLong_AsLongAndOverflow(o, &overflow);
+ if (tmp == -1 && PyErr_Occurred())
+ return 0;
+ if (overflow > 0 || tmp > INT_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "fd is greater than maximum");
+ return 0;
+ }
+ if (overflow < 0 || tmp < 0) {
+ PyErr_SetString(PyExc_ValueError, "fd is negative");
+ return 0;
+ }
+ *fd = tmp;
+ return 1;
+}
+
+int path_converter(PyObject *o, void *p)
+{
+ struct path_arg *path = p;
+ int is_index, is_bytes, is_unicode;
+ PyObject *bytes = NULL;
+ Py_ssize_t length = 0;
+ char *tmp;
+
+ if (o == NULL) {
+ path_cleanup(p);
+ return 1;
+ }
+
+ path->object = path->cleanup = NULL;
+ Py_INCREF(o);
+
+ path->fd = -1;
+
+ is_index = path->allow_fd && PyIndex_Check(o);
+ is_bytes = PyBytes_Check(o);
+ is_unicode = PyUnicode_Check(o);
+
+ if (!is_index && !is_bytes && !is_unicode) {
+ _Py_IDENTIFIER(__fspath__);
+ PyObject *func;
+
+ func = _PyObject_LookupSpecial(o, &PyId___fspath__);
+ if (func == NULL)
+ goto err_format;
+ Py_DECREF(o);
+ o = PyObject_CallFunctionObjArgs(func, NULL);
+ Py_DECREF(func);
+ if (o == NULL)
+ return 0;
+ is_bytes = PyBytes_Check(o);
+ is_unicode = PyUnicode_Check(o);
+ }
+
+ if (is_unicode) {
+ if (!PyUnicode_FSConverter(o, &bytes))
+ goto err;
+ } else if (is_bytes) {
+ bytes = o;
+ Py_INCREF(bytes);
+ } else if (is_index) {
+ if (!fd_converter(o, &path->fd))
+ goto err;
+ path->path = NULL;
+ goto out;
+ } else {
+err_format:
+ PyErr_Format(PyExc_TypeError, "expected %s, not %s",
+ path->allow_fd ? "string, bytes, os.PathLike, or integer" :
+ "string, bytes, or os.PathLike",
+ Py_TYPE(o)->tp_name);
+ goto err;
+ }
+
+ length = PyBytes_GET_SIZE(bytes);
+ tmp = PyBytes_AS_STRING(bytes);
+ if ((size_t)length != strlen(tmp)) {
+ PyErr_SetString(PyExc_TypeError,
+ "path has embedded nul character");
+ goto err;
+ }
+
+ path->path = tmp;
+ if (bytes == o)
+ Py_DECREF(bytes);
+ else
+ path->cleanup = bytes;
+ path->fd = -1;
+
+out:
+ path->length = length;
+ path->object = o;
+ return Py_CLEANUP_SUPPORTED;
+
+err:
+ Py_XDECREF(o);
+ Py_XDECREF(bytes);
+ return 0;
+}
+
+PyObject *list_from_uint64_array(const uint64_t *arr, size_t n)
+{
+ PyObject *ret;
+ size_t i;
+
+ ret = PyList_New(n);
+ if (!ret)
+ return NULL;
+
+ for (i = 0; i < n; i++) {
+ PyObject *tmp;
+
+ tmp = PyLong_FromUnsignedLongLong(arr[i]);
+ if (!tmp) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ PyList_SET_ITEM(ret, i, tmp);
+ }
+
+ return ret;
+}
+
+void path_cleanup(struct path_arg *path)
+{
+ Py_CLEAR(path->object);
+ Py_CLEAR(path->cleanup);
+}
+
+static PyMethodDef btrfsutil_methods[] = {
+ {"sync", (PyCFunction)filesystem_sync,
+ METH_VARARGS | METH_KEYWORDS,
+ "sync(path)\n\n"
+ "Sync a specific Btrfs filesystem.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"start_sync", (PyCFunction)start_sync,
+ METH_VARARGS | METH_KEYWORDS,
+ "start_sync(path) -> int\n\n"
+ "Start a sync on a specific Btrfs filesystem and return the\n"
+ "transaction ID.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"wait_sync", (PyCFunction)wait_sync,
+ METH_VARARGS | METH_KEYWORDS,
+ "wait_sync(path, transid=0)\n\n"
+ "Wait for a transaction to sync.\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "transid -- int transaction ID to wait for, or zero for the current\n"
+ "transaction"},
+ {"is_subvolume", (PyCFunction)is_subvolume,
+ METH_VARARGS | METH_KEYWORDS,
+ "is_subvolume(path) -> bool\n\n"
+ "Get whether a file is a subvolume.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"subvolume_id", (PyCFunction)subvolume_id,
+ METH_VARARGS | METH_KEYWORDS,
+ "subvolume_id(path) -> int\n\n"
+ "Get the ID of the subvolume containing a file.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"subvolume_path", (PyCFunction)subvolume_path,
+ METH_VARARGS | METH_KEYWORDS,
+ "subvolume_path(path, id=0) -> int\n\n"
+ "Get the path of a subvolume relative to the filesystem root.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "id -- if not zero, instead of returning the subvolume path of the\n"
+ "given path, return the path of the subvolume with this ID"},
+ {"subvolume_info", (PyCFunction)subvolume_info,
+ METH_VARARGS | METH_KEYWORDS,
+ "subvolume_info(path, id=0) -> SubvolumeInfo\n\n"
+ "Get information about a subvolume.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "id -- if not zero, instead of returning information about the\n"
+ "given path, return information about the subvolume with this ID"},
+ {"get_subvolume_read_only", (PyCFunction)get_subvolume_read_only,
+ METH_VARARGS | METH_KEYWORDS,
+ "get_subvolume_read_only(path) -> bool\n\n"
+ "Get whether a subvolume is read-only.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"set_subvolume_read_only", (PyCFunction)set_subvolume_read_only,
+ METH_VARARGS | METH_KEYWORDS,
+ "set_subvolume_read_only(path, read_only=True)\n\n"
+ "Set whether a subvolume is read-only.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "read_only -- bool flag value"},
+ {"get_default_subvolume", (PyCFunction)get_default_subvolume,
+ METH_VARARGS | METH_KEYWORDS,
+ "get_default_subvolume(path) -> int\n\n"
+ "Get the ID of the default subvolume of a filesystem.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {"set_default_subvolume", (PyCFunction)set_default_subvolume,
+ METH_VARARGS | METH_KEYWORDS,
+ "set_default_subvolume(path, id=0)\n\n"
+ "Set the default subvolume of a filesystem.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor\n"
+ "id -- if not zero, set the default subvolume to the subvolume with\n"
+ "this ID instead of the given path"},
+ {"create_subvolume", (PyCFunction)create_subvolume,
+ METH_VARARGS | METH_KEYWORDS,
+ "create_subvolume(path, async=False)\n\n"
+ "Create a new subvolume.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, or path-like object\n"
+ "async -- create the subvolume without waiting for it to commit to\n"
+ "disk and return the transaction ID"},
+ {"create_snapshot", (PyCFunction)create_snapshot,
+ METH_VARARGS | METH_KEYWORDS,
+ "create_snapshot(source, path, recursive=False, read_only=False, async=False)\n\n"
+ "Create a new snapshot.\n\n"
+ "Arguments:\n"
+ "source -- string, bytes, path-like object, or open file descriptor\n"
+ "path -- string, bytes, or path-like object\n"
+ "recursive -- also snapshot child subvolumes\n"
+ "read_only -- create a read-only snapshot\n"
+ "async -- create the subvolume without waiting for it to commit to\n"
+ "disk and return the transaction ID"},
+ {"delete_subvolume", (PyCFunction)delete_subvolume,
+ METH_VARARGS | METH_KEYWORDS,
+ "delete_subvolume(path, recursive=False)\n\n"
+ "Delete a subvolume or snapshot.\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, or path-like object\n"
+ "recursive -- if the given subvolume has child subvolumes, delete\n"
+ "them instead of failing"},
+ {"deleted_subvolumes", (PyCFunction)deleted_subvolumes,
+ METH_VARARGS | METH_KEYWORDS,
+ "deleted_subvolumes(path)\n\n"
+ "Get the list of subvolume IDs which have been deleted but not yet\n"
+ "cleaned up\n\n"
+ "Arguments:\n"
+ "path -- string, bytes, path-like object, or open file descriptor"},
+ {},
+};
+
+static struct PyModuleDef btrfsutilmodule = {
+ PyModuleDef_HEAD_INIT,
+ "btrfsutil",
+ "Library for managing Btrfs filesystems",
+ -1,
+ btrfsutil_methods,
+};
+
+PyMODINIT_FUNC
+PyInit_btrfsutil(void)
+{
+ PyObject *m;
+
+ BtrfsUtilError_type.tp_base = (PyTypeObject *)PyExc_OSError;
+ if (PyType_Ready(&BtrfsUtilError_type) < 0)
+ return NULL;
+
+ if (PyStructSequence_InitType2(&SubvolumeInfo_type, &SubvolumeInfo_desc) < 0)
+ return NULL;
+
+ SubvolumeIterator_type.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&SubvolumeIterator_type) < 0)
+ return NULL;
+
+ QgroupInherit_type.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&QgroupInherit_type) < 0)
+ return NULL;
+
+ m = PyModule_Create(&btrfsutilmodule);
+ if (!m)
+ return NULL;
+
+ Py_INCREF(&BtrfsUtilError_type);
+ PyModule_AddObject(m, "BtrfsUtilError",
+ (PyObject *)&BtrfsUtilError_type);
+
+ Py_INCREF(&SubvolumeInfo_type);
+ PyModule_AddObject(m, "SubvolumeInfo", (PyObject *)&SubvolumeInfo_type);
+
+ Py_INCREF(&SubvolumeIterator_type);
+ PyModule_AddObject(m, "SubvolumeIterator",
+ (PyObject *)&SubvolumeIterator_type);
+
+ Py_INCREF(&QgroupInherit_type);
+ PyModule_AddObject(m, "QgroupInherit",
+ (PyObject *)&QgroupInherit_type);
+
+ add_module_constants(m);
+
+ return m;
+}
diff --git a/libbtrfsutil/python/qgroup.c b/libbtrfsutil/python/qgroup.c
new file mode 100644
index 00000000..44ac5ebc
--- /dev/null
+++ b/libbtrfsutil/python/qgroup.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "btrfsutilpy.h"
+
+static void QgroupInherit_dealloc(QgroupInherit *self)
+{
+ btrfs_util_destroy_qgroup_inherit(self->inherit);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static int QgroupInherit_init(QgroupInherit *self, PyObject *args,
+ PyObject *kwds)
+{
+ static char *keywords[] = {NULL};
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, ":QgroupInherit",
+ keywords))
+ return -1;
+
+ err = btrfs_util_create_qgroup_inherit(0, &self->inherit);
+ if (err) {
+ SetFromBtrfsUtilError(err);
+ return -1;
+ }
+
+ return 0;
+}
+
+static PyObject *QgroupInherit_getattro(QgroupInherit *self, PyObject *nameobj)
+{
+ const char *name = "";
+
+ if (PyUnicode_Check(nameobj)) {
+ name = PyUnicode_AsUTF8(nameobj);
+ if (!name)
+ return NULL;
+ }
+
+ if (strcmp(name, "groups") == 0) {
+ const uint64_t *arr;
+ size_t n;
+
+ btrfs_util_qgroup_inherit_get_groups(self->inherit, &arr, &n);
+
+ return list_from_uint64_array(arr, n);
+ } else {
+ return PyObject_GenericGetAttr((PyObject *)self, nameobj);
+ }
+}
+
+static PyObject *QgroupInherit_add_group(QgroupInherit *self, PyObject *args,
+ PyObject *kwds)
+{
+ static char *keywords[] = {"qgroupid", NULL};
+ enum btrfs_util_error err;
+ uint64_t qgroupid;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "K:add_group", keywords,
+ &qgroupid))
+ return NULL;
+
+ err = btrfs_util_qgroup_inherit_add_group(&self->inherit, qgroupid);
+ if (err) {
+ SetFromBtrfsUtilError(err);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef QgroupInherit_methods[] = {
+ {"add_group", (PyCFunction)QgroupInherit_add_group,
+ METH_VARARGS | METH_KEYWORDS,
+ "add_group(qgroupid)\n\n"
+ "Add a qgroup to inherit from.\n\n"
+ "Arguments:\n"
+ "qgroupid -- ID of qgroup to add"},
+ {},
+};
+
+#define QgroupInherit_DOC \
+ "QgroupInherit() -> new qgroup inheritance specifier\n\n" \
+ "Create a new object which specifies what qgroups to inherit\n" \
+ "from for create_subvolume() and create_snapshot()"
+
+PyTypeObject QgroupInherit_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "btrfsutil.QgroupInherit", /* tp_name */
+ sizeof(QgroupInherit), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)QgroupInherit_dealloc, /* tp_dealloc */
+ NULL, /* tp_print */
+ NULL, /* tp_getattr */
+ NULL, /* tp_setattr */
+ NULL, /* tp_as_async */
+ NULL, /* tp_repr */
+ NULL, /* tp_as_number */
+ NULL, /* tp_as_sequence */
+ NULL, /* tp_as_mapping */
+ NULL, /* tp_hash */
+ NULL, /* tp_call */
+ NULL, /* tp_str */
+ (getattrofunc)QgroupInherit_getattro, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ QgroupInherit_DOC, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ QgroupInherit_methods, /* tp_methods */
+ NULL, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)QgroupInherit_init, /* tp_init */
+};
diff --git a/libbtrfsutil/python/setup.py b/libbtrfsutil/python/setup.py
new file mode 100755
index 00000000..1cd3bc00
--- /dev/null
+++ b/libbtrfsutil/python/setup.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2018 Facebook
+#
+# This file is part of libbtrfsutil.
+#
+# libbtrfsutil is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# libbtrfsutil is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import os.path
+from setuptools import setup, Extension
+from setuptools.command.build_ext import build_ext
+import subprocess
+
+
+def get_version():
+ with open('../btrfsutil.h', 'r') as f:
+ btrfsutil_h = f.read()
+ major = re.search(r'^#define BTRFS_UTIL_VERSION_MAJOR ([0-9]+)$',
+ btrfsutil_h, flags=re.MULTILINE).group(1)
+ minor = re.search(r'^#define BTRFS_UTIL_VERSION_MINOR ([0-9]+)$',
+ btrfsutil_h, flags=re.MULTILINE).group(1)
+ patch = re.search(r'^#define BTRFS_UTIL_VERSION_PATCH ([0-9]+)$',
+ btrfsutil_h, flags=re.MULTILINE).group(1)
+ return major + '.' + minor + '.' + patch
+
+
+def out_of_date(dependencies, target):
+ dependency_mtimes = [os.path.getmtime(dependency) for dependency in dependencies]
+ try:
+ target_mtime = os.path.getmtime(target)
+ except OSError:
+ return True
+ return any(dependency_mtime >= target_mtime for dependency_mtime in dependency_mtimes)
+
+
+def gen_constants():
+ with open('../btrfsutil.h', 'r') as f:
+ btrfsutil_h = f.read()
+
+ constants = re.findall(
+ r'^\s*(BTRFS_UTIL_ERROR_[a-zA-Z0-9_]+)',
+ btrfsutil_h, flags=re.MULTILINE)
+
+ with open('constants.c', 'w') as f:
+ f.write("""\
+#include <btrfsutil.h>
+#include "btrfsutilpy.h"
+
+void add_module_constants(PyObject *m)
+{
+""")
+ for constant in constants:
+ assert constant.startswith('BTRFS_UTIL_')
+ name = constant[len('BTRFS_UTIL_'):]
+ f.write('\tPyModule_AddIntConstant(m, "{}", {});\n'.format(name, constant))
+ f.write("""\
+}
+""")
+
+
+class my_build_ext(build_ext):
+ def run(self):
+ if out_of_date(['../btrfsutil.h'], 'constants.c'):
+ try:
+ gen_constants()
+ except Exception as e:
+ try:
+ os.remove('constants.c')
+ except OSError:
+ pass
+ raise e
+ super().run()
+
+
+module = Extension(
+ name='btrfsutil',
+ sources=[
+ 'constants.c',
+ 'error.c',
+ 'filesystem.c',
+ 'module.c',
+ 'qgroup.c',
+ 'subvolume.c',
+ ],
+ include_dirs=['..'],
+ library_dirs=['../..'],
+ libraries=['btrfsutil'],
+)
+
+setup(
+ name='btrfsutil',
+ version=get_version(),
+ description='Library for managing Btrfs filesystems',
+ url='https://github.com/kdave/btrfs-progs',
+ license='LGPLv3',
+ cmdclass={'build_ext': my_build_ext},
+ ext_modules=[module],
+)
diff --git a/libbtrfsutil/python/subvolume.c b/libbtrfsutil/python/subvolume.c
new file mode 100644
index 00000000..069e606b
--- /dev/null
+++ b/libbtrfsutil/python/subvolume.c
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "btrfsutilpy.h"
+
+PyObject *is_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:is_subvolume",
+ keywords, &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_is_subvolume(path.path);
+ else
+ err = btrfs_util_is_subvolume_fd(path.fd);
+ if (err == BTRFS_UTIL_OK) {
+ path_cleanup(&path);
+ Py_RETURN_TRUE;
+ } else if (err == BTRFS_UTIL_ERROR_NOT_BTRFS ||
+ err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) {
+ path_cleanup(&path);
+ Py_RETURN_FALSE;
+ } else {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+}
+
+PyObject *subvolume_id(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ uint64_t id;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:subvolume_id",
+ keywords, &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_subvolume_id(path.path, &id);
+ else
+ err = btrfs_util_subvolume_id_fd(path.fd, &id);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ return PyLong_FromUnsignedLongLong(id);
+}
+
+PyObject *subvolume_path(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "id", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ uint64_t id = 0;
+ char *subvol_path;
+ PyObject *ret;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:subvolume_path",
+ keywords, &path_converter, &path, &id))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_subvolume_path(path.path, id, &subvol_path);
+ else
+ err = btrfs_util_subvolume_path_fd(path.fd, id, &subvol_path);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+
+ ret = PyUnicode_DecodeFSDefault(subvol_path);
+ free(subvol_path);
+ return ret;
+}
+
+static PyObject *subvolume_info_to_object(const struct btrfs_util_subvolume_info *subvol)
+{
+ PyObject *ret, *tmp;
+
+ ret = PyStructSequence_New(&SubvolumeInfo_type);
+ if (ret == NULL)
+ return NULL;
+
+#define SET_UINT64(i, field) \
+ tmp = PyLong_FromUnsignedLongLong(subvol->field); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+#define SET_UUID(i, field) \
+ tmp = PyBytes_FromStringAndSize((char *)subvol->field, 16); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+#define SET_TIME(i, field) \
+ tmp = PyFloat_FromDouble(subvol->field.tv_sec + \
+ subvol->field.tv_nsec / 1000000000); \
+ if (tmp == NULL) { \
+ Py_DECREF(ret); \
+ return ret; \
+ } \
+ PyStructSequence_SET_ITEM(ret, i, tmp);
+
+ SET_UINT64(0, id);
+ SET_UINT64(1, parent_id);
+ SET_UINT64(2, dir_id);
+ SET_UINT64(3, flags);
+ SET_UUID(4, uuid);
+ SET_UUID(5, parent_uuid);
+ SET_UUID(6, received_uuid);
+ SET_UINT64(7, generation);
+ SET_UINT64(8, ctransid);
+ SET_UINT64(9, otransid);
+ SET_UINT64(10, stransid);
+ SET_UINT64(11, rtransid);
+ SET_TIME(12, ctime);
+ SET_TIME(13, otime);
+ SET_TIME(14, stime);
+ SET_TIME(15, rtime);
+
+#undef SET_TIME
+#undef SET_UUID
+#undef SET_UINT64
+
+ return ret;
+}
+
+PyObject *subvolume_info(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "id", NULL};
+ struct path_arg path = {.allow_fd = true};
+ struct btrfs_util_subvolume_info subvol;
+ enum btrfs_util_error err;
+ uint64_t id = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:subvolume_info",
+ keywords, &path_converter, &path, &id))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_subvolume_info(path.path, id, &subvol);
+ else
+ err = btrfs_util_subvolume_info_fd(path.fd, id, &subvol);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+
+ return subvolume_info_to_object(&subvol);
+}
+
+static PyStructSequence_Field SubvolumeInfo_fields[] = {
+ {"id", "int ID of this subvolume"},
+ {"parent_id", "int ID of the subvolume containing this subvolume"},
+ {"dir_id", "int inode number of the directory containing this subvolume"},
+ {"flags", "int root item flags"},
+ {"uuid", "bytes UUID of this subvolume"},
+ {"parent_uuid", "bytes UUID of the subvolume this is a snapshot of"},
+ {"received_uuid", "bytes UUID of the subvolume this was received from"},
+ {"generation", "int transaction ID of the subvolume root"},
+ {"ctransid", "int transaction ID when an inode was last changed"},
+ {"otransid", "int transaction ID when this subvolume was created"},
+ {"stransid", "int transaction ID of the sent subvolume this subvolume was received from"},
+ {"rtransid", "int transaction ID when this subvolume was received"},
+ {"ctime", "float time when an inode was last changed"},
+ {"otime", "float time when this subvolume was created"},
+ {"stime", "float time, usually zero"},
+ {"rtime", "float time when this subvolume was received"},
+ {},
+};
+
+PyStructSequence_Desc SubvolumeInfo_desc = {
+ "btrfsutil.SubvolumeInfo",
+ "Information about a Btrfs subvolume.",
+ SubvolumeInfo_fields,
+ 14,
+};
+
+PyTypeObject SubvolumeInfo_type;
+
+PyObject *get_subvolume_read_only(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ bool read_only;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O&:get_subvolume_read_only",
+ keywords, &path_converter, &path))
+ return NULL;
+
+ if (path.path) {
+ err = btrfs_util_get_subvolume_read_only(path.path, &read_only);
+ } else {
+ err = btrfs_util_get_subvolume_read_only_fd(path.fd,
+ &read_only);
+ }
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ return PyBool_FromLong(read_only);
+}
+
+PyObject *set_subvolume_read_only(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "read_only", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ int read_only = 1;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O&|p:set_subvolume_read_only",
+ keywords, &path_converter, &path,
+ &read_only))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_set_subvolume_read_only(path.path, read_only);
+ else
+ err = btrfs_util_set_subvolume_read_only_fd(path.fd, read_only);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ Py_RETURN_NONE;
+}
+
+PyObject *get_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ uint64_t id;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:get_default_subvolume",
+ keywords, &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_get_default_subvolume(path.path, &id);
+ else
+ err = btrfs_util_get_default_subvolume_fd(path.fd, &id);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ return PyLong_FromUnsignedLongLong(id);
+}
+
+PyObject *set_default_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "id", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ uint64_t id = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|K:set_default_subvolume",
+ keywords, &path_converter, &path, &id))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_set_default_subvolume(path.path, id);
+ else
+ err = btrfs_util_set_default_subvolume_fd(path.fd, id);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ Py_RETURN_NONE;
+}
+
+PyObject *create_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "async", "qgroup_inherit", NULL};
+ struct path_arg path = {.allow_fd = false};
+ enum btrfs_util_error err;
+ int async = 0;
+ QgroupInherit *inherit = NULL;
+ uint64_t transid;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|pO!:create_subvolume",
+ keywords, &path_converter, &path,
+ &async, &QgroupInherit_type, &inherit))
+ return NULL;
+
+ err = btrfs_util_create_subvolume(path.path, 0, async ? &transid : NULL,
+ inherit ? inherit->inherit : NULL);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ if (async)
+ return PyLong_FromUnsignedLongLong(transid);
+ else
+ Py_RETURN_NONE;
+}
+
+PyObject *create_snapshot(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {
+ "source", "path", "recursive", "read_only", "async",
+ "qgroup_inherit", NULL,
+ };
+ struct path_arg src = {.allow_fd = true}, dst = {.allow_fd = false};
+ enum btrfs_util_error err;
+ int recursive = 0, read_only = 0, async = 0;
+ int flags = 0;
+ QgroupInherit *inherit = NULL;
+ uint64_t transid;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O&|pppO!:create_snapshot",
+ keywords, &path_converter, &src,
+ &path_converter, &dst, &recursive,
+ &read_only, &async,
+ &QgroupInherit_type, &inherit))
+ return NULL;
+
+ if (recursive)
+ flags |= BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE;
+ if (read_only)
+ flags |= BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY;
+
+ if (src.path) {
+ err = btrfs_util_create_snapshot(src.path, dst.path, flags,
+ async ? &transid : NULL,
+ inherit ? inherit->inherit : NULL);
+ } else {
+ err = btrfs_util_create_snapshot_fd(src.fd, dst.path, flags,
+ async ? &transid : NULL,
+ inherit ? inherit->inherit : NULL);
+ }
+ if (err) {
+ SetFromBtrfsUtilErrorWithPaths(err, &src, &dst);
+ path_cleanup(&src);
+ path_cleanup(&dst);
+ return NULL;
+ }
+
+ path_cleanup(&src);
+ path_cleanup(&dst);
+ if (async)
+ return PyLong_FromUnsignedLongLong(transid);
+ else
+ Py_RETURN_NONE;
+}
+
+PyObject *delete_subvolume(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", "recursive", NULL};
+ struct path_arg path = {.allow_fd = false};
+ enum btrfs_util_error err;
+ int recursive = 0;
+ int flags = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|p:delete_subvolume",
+ keywords, &path_converter, &path,
+ &recursive))
+ return NULL;
+
+ if (recursive)
+ flags |= BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE;
+
+ err = btrfs_util_delete_subvolume(path.path, flags);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+ Py_RETURN_NONE;
+}
+
+PyObject *deleted_subvolumes(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *keywords[] = {"path", NULL};
+ struct path_arg path = {.allow_fd = true};
+ PyObject *ret;
+ uint64_t *ids;
+ size_t n;
+ enum btrfs_util_error err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:deleted_subvolumes",
+ keywords, &path_converter, &path))
+ return NULL;
+
+ if (path.path)
+ err = btrfs_util_deleted_subvolumes(path.path, &ids, &n);
+ else
+ err = btrfs_util_deleted_subvolumes_fd(path.fd, &ids, &n);
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return NULL;
+ }
+
+ path_cleanup(&path);
+
+ ret = list_from_uint64_array(ids, n);
+ free(ids);
+ return ret;
+}
+
+typedef struct {
+ PyObject_HEAD
+ struct btrfs_util_subvolume_iterator *iter;
+ bool info;
+} SubvolumeIterator;
+
+static void SubvolumeIterator_dealloc(SubvolumeIterator *self)
+{
+ btrfs_util_destroy_subvolume_iterator(self->iter);
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *SubvolumeIterator_next(SubvolumeIterator *self)
+{
+ enum btrfs_util_error err;
+ PyObject *ret, *tmp;
+ char *path;
+
+ if (!self->iter) {
+ PyErr_SetString(PyExc_ValueError,
+ "operation on closed iterator");
+ return NULL;
+ }
+
+ if (self->info) {
+ struct btrfs_util_subvolume_info subvol;
+
+ err = btrfs_util_subvolume_iterator_next_info(self->iter, &path,
+ &subvol);
+ if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+ } else if (err) {
+ SetFromBtrfsUtilError(err);
+ return NULL;
+ }
+
+ tmp = subvolume_info_to_object(&subvol);
+ } else {
+ uint64_t id;
+
+ err = btrfs_util_subvolume_iterator_next(self->iter, &path, &id);
+ if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+ } else if (err) {
+ SetFromBtrfsUtilError(err);
+ return NULL;
+ }
+
+ tmp = PyLong_FromUnsignedLongLong(id);
+
+ }
+ if (tmp) {
+ ret = Py_BuildValue("O&O", PyUnicode_DecodeFSDefault, path,
+ tmp);
+ Py_DECREF(tmp);
+ free(path);
+ } else {
+ ret = NULL;
+ }
+ return ret;
+}
+
+static int SubvolumeIterator_init(SubvolumeIterator *self, PyObject *args,
+ PyObject *kwds)
+{
+ static char *keywords[] = {"path", "top", "info", "post_order", NULL};
+ struct path_arg path = {.allow_fd = true};
+ enum btrfs_util_error err;
+ unsigned long long top = 5;
+ int info = 0;
+ int post_order = 0;
+ int flags = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|Kpp:SubvolumeIterator",
+ keywords, &path_converter, &path, &top,
+ &info, &post_order))
+ return -1;
+
+ if (post_order)
+ flags |= BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER;
+
+ if (path.path) {
+ err = btrfs_util_create_subvolume_iterator(path.path, top,
+ flags, &self->iter);
+ } else {
+ err = btrfs_util_create_subvolume_iterator_fd(path.fd, top,
+ flags,
+ &self->iter);
+ }
+ if (err) {
+ SetFromBtrfsUtilErrorWithPath(err, &path);
+ path_cleanup(&path);
+ return -1;
+ }
+
+ self->info = info;
+
+ return 0;
+}
+
+static PyObject *SubvolumeIterator_close(SubvolumeIterator *self)
+{
+ if (self->iter) {
+ btrfs_util_destroy_subvolume_iterator(self->iter);
+ self->iter = NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *SubvolumeIterator_fileno(SubvolumeIterator *self)
+{
+ if (!self->iter) {
+ PyErr_SetString(PyExc_ValueError,
+ "operation on closed iterator");
+ return NULL;
+ }
+ return PyLong_FromLong(btrfs_util_subvolume_iterator_fd(self->iter));
+}
+
+static PyObject *SubvolumeIterator_enter(SubvolumeIterator *self)
+{
+ Py_INCREF((PyObject *)self);
+ return (PyObject *)self;
+}
+
+static PyObject *SubvolumeIterator_exit(SubvolumeIterator *self, PyObject *args,
+ PyObject *kwds)
+{
+ static char *keywords[] = {"exc_type", "exc_value", "traceback", NULL};
+ PyObject *exc_type, *exc_value, *traceback;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOO:__exit__", keywords,
+ &exc_type, &exc_value, &traceback))
+ return NULL;
+
+ return SubvolumeIterator_close(self);
+}
+
+#define SubvolumeIterator_DOC \
+ "SubvolumeIterator(path, top=0, info=False, post_order=False) -> new subvolume iterator\n\n" \
+ "Create a new iterator that produces tuples of (path, ID) representing\n" \
+ "subvolumes on a filesystem.\n\n" \
+ "Arguments:\n" \
+ "path -- string, bytes, path-like object, or open file descriptor in\n" \
+ "filesystem to list\n" \
+ "top -- if not zero, instead of only listing subvolumes beneath the\n" \
+ "given path, list subvolumes beneath the subvolume with this ID; passing\n" \
+ "BTRFS_FS_TREE_OBJECTID (5) here lists all subvolumes. The subvolumes\n" \
+ "are listed relative to the subvolume with this ID.\n" \
+ "info -- bool indicating the iterator should yield SubvolumeInfo instead of\n" \
+ "the subvolume ID\n" \
+ "post_order -- bool indicating whether to yield parent subvolumes before\n" \
+ "child subvolumes (e.g., 'foo/bar' before 'foo')"
+
+static PyMethodDef SubvolumeIterator_methods[] = {
+ {"close", (PyCFunction)SubvolumeIterator_close,
+ METH_NOARGS,
+ "close()\n\n"
+ "Close this iterator."},
+ {"fileno", (PyCFunction)SubvolumeIterator_fileno,
+ METH_NOARGS,
+ "fileno() -> int\n\n"
+ "Get the file descriptor associated with this iterator."},
+ {"__enter__", (PyCFunction)SubvolumeIterator_enter,
+ METH_NOARGS, ""},
+ {"__exit__", (PyCFunction)SubvolumeIterator_exit,
+ METH_VARARGS | METH_KEYWORDS, ""},
+ {},
+};
+
+PyTypeObject SubvolumeIterator_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "btrfsutil.SubvolumeIterator", /* tp_name */
+ sizeof(SubvolumeIterator), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor)SubvolumeIterator_dealloc, /* tp_dealloc */
+ NULL, /* tp_print */
+ NULL, /* tp_getattr */
+ NULL, /* tp_setattr */
+ NULL, /* tp_as_async */
+ NULL, /* tp_repr */
+ NULL, /* tp_as_number */
+ NULL, /* tp_as_sequence */
+ NULL, /* tp_as_mapping */
+ NULL, /* tp_hash */
+ NULL, /* tp_call */
+ NULL, /* tp_str */
+ NULL, /* tp_getattro */
+ NULL, /* tp_setattro */
+ NULL, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ SubvolumeIterator_DOC, /* tp_doc */
+ NULL, /* tp_traverse */
+ NULL, /* tp_clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ PyObject_SelfIter, /* tp_iter */
+ (iternextfunc)SubvolumeIterator_next, /* tp_iternext */
+ SubvolumeIterator_methods, /* tp_methods */
+ NULL, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)SubvolumeIterator_init, /* tp_init */
+};
diff --git a/libbtrfsutil/python/tests/__init__.py b/libbtrfsutil/python/tests/__init__.py
new file mode 100644
index 00000000..35550e0a
--- /dev/null
+++ b/libbtrfsutil/python/tests/__init__.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2018 Facebook
+#
+# This file is part of libbtrfsutil.
+#
+# libbtrfsutil is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# libbtrfsutil is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+from pathlib import PurePath
+import subprocess
+import tempfile
+import unittest
+
+
+HAVE_PATH_LIKE = hasattr(PurePath, '__fspath__')
+
+
+@unittest.skipIf(os.geteuid() != 0, 'must be run as root')
+class BtrfsTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mountpoint = tempfile.mkdtemp()
+ try:
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ os.truncate(f.fileno(), 1024 * 1024 * 1024)
+ self.image = f.name
+ except Exception as e:
+ os.rmdir(self.mountpoint)
+ raise e
+
+ if os.path.exists('../../mkfs.btrfs'):
+ mkfs = '../../mkfs.btrfs'
+ else:
+ mkfs = 'mkfs.btrfs'
+ try:
+ subprocess.check_call([mkfs, '-q', self.image])
+ subprocess.check_call(['mount', '-o', 'loop', '--', self.image, self.mountpoint])
+ except Exception as e:
+ os.remove(self.image)
+ os.rmdir(self.mountpoint)
+ raise e
+
+ def tearDown(self):
+ try:
+ subprocess.check_call(['umount', self.mountpoint])
+ finally:
+ os.remove(self.image)
+ os.rmdir(self.mountpoint)
+
+ @staticmethod
+ def path_or_fd(path, open_flags=os.O_RDONLY):
+ yield path
+ yield path.encode()
+ if HAVE_PATH_LIKE:
+ yield PurePath(path)
+ fd = os.open(path, open_flags)
+ try:
+ yield fd
+ finally:
+ os.close(fd)
+
diff --git a/libbtrfsutil/python/tests/test_filesystem.py b/libbtrfsutil/python/tests/test_filesystem.py
new file mode 100644
index 00000000..006a6b1e
--- /dev/null
+++ b/libbtrfsutil/python/tests/test_filesystem.py
@@ -0,0 +1,73 @@
+# Copyright (C) 2018 Facebook
+#
+# This file is part of libbtrfsutil.
+#
+# libbtrfsutil is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# libbtrfsutil is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+
+import btrfsutil
+from tests import BtrfsTestCase, HAVE_PATH_LIKE
+
+
+def touch(path):
+ now = time.time()
+ os.utime(path, (now, now))
+
+
+class TestSubvolume(BtrfsTestCase):
+ def super_generation(self):
+ with open(self.image, 'rb') as f:
+ # csum is 32 bytes, fsid is 16 bytes, bytenr is 8 bytes, flags is 8
+ # bytes
+ f.seek(65536 + 32 + 16 + 8 + 8)
+ self.assertEqual(f.read(8), b'_BHRfS_M')
+ return int.from_bytes(f.read(8), 'little')
+
+ def test_sync(self):
+ old_generation = self.super_generation()
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ touch(arg)
+ btrfsutil.sync(arg)
+ new_generation = self.super_generation()
+ self.assertGreater(new_generation, old_generation)
+ old_generation = new_generation
+
+ def test_start_sync(self):
+ old_generation = self.super_generation()
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ touch(arg)
+ transid = btrfsutil.start_sync(arg)
+ self.assertGreater(transid, old_generation)
+
+ def test_wait_sync(self):
+ old_generation = self.super_generation()
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ touch(arg)
+ transid = btrfsutil.start_sync(arg)
+ btrfsutil.wait_sync(arg, transid)
+ new_generation = self.super_generation()
+ self.assertGreater(new_generation, old_generation)
+ old_generation = new_generation
+
+ touch(arg)
+ btrfsutil.start_sync(arg)
+ btrfsutil.wait_sync(arg)
+ new_generation = self.super_generation()
+ self.assertGreater(new_generation, old_generation)
+ old_generation = new_generation
diff --git a/libbtrfsutil/python/tests/test_qgroup.py b/libbtrfsutil/python/tests/test_qgroup.py
new file mode 100644
index 00000000..74fc46b6
--- /dev/null
+++ b/libbtrfsutil/python/tests/test_qgroup.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2018 Facebook
+#
+# This file is part of libbtrfsutil.
+#
+# libbtrfsutil is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# libbtrfsutil is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+
+import btrfsutil
+from tests import BtrfsTestCase
+
+
+class TestQgroup(BtrfsTestCase):
+ def test_subvolume_inherit(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+
+ inherit = btrfsutil.QgroupInherit()
+ inherit.add_group(5)
+
+ btrfsutil.create_subvolume(subvol, qgroup_inherit=inherit)
+
+ def test_snapshot_inherit(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ snapshot = os.path.join(self.mountpoint, 'snapshot')
+
+ inherit = btrfsutil.QgroupInherit()
+ inherit.add_group(5)
+
+ btrfsutil.create_subvolume(subvol)
+ btrfsutil.create_snapshot(subvol, snapshot, qgroup_inherit=inherit)
+
+
+class TestQgroupInherit(unittest.TestCase):
+ def test_new(self):
+ inherit = btrfsutil.QgroupInherit()
+ self.assertEqual(inherit.groups, [])
+
+ def test_add_group(self):
+ inherit = btrfsutil.QgroupInherit()
+ inherit.add_group(1)
+ self.assertEqual(inherit.groups, [1])
+ inherit.add_group(2)
+ self.assertEqual(inherit.groups, [1, 2])
+ inherit.add_group(3)
+ self.assertEqual(inherit.groups, [1, 2, 3])
diff --git a/libbtrfsutil/python/tests/test_subvolume.py b/libbtrfsutil/python/tests/test_subvolume.py
new file mode 100644
index 00000000..93396cba
--- /dev/null
+++ b/libbtrfsutil/python/tests/test_subvolume.py
@@ -0,0 +1,385 @@
+# Copyright (C) 2018 Facebook
+#
+# This file is part of libbtrfsutil.
+#
+# libbtrfsutil is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# libbtrfsutil is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+
+import fcntl
+import errno
+import os
+import os.path
+from pathlib import PurePath
+import traceback
+
+import btrfsutil
+from tests import BtrfsTestCase, HAVE_PATH_LIKE
+
+
+class TestSubvolume(BtrfsTestCase):
+ def test_is_subvolume(self):
+ dir = os.path.join(self.mountpoint, 'foo')
+ os.mkdir(dir)
+
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ self.assertTrue(btrfsutil.is_subvolume(arg))
+ for arg in self.path_or_fd(dir):
+ with self.subTest(type=type(arg)):
+ self.assertFalse(btrfsutil.is_subvolume(arg))
+
+ with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
+ btrfsutil.is_subvolume(os.path.join(self.mountpoint, 'bar'))
+ # This is a bit of an implementation detail, but really this is testing
+ # that the exception is initialized correctly.
+ self.assertEqual(e.exception.btrfsutilerror, btrfsutil.ERROR_STATFS_FAILED)
+ self.assertEqual(e.exception.errno, errno.ENOENT)
+
+ def test_subvolume_id(self):
+ dir = os.path.join(self.mountpoint, 'foo')
+ os.mkdir(dir)
+
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(btrfsutil.subvolume_id(arg), 5)
+ for arg in self.path_or_fd(dir):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(btrfsutil.subvolume_id(arg), 5)
+
+ def test_subvolume_path(self):
+ btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1'))
+ os.mkdir(os.path.join(self.mountpoint, 'dir1'))
+ os.mkdir(os.path.join(self.mountpoint, 'dir1/dir2'))
+ btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2'))
+ btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'dir1/dir2/subvol2/subvol3'))
+ os.mkdir(os.path.join(self.mountpoint, 'subvol1/dir3'))
+ btrfsutil.create_subvolume(os.path.join(self.mountpoint, 'subvol1/dir3/subvol4'))
+
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(btrfsutil.subvolume_path(arg), '')
+ self.assertEqual(btrfsutil.subvolume_path(arg, 5), '')
+ self.assertEqual(btrfsutil.subvolume_path(arg, 256), 'subvol1')
+ self.assertEqual(btrfsutil.subvolume_path(arg, 257), 'dir1/dir2/subvol2')
+ self.assertEqual(btrfsutil.subvolume_path(arg, 258), 'dir1/dir2/subvol2/subvol3')
+ self.assertEqual(btrfsutil.subvolume_path(arg, 259), 'subvol1/dir3/subvol4')
+
+ pwd = os.getcwd()
+ try:
+ os.chdir(self.mountpoint)
+ path = ''
+ for i in range(26):
+ name = chr(ord('a') + i) * 255
+ path = os.path.join(path, name)
+ btrfsutil.create_subvolume(name)
+ os.chdir(name)
+ self.assertEqual(btrfsutil.subvolume_path('.'), path)
+ finally:
+ os.chdir(pwd)
+
+ def test_subvolume_info(self):
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ info = btrfsutil.subvolume_info(arg)
+ self.assertEqual(info.id, 5)
+ self.assertEqual(info.parent_id, 0)
+ self.assertEqual(info.dir_id, 0)
+ self.assertEqual(info.flags, 0)
+ self.assertIsInstance(info.uuid, bytes)
+ self.assertEqual(len(info.uuid), 16)
+ self.assertEqual(info.parent_uuid, bytes(16))
+ self.assertEqual(info.received_uuid, bytes(16))
+ self.assertNotEqual(info.generation, 0)
+ self.assertEqual(info.ctransid, 0)
+ self.assertEqual(info.otransid, 0)
+ self.assertEqual(info.stransid, 0)
+ self.assertEqual(info.rtransid, 0)
+ self.assertIsInstance(info.ctime, float)
+ self.assertIsInstance(info.otime, float)
+ self.assertEqual(info.stime, 0)
+ self.assertEqual(info.rtime, 0)
+
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol)
+
+ info = btrfsutil.subvolume_info(subvol)
+ self.assertEqual(info.id, 256)
+ self.assertEqual(info.parent_id, 5)
+ self.assertEqual(info.dir_id, 256)
+ self.assertEqual(info.flags, 0)
+ self.assertIsInstance(info.uuid, bytes)
+ self.assertEqual(len(info.uuid), 16)
+ self.assertEqual(info.parent_uuid, bytes(16))
+ self.assertEqual(info.received_uuid, bytes(16))
+ self.assertNotEqual(info.generation, 0)
+ self.assertNotEqual(info.ctransid, 0)
+ self.assertNotEqual(info.otransid, 0)
+ self.assertEqual(info.stransid, 0)
+ self.assertEqual(info.rtransid, 0)
+ self.assertNotEqual(info.ctime, 0)
+ self.assertNotEqual(info.otime, 0)
+ self.assertEqual(info.stime, 0)
+ self.assertEqual(info.rtime, 0)
+
+ subvol_uuid = info.uuid
+ snapshot = os.path.join(self.mountpoint, 'snapshot')
+ btrfsutil.create_snapshot(subvol, snapshot)
+
+ info = btrfsutil.subvolume_info(snapshot)
+ self.assertEqual(info.parent_uuid, subvol_uuid)
+
+ # TODO: test received_uuid, stransid, rtransid, stime, and rtime
+
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ with self.assertRaises(btrfsutil.BtrfsUtilError) as e:
+ # BTRFS_EXTENT_TREE_OBJECTID
+ btrfsutil.subvolume_info(arg, 2)
+
+ def test_read_only(self):
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ btrfsutil.set_subvolume_read_only(arg)
+ self.assertTrue(btrfsutil.get_subvolume_read_only(arg))
+ self.assertTrue(btrfsutil.subvolume_info(arg).flags & 1)
+
+ btrfsutil.set_subvolume_read_only(arg, False)
+ self.assertFalse(btrfsutil.get_subvolume_read_only(arg))
+ self.assertFalse(btrfsutil.subvolume_info(arg).flags & 1)
+
+ btrfsutil.set_subvolume_read_only(arg, True)
+ self.assertTrue(btrfsutil.get_subvolume_read_only(arg))
+ self.assertTrue(btrfsutil.subvolume_info(arg).flags & 1)
+
+ btrfsutil.set_subvolume_read_only(arg, False)
+
+ def test_default_subvolume(self):
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(btrfsutil.get_default_subvolume(arg), 5)
+
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol)
+ for arg in self.path_or_fd(subvol):
+ with self.subTest(type=type(arg)):
+ btrfsutil.set_default_subvolume(arg)
+ self.assertEqual(btrfsutil.get_default_subvolume(arg), 256)
+ btrfsutil.set_default_subvolume(arg, 5)
+ self.assertEqual(btrfsutil.get_default_subvolume(arg), 5)
+
+ def test_create_subvolume(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+
+ btrfsutil.create_subvolume(subvol + '1')
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '1'))
+ btrfsutil.create_subvolume((subvol + '2').encode())
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '2'))
+ if HAVE_PATH_LIKE:
+ btrfsutil.create_subvolume(PurePath(subvol + '3'))
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '3'))
+
+ pwd = os.getcwd()
+ try:
+ os.chdir(self.mountpoint)
+ btrfsutil.create_subvolume('subvol4')
+ self.assertTrue(btrfsutil.is_subvolume('subvol4'))
+ finally:
+ os.chdir(pwd)
+
+ btrfsutil.create_subvolume(subvol + '5/')
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '5'))
+
+ btrfsutil.create_subvolume(subvol + '6//')
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '6'))
+
+ transid = btrfsutil.create_subvolume(subvol + '7', async=True)
+ self.assertTrue(btrfsutil.is_subvolume(subvol + '7'))
+ self.assertGreater(transid, 0)
+
+ # Test creating subvolumes under '/' in a chroot.
+ pid = os.fork()
+ if pid == 0:
+ try:
+ os.chroot(self.mountpoint)
+ os.chdir('/')
+ btrfsutil.create_subvolume('/subvol8')
+ self.assertTrue(btrfsutil.is_subvolume('/subvol8'))
+ with self.assertRaises(btrfsutil.BtrfsUtilError):
+ btrfsutil.create_subvolume('/')
+ os._exit(0)
+ except Exception:
+ traceback.print_exc()
+ os._exit(1)
+ wstatus = os.waitpid(pid, 0)[1]
+ self.assertTrue(os.WIFEXITED(wstatus))
+ self.assertEqual(os.WEXITSTATUS(wstatus), 0)
+
+ def test_create_snapshot(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+
+ btrfsutil.create_subvolume(subvol)
+ os.mkdir(os.path.join(subvol, 'dir'))
+
+ for i, arg in enumerate(self.path_or_fd(subvol)):
+ with self.subTest(type=type(arg)):
+ snapshots_dir = os.path.join(self.mountpoint, 'snapshots{}'.format(i))
+ os.mkdir(snapshots_dir)
+ snapshot = os.path.join(snapshots_dir, 'snapshot')
+
+ btrfsutil.create_snapshot(subvol, snapshot + '1')
+ self.assertTrue(btrfsutil.is_subvolume(snapshot + '1'))
+ self.assertTrue(os.path.exists(os.path.join(snapshot + '1', 'dir')))
+
+ btrfsutil.create_snapshot(subvol, (snapshot + '2').encode())
+ self.assertTrue(btrfsutil.is_subvolume(snapshot + '2'))
+ self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'dir')))
+
+ if HAVE_PATH_LIKE:
+ btrfsutil.create_snapshot(subvol, PurePath(snapshot + '3'))
+ self.assertTrue(btrfsutil.is_subvolume(snapshot + '3'))
+ self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'dir')))
+
+ nested_subvol = os.path.join(subvol, 'nested')
+ more_nested_subvol = os.path.join(nested_subvol, 'more_nested')
+ btrfsutil.create_subvolume(nested_subvol)
+ btrfsutil.create_subvolume(more_nested_subvol)
+ os.mkdir(os.path.join(more_nested_subvol, 'nested_dir'))
+
+ snapshot = os.path.join(self.mountpoint, 'snapshot')
+
+ btrfsutil.create_snapshot(subvol, snapshot + '1')
+ # Dummy subvolume.
+ self.assertEqual(os.stat(os.path.join(snapshot + '1', 'nested')).st_ino, 2)
+ self.assertFalse(os.path.exists(os.path.join(snapshot + '1', 'nested', 'more_nested')))
+
+ btrfsutil.create_snapshot(subvol, snapshot + '2', recursive=True)
+ self.assertTrue(os.path.exists(os.path.join(snapshot + '2', 'nested/more_nested/nested_dir')))
+
+ transid = btrfsutil.create_snapshot(subvol, snapshot + '3', recursive=True, async=True)
+ self.assertTrue(os.path.exists(os.path.join(snapshot + '3', 'nested/more_nested/nested_dir')))
+ self.assertGreater(transid, 0)
+
+ btrfsutil.create_snapshot(subvol, snapshot + '4', read_only=True)
+ self.assertTrue(btrfsutil.get_subvolume_read_only(snapshot + '4'))
+
+ def test_delete_subvolume(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol + '1')
+ self.assertTrue(os.path.exists(subvol + '1'))
+ btrfsutil.create_subvolume(subvol + '2')
+ self.assertTrue(os.path.exists(subvol + '2'))
+ btrfsutil.create_subvolume(subvol + '3')
+ self.assertTrue(os.path.exists(subvol + '3'))
+
+ btrfsutil.delete_subvolume(subvol + '1')
+ self.assertFalse(os.path.exists(subvol + '1'))
+ btrfsutil.delete_subvolume((subvol + '2').encode())
+ self.assertFalse(os.path.exists(subvol + '2'))
+ if HAVE_PATH_LIKE:
+ btrfsutil.delete_subvolume(PurePath(subvol + '3'))
+ self.assertFalse(os.path.exists(subvol + '3'))
+
+ # Test deleting subvolumes under '/' in a chroot.
+ pid = os.fork()
+ if pid == 0:
+ try:
+ os.chroot(self.mountpoint)
+ os.chdir('/')
+ btrfsutil.create_subvolume('/subvol4')
+ self.assertTrue(os.path.exists('/subvol4'))
+ btrfsutil.delete_subvolume('/subvol4')
+ self.assertFalse(os.path.exists('/subvol4'))
+ with self.assertRaises(btrfsutil.BtrfsUtilError):
+ btrfsutil.delete_subvolume('/')
+ os._exit(0)
+ except Exception:
+ traceback.print_exc()
+ os._exit(1)
+ wstatus = os.waitpid(pid, 0)[1]
+ self.assertTrue(os.WIFEXITED(wstatus))
+ self.assertEqual(os.WEXITSTATUS(wstatus), 0)
+
+ btrfsutil.create_subvolume(subvol + '5')
+ btrfsutil.create_subvolume(subvol + '5/foo')
+ btrfsutil.create_subvolume(subvol + '5/bar')
+ btrfsutil.create_subvolume(subvol + '5/bar/baz')
+ btrfsutil.create_subvolume(subvol + '5/bar/qux')
+ btrfsutil.create_subvolume(subvol + '5/quux')
+ with self.assertRaises(btrfsutil.BtrfsUtilError):
+ btrfsutil.delete_subvolume(subvol + '5')
+ btrfsutil.delete_subvolume(subvol + '5', recursive=True)
+ self.assertFalse(os.path.exists(subvol + '5'))
+
+ def test_deleted_subvolumes(self):
+ subvol = os.path.join(self.mountpoint, 'subvol')
+ btrfsutil.create_subvolume(subvol + '1')
+ btrfsutil.delete_subvolume(subvol + '1')
+ for arg in self.path_or_fd(self.mountpoint):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(btrfsutil.deleted_subvolumes(arg), [256])
+
+ def test_subvolume_iterator(self):
+ pwd = os.getcwd()
+ try:
+ os.chdir(self.mountpoint)
+ btrfsutil.create_subvolume('foo')
+
+ path, subvol = next(btrfsutil.SubvolumeIterator('.', info=True))
+ self.assertEqual(path, 'foo')
+ self.assertIsInstance(subvol, btrfsutil.SubvolumeInfo)
+ self.assertEqual(subvol.id, 256)
+ self.assertEqual(subvol.parent_id, 5)
+
+ btrfsutil.create_subvolume('foo/bar')
+ btrfsutil.create_subvolume('foo/bar/baz')
+
+ subvols = [
+ ('foo', 256),
+ ('foo/bar', 257),
+ ('foo/bar/baz', 258),
+ ]
+
+ for arg in self.path_or_fd('.'):
+ with self.subTest(type=type(arg)):
+ self.assertEqual(list(btrfsutil.SubvolumeIterator(arg)), subvols)
+ self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=0)), subvols)
+
+ self.assertEqual(list(btrfsutil.SubvolumeIterator('.', post_order=True)),
+ [('foo/bar/baz', 258),
+ ('foo/bar', 257),
+ ('foo', 256)])
+
+ subvols = [
+ ('bar', 257),
+ ('bar/baz', 258),
+ ]
+
+ self.assertEqual(list(btrfsutil.SubvolumeIterator('.', top=256)), subvols)
+ self.assertEqual(list(btrfsutil.SubvolumeIterator('foo', top=0)), subvols)
+
+ os.rename('foo/bar/baz', 'baz')
+ self.assertEqual(sorted(btrfsutil.SubvolumeIterator('.')),
+ [('baz', 258),
+ ('foo', 256),
+ ('foo/bar', 257)])
+
+ with btrfsutil.SubvolumeIterator('.') as it:
+ self.assertGreaterEqual(it.fileno(), 0)
+ it.close()
+ with self.assertRaises(ValueError):
+ next(iter(it))
+ with self.assertRaises(ValueError):
+ it.fileno()
+ it.close()
+ finally:
+ os.chdir(pwd)
diff --git a/libbtrfsutil/qgroup.c b/libbtrfsutil/qgroup.c
new file mode 100644
index 00000000..46a9043b
--- /dev/null
+++ b/libbtrfsutil/qgroup.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "btrfsutil_internal.h"
+
+PUBLIC enum btrfs_util_error btrfs_util_create_qgroup_inherit(int flags,
+ struct btrfs_util_qgroup_inherit **ret)
+{
+ struct btrfs_qgroup_inherit *inherit;
+
+ if (flags) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+
+ inherit = calloc(1, sizeof(*inherit));
+ if (!inherit)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+
+ /*
+ * struct btrfs_util_qgroup_inherit is a lie; it's actually struct
+ * btrfs_qgroup_inherit, but we abstract it away so that users don't
+ * need to depend on the Btrfs UAPI headers.
+ */
+ *ret = (struct btrfs_util_qgroup_inherit *)inherit;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC void btrfs_util_destroy_qgroup_inherit(struct btrfs_util_qgroup_inherit *inherit)
+{
+ free(inherit);
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_qgroup_inherit_add_group(struct btrfs_util_qgroup_inherit **inherit,
+ uint64_t qgroupid)
+{
+ struct btrfs_qgroup_inherit *tmp = (struct btrfs_qgroup_inherit *)*inherit;
+
+ tmp = realloc(tmp, sizeof(*tmp) +
+ (tmp->num_qgroups + 1) * sizeof(tmp->qgroups[0]));
+ if (!tmp)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+
+ tmp->qgroups[tmp->num_qgroups++] = qgroupid;
+
+ *inherit = (struct btrfs_util_qgroup_inherit *)tmp;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC void btrfs_util_qgroup_inherit_get_groups(const struct btrfs_util_qgroup_inherit *inherit,
+ const uint64_t **groups,
+ size_t *n)
+{
+ struct btrfs_qgroup_inherit *tmp = (struct btrfs_qgroup_inherit *)inherit;
+
+ /* Need to cast because __u64 != uint64_t. */
+ *groups = (const uint64_t *)&tmp->qgroups[0];
+ *n = tmp->num_qgroups;
+}
diff --git a/libbtrfsutil/stubs.c b/libbtrfsutil/stubs.c
new file mode 100644
index 00000000..9b9e037f
--- /dev/null
+++ b/libbtrfsutil/stubs.c
@@ -0,0 +1,35 @@
+/*
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if HAVE_REALLOCARRAY != 1
+
+#include <stdlib.h>
+#include <errno.h>
+
+void *reallocarray(void *ptr, size_t nmemb, size_t size)
+{
+ size_t res;
+
+ res = nmemb * size;
+ if (res < nmemb || res < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return realloc(ptr, res);
+}
+
+#endif
diff --git a/libbtrfsutil/stubs.h b/libbtrfsutil/stubs.h
new file mode 100644
index 00000000..cb6d43ca
--- /dev/null
+++ b/libbtrfsutil/stubs.h
@@ -0,0 +1,22 @@
+/*
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIBBTRFSUTIL_STUBS_H_
+
+void *reallocarray(void *ptr, size_t nmemb, size_t size);
+
+#endif
diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c
new file mode 100644
index 00000000..867b3e10
--- /dev/null
+++ b/libbtrfsutil/subvolume.c
@@ -0,0 +1,1382 @@
+/*
+ * Copyright (C) 2018 Facebook
+ *
+ * This file is part of libbtrfsutil.
+ *
+ * libbtrfsutil is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libbtrfsutil is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libbtrfsutil. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+#include "stubs.h"
+
+#include "btrfsutil_internal.h"
+
+/*
+ * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening
+ * a file descriptor and calling it, because fstat() and fstatfs() don't accept
+ * file descriptors opened with O_PATH on old kernels (before v3.6 and before
+ * v3.12, respectively), but stat() and statfs() can be called on a path that
+ * the user doesn't have read or write permissions to.
+ */
+PUBLIC enum btrfs_util_error btrfs_util_is_subvolume(const char *path)
+{
+ struct statfs sfs;
+ struct stat st;
+ int ret;
+
+ ret = statfs(path, &sfs);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_STATFS_FAILED;
+
+ if (sfs.f_type != BTRFS_SUPER_MAGIC) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_NOT_BTRFS;
+ }
+
+ ret = stat(path, &st);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_STAT_FAILED;
+
+ if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_NOT_SUBVOLUME;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_is_subvolume_fd(int fd)
+{
+ struct statfs sfs;
+ struct stat st;
+ int ret;
+
+ ret = fstatfs(fd, &sfs);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_STATFS_FAILED;
+
+ if (sfs.f_type != BTRFS_SUPER_MAGIC) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_NOT_BTRFS;
+ }
+
+ ret = fstat(fd, &st);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_STAT_FAILED;
+
+ if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_NOT_SUBVOLUME;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_id(const char *path,
+ uint64_t *id_ret)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_subvolume_id_fd(fd, id_ret);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd,
+ uint64_t *id_ret)
+{
+ struct btrfs_ioctl_ino_lookup_args args = {
+ .treeid = 0,
+ .objectid = BTRFS_FIRST_FREE_OBJECTID,
+ };
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
+ if (ret == -1) {
+ close(fd);
+ return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED;
+ }
+
+ *id_ret = args.treeid;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_path(const char *path,
+ uint64_t id,
+ char **path_ret)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_subvolume_path_fd(fd, id, path_ret);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id,
+ char **path_ret)
+{
+ char *path, *p;
+ size_t capacity = 4096;
+
+ if (id == 0) {
+ enum btrfs_util_error err;
+
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ }
+
+ path = malloc(capacity);
+ if (!path)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ p = path + capacity - 1;
+ p[0] = '\0';
+
+ while (id != BTRFS_FS_TREE_OBJECTID) {
+ struct btrfs_ioctl_search_args search = {
+ .key = {
+ .tree_id = BTRFS_ROOT_TREE_OBJECTID,
+ .min_objectid = id,
+ .max_objectid = id,
+ .min_type = BTRFS_ROOT_BACKREF_KEY,
+ .max_type = BTRFS_ROOT_BACKREF_KEY,
+ .min_offset = 0,
+ .max_offset = UINT64_MAX,
+ .min_transid = 0,
+ .max_transid = UINT64_MAX,
+ .nr_items = 1,
+ },
+ };
+ struct btrfs_ioctl_ino_lookup_args lookup;
+ const struct btrfs_ioctl_search_header *header;
+ const struct btrfs_root_ref *ref;
+ const char *name;
+ uint16_t name_len;
+ size_t lookup_len;
+ size_t total_len;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
+ if (ret == -1) {
+ free(path);
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ }
+
+ if (search.key.nr_items == 0) {
+ free(path);
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ header = (struct btrfs_ioctl_search_header *)search.buf;
+ ref = (struct btrfs_root_ref *)(header + 1);
+ name = (char *)(ref + 1);
+ name_len = le16_to_cpu(ref->name_len);
+
+ id = header->offset;
+
+ lookup.treeid = id;
+ lookup.objectid = le64_to_cpu(ref->dirid);
+ ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &lookup);
+ if (ret == -1) {
+ free(path);
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ }
+ lookup_len = strlen(lookup.name);
+
+ total_len = name_len + lookup_len + (id != BTRFS_FS_TREE_OBJECTID);
+ if (p - total_len < path) {
+ char *new_path, *new_p;
+ size_t new_capacity = capacity * 2;
+
+ new_path = malloc(new_capacity);
+ if (!new_path) {
+ free(path);
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ }
+ new_p = new_path + new_capacity - (path + capacity - p);
+ memcpy(new_p, p, path + capacity - p);
+ free(path);
+ path = new_path;
+ p = new_p;
+ capacity = new_capacity;
+ }
+ p -= name_len;
+ memcpy(p, name, name_len);
+ p -= lookup_len;
+ memcpy(p, lookup.name, lookup_len);
+ if (id != BTRFS_FS_TREE_OBJECTID)
+ *--p = '/';
+ }
+
+ if (p != path)
+ memmove(path, p, path + capacity - p);
+
+ *path_ret = path;
+
+ return BTRFS_UTIL_OK;
+}
+
+static void copy_timespec(struct timespec *timespec,
+ const struct btrfs_timespec *btrfs_timespec)
+{
+ timespec->tv_sec = le64_to_cpu(btrfs_timespec->sec);
+ timespec->tv_nsec = le32_to_cpu(btrfs_timespec->nsec);
+}
+
+static void copy_root_item(struct btrfs_util_subvolume_info *subvol,
+ const struct btrfs_root_item *root)
+{
+ subvol->flags = le64_to_cpu(root->flags);
+ memcpy(subvol->uuid, root->uuid, sizeof(subvol->uuid));
+ memcpy(subvol->parent_uuid, root->parent_uuid,
+ sizeof(subvol->parent_uuid));
+ memcpy(subvol->received_uuid, root->received_uuid,
+ sizeof(subvol->received_uuid));
+ subvol->generation = le64_to_cpu(root->generation);
+ subvol->ctransid = le64_to_cpu(root->ctransid);
+ subvol->otransid = le64_to_cpu(root->otransid);
+ subvol->stransid = le64_to_cpu(root->stransid);
+ subvol->rtransid = le64_to_cpu(root->rtransid);
+ copy_timespec(&subvol->ctime, &root->ctime);
+ copy_timespec(&subvol->otime, &root->otime);
+ copy_timespec(&subvol->stime, &root->stime);
+ copy_timespec(&subvol->rtime, &root->rtime);
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_info(const char *path,
+ uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_subvolume_info_fd(fd, id, subvol);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ struct btrfs_ioctl_search_args search = {
+ .key = {
+ .tree_id = BTRFS_ROOT_TREE_OBJECTID,
+ .min_type = BTRFS_ROOT_ITEM_KEY,
+ .max_type = BTRFS_ROOT_BACKREF_KEY,
+ .min_offset = 0,
+ .max_offset = UINT64_MAX,
+ .min_transid = 0,
+ .max_transid = UINT64_MAX,
+ .nr_items = 0,
+ },
+ };
+ enum btrfs_util_error err;
+ size_t items_pos = 0, buf_off = 0;
+ bool need_root_item = true, need_root_backref = true;
+ int ret;
+
+ if (id == 0) {
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ }
+
+ if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
+ id > BTRFS_LAST_FREE_OBJECTID) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+
+ search.key.min_objectid = search.key.max_objectid = id;
+
+ if (subvol) {
+ subvol->id = id;
+ subvol->parent_id = 0;
+ subvol->dir_id = 0;
+ if (id == BTRFS_FS_TREE_OBJECTID)
+ need_root_backref = false;
+ } else {
+ /*
+ * We only need the backref for filling in the subvolume info.
+ */
+ need_root_backref = false;
+ }
+
+ /* Don't bother searching for the backref if we don't need it. */
+ if (!need_root_backref)
+ search.key.max_type = BTRFS_ROOT_ITEM_KEY;
+
+ while (need_root_item || need_root_backref) {
+ const struct btrfs_ioctl_search_header *header;
+
+ if (items_pos >= search.key.nr_items) {
+ search.key.nr_items = 4096;
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ items_pos = 0;
+ buf_off = 0;
+
+ if (search.key.nr_items == 0) {
+ if (need_root_item) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ } else {
+ break;
+ }
+ }
+ }
+
+ header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off);
+ if (header->type == BTRFS_ROOT_ITEM_KEY) {
+ if (subvol) {
+ const struct btrfs_root_item *root;
+
+ root = (const struct btrfs_root_item *)(header + 1);
+ copy_root_item(subvol, root);
+ }
+ need_root_item = false;
+ search.key.min_type = BTRFS_ROOT_BACKREF_KEY;
+ } else if (header->type == BTRFS_ROOT_BACKREF_KEY) {
+ if (subvol) {
+ const struct btrfs_root_ref *ref;
+
+ ref = (const struct btrfs_root_ref *)(header + 1);
+ subvol->parent_id = header->offset;
+ subvol->dir_id = le64_to_cpu(ref->dirid);
+ }
+ need_root_backref = false;
+ search.key.min_type = UINT32_MAX;
+ }
+
+ items_pos++;
+ buf_off += sizeof(*header) + header->len;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_get_subvolume_read_only_fd(int fd,
+ bool *read_only_ret)
+{
+ uint64_t flags;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SUBVOL_GETFLAGS_FAILED;
+
+ *read_only_ret = flags & BTRFS_SUBVOL_RDONLY;
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_get_subvolume_read_only(const char *path,
+ bool *ret)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_get_subvolume_read_only_fd(fd, ret);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_set_subvolume_read_only(const char *path,
+ bool read_only)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_set_subvolume_read_only_fd(fd, read_only);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_set_subvolume_read_only_fd(int fd,
+ bool read_only)
+{
+ uint64_t flags;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SUBVOL_GETFLAGS_FAILED;
+
+ if (read_only)
+ flags |= BTRFS_SUBVOL_RDONLY;
+ else
+ flags &= ~BTRFS_SUBVOL_RDONLY;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SUBVOL_SETFLAGS_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_get_default_subvolume(const char *path,
+ uint64_t *id_ret)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_get_default_subvolume_fd(fd, id_ret);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_get_default_subvolume_fd(int fd,
+ uint64_t *id_ret)
+{
+ struct btrfs_ioctl_search_args search = {
+ .key = {
+ .tree_id = BTRFS_ROOT_TREE_OBJECTID,
+ .min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID,
+ .max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID,
+ .min_type = BTRFS_DIR_ITEM_KEY,
+ .max_type = BTRFS_DIR_ITEM_KEY,
+ .min_offset = 0,
+ .max_offset = UINT64_MAX,
+ .min_transid = 0,
+ .max_transid = UINT64_MAX,
+ .nr_items = 0,
+ },
+ };
+ size_t items_pos = 0, buf_off = 0;
+ int ret;
+
+ for (;;) {
+ const struct btrfs_ioctl_search_header *header;
+
+ if (items_pos >= search.key.nr_items) {
+ search.key.nr_items = 4096;
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ items_pos = 0;
+ buf_off = 0;
+
+ if (search.key.nr_items == 0) {
+ errno = ENOENT;
+ return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+ }
+ }
+
+ header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off);
+ if (header->type == BTRFS_DIR_ITEM_KEY) {
+ const struct btrfs_dir_item *dir;
+ const char *name;
+ uint16_t name_len;
+
+ dir = (struct btrfs_dir_item *)(header + 1);
+ name = (const char *)(dir + 1);
+ name_len = le16_to_cpu(dir->name_len);
+ if (strncmp(name, "default", name_len) == 0) {
+ *id_ret = le64_to_cpu(dir->location.objectid);
+ break;
+ }
+ }
+
+ items_pos++;
+ buf_off += sizeof(*header) + header->len;
+ search.key.min_offset = header->offset + 1;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_set_default_subvolume(const char *path,
+ uint64_t id)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_set_default_subvolume_fd(fd, id);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_set_default_subvolume_fd(int fd,
+ uint64_t id)
+{
+ enum btrfs_util_error err;
+ int ret;
+
+ if (id == 0) {
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_subvolume_id_fd(fd, &id);
+ if (err)
+ return err;
+ }
+
+ ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_DEFAULT_SUBVOL_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
+
+static enum btrfs_util_error openat_parent_and_name(int dirfd, const char *path,
+ char *name, size_t name_len,
+ int *fd)
+{
+ char *tmp_path, *slash, *dirname, *basename;
+ size_t len;
+
+ /* Ignore trailing slashes. */
+ len = strlen(path);
+ while (len > 1 && path[len - 1] == '/')
+ len--;
+
+ tmp_path = malloc(len + 1);
+ if (!tmp_path)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ memcpy(tmp_path, path, len);
+ tmp_path[len] = '\0';
+
+ slash = memrchr(tmp_path, '/', len);
+ if (slash == tmp_path) {
+ dirname = "/";
+ basename = tmp_path + 1;
+ } else if (slash) {
+ *slash = '\0';
+ dirname = tmp_path;
+ basename = slash + 1;
+ } else {
+ dirname = ".";
+ basename = tmp_path;
+ }
+
+ len = strlen(basename);
+ if (len >= name_len) {
+ free(tmp_path);
+ errno = ENAMETOOLONG;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+ memcpy(name, basename, len);
+ name[len] = '\0';
+
+ *fd = openat(dirfd, dirname, O_RDONLY | O_DIRECTORY);
+ if (*fd == -1) {
+ free(tmp_path);
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+ }
+
+ free(tmp_path);
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_subvolume(const char *path,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit)
+{
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+ enum btrfs_util_error err;
+ int parent_fd;
+
+ err = openat_parent_and_name(AT_FDCWD, path, name, sizeof(name),
+ &parent_fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_create_subvolume_fd(parent_fd, name, flags,
+ async_transid, qgroup_inherit);
+ SAVE_ERRNO_AND_CLOSE(parent_fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_fd(int parent_fd,
+ const char *name,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit)
+{
+ struct btrfs_ioctl_vol_args_v2 args = {};
+ size_t len;
+ int ret;
+
+ if (flags) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (async_transid)
+ args.flags |= BTRFS_SUBVOL_CREATE_ASYNC;
+ if (qgroup_inherit) {
+ args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT;
+ args.qgroup_inherit = (struct btrfs_qgroup_inherit *)qgroup_inherit;
+ args.size = (sizeof(*args.qgroup_inherit) +
+ args.qgroup_inherit->num_qgroups *
+ sizeof(args.qgroup_inherit->qgroups[0]));
+ }
+
+ len = strlen(name);
+ if (len >= sizeof(args.name)) {
+ errno = ENAMETOOLONG;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SUBVOL_CREATE_FAILED;
+
+ if (async_transid)
+ *async_transid = args.transid;
+
+ return BTRFS_UTIL_OK;
+}
+
+#define BTRFS_UTIL_SUBVOLUME_ITERATOR_CLOSE_FD (1 << 30)
+
+struct search_stack_entry {
+ struct btrfs_ioctl_search_args search;
+ size_t items_pos, buf_off;
+ size_t path_len;
+};
+
+struct btrfs_util_subvolume_iterator {
+ int fd;
+ int flags;
+
+ struct search_stack_entry *search_stack;
+ size_t search_stack_len;
+ size_t search_stack_capacity;
+
+ char *cur_path;
+ size_t cur_path_capacity;
+};
+
+static enum btrfs_util_error append_to_search_stack(struct btrfs_util_subvolume_iterator *iter,
+ uint64_t tree_id,
+ size_t path_len)
+{
+ struct search_stack_entry *entry;
+
+ if (iter->search_stack_len >= iter->search_stack_capacity) {
+ size_t new_capacity = iter->search_stack_capacity * 2;
+ struct search_stack_entry *new_search_stack;
+
+ new_search_stack = reallocarray(iter->search_stack,
+ new_capacity,
+ sizeof(*iter->search_stack));
+ if (!new_search_stack)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+
+ iter->search_stack_capacity = new_capacity;
+ iter->search_stack = new_search_stack;
+ }
+
+ entry = &iter->search_stack[iter->search_stack_len++];
+
+ memset(&entry->search, 0, sizeof(entry->search));
+ entry->search.key.tree_id = BTRFS_ROOT_TREE_OBJECTID;
+ entry->search.key.min_objectid = tree_id;
+ entry->search.key.max_objectid = tree_id;
+ entry->search.key.min_type = BTRFS_ROOT_REF_KEY;
+ entry->search.key.max_type = BTRFS_ROOT_REF_KEY;
+ entry->search.key.min_offset = 0;
+ entry->search.key.max_offset = UINT64_MAX;
+ entry->search.key.min_transid = 0;
+ entry->search.key.max_transid = UINT64_MAX;
+ entry->search.key.nr_items = 0;
+
+ entry->items_pos = 0;
+ entry->buf_off = 0;
+
+ entry->path_len = path_len;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator(const char *path,
+ uint64_t top,
+ int flags,
+ struct btrfs_util_subvolume_iterator **ret)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_create_subvolume_iterator_fd(fd, top, flags, ret);
+ if (err)
+ SAVE_ERRNO_AND_CLOSE(fd);
+ else
+ (*ret)->flags |= BTRFS_UTIL_SUBVOLUME_ITERATOR_CLOSE_FD;
+
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd,
+ uint64_t top,
+ int flags,
+ struct btrfs_util_subvolume_iterator **ret)
+{
+ struct btrfs_util_subvolume_iterator *iter;
+ enum btrfs_util_error err;
+
+ if (flags & ~BTRFS_UTIL_SUBVOLUME_ITERATOR_MASK) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (top == 0) {
+ err = btrfs_util_is_subvolume_fd(fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_subvolume_id_fd(fd, &top);
+ if (err)
+ return err;
+ }
+
+ iter = malloc(sizeof(*iter));
+ if (!iter)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+
+ iter->fd = fd;
+ iter->flags = flags;
+
+ iter->search_stack_len = 0;
+ iter->search_stack_capacity = 4;
+ iter->search_stack = malloc(sizeof(*iter->search_stack) *
+ iter->search_stack_capacity);
+ if (!iter->search_stack) {
+ err = BTRFS_UTIL_ERROR_NO_MEMORY;
+ goto out_iter;
+ }
+
+ iter->cur_path_capacity = 256;
+ iter->cur_path = malloc(iter->cur_path_capacity);
+ if (!iter->cur_path) {
+ err = BTRFS_UTIL_ERROR_NO_MEMORY;
+ goto out_search_stack;
+ }
+
+ err = append_to_search_stack(iter, top, 0);
+ if (err)
+ goto out_cur_path;
+
+ *ret = iter;
+
+ return BTRFS_UTIL_OK;
+
+out_cur_path:
+ free(iter->cur_path);
+out_search_stack:
+ free(iter->search_stack);
+out_iter:
+ free(iter);
+ return err;
+}
+
+static enum btrfs_util_error snapshot_subvolume_children(int fd, int parent_fd,
+ const char *name,
+ uint64_t *async_transid)
+{
+ struct btrfs_util_subvolume_iterator *iter;
+ enum btrfs_util_error err;
+ int dstfd;
+
+ dstfd = openat(parent_fd, name, O_RDONLY);
+ if (dstfd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_create_subvolume_iterator_fd(fd, 0, 0, &iter);
+ if (err)
+ goto out;
+
+ for (;;) {
+ char child_name[BTRFS_SUBVOL_NAME_MAX + 1];
+ char *child_path;
+ int child_fd, new_parent_fd;
+ uint64_t tmp_transid;
+
+ err = btrfs_util_subvolume_iterator_next(iter, &child_path,
+ NULL);
+ if (err) {
+ if (err == BTRFS_UTIL_ERROR_STOP_ITERATION)
+ err = BTRFS_UTIL_OK;
+ break;
+ }
+
+ /* Remove the placeholder directory. */
+ if (unlinkat(dstfd, child_path, AT_REMOVEDIR) == -1) {
+ free(child_path);
+ err = BTRFS_UTIL_ERROR_RMDIR_FAILED;
+ break;
+ }
+
+ child_fd = openat(fd, child_path, O_RDONLY);
+ if (child_fd == -1) {
+ free(child_path);
+ err = BTRFS_UTIL_ERROR_OPEN_FAILED;
+ break;
+ }
+
+ err = openat_parent_and_name(dstfd, child_path, child_name,
+ sizeof(child_name),
+ &new_parent_fd);
+ free(child_path);
+ if (err) {
+ SAVE_ERRNO_AND_CLOSE(child_fd);
+ break;
+ }
+
+ err = btrfs_util_create_snapshot_fd2(child_fd, new_parent_fd,
+ child_name, 0,
+ async_transid ? &tmp_transid : NULL,
+ NULL);
+ SAVE_ERRNO_AND_CLOSE(child_fd);
+ SAVE_ERRNO_AND_CLOSE(new_parent_fd);
+ if (err)
+ break;
+ if (async_transid && tmp_transid > *async_transid)
+ *async_transid = tmp_transid;
+ }
+
+ btrfs_util_destroy_subvolume_iterator(iter);
+out:
+ SAVE_ERRNO_AND_CLOSE(dstfd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_snapshot(const char *source,
+ const char *path,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(source, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_create_snapshot_fd(fd, path, flags, async_transid,
+ qgroup_inherit);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_snapshot_fd(int fd,
+ const char *path,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit)
+{
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+ enum btrfs_util_error err;
+ int parent_fd;
+
+ err = openat_parent_and_name(AT_FDCWD, path, name, sizeof(name),
+ &parent_fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_create_snapshot_fd2(fd, parent_fd, name, flags,
+ async_transid, qgroup_inherit);
+ SAVE_ERRNO_AND_CLOSE(parent_fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_create_snapshot_fd2(int fd,
+ int parent_fd,
+ const char *name,
+ int flags,
+ uint64_t *async_transid,
+ struct btrfs_util_qgroup_inherit *qgroup_inherit)
+{
+ struct btrfs_ioctl_vol_args_v2 args = {.fd = fd};
+ enum btrfs_util_error err;
+ size_t len;
+ int ret;
+
+ if ((flags & ~BTRFS_UTIL_CREATE_SNAPSHOT_MASK) ||
+ ((flags & BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY) &&
+ (flags & BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE))) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (flags & BTRFS_UTIL_CREATE_SNAPSHOT_READ_ONLY)
+ args.flags |= BTRFS_SUBVOL_RDONLY;
+ if (async_transid)
+ args.flags |= BTRFS_SUBVOL_CREATE_ASYNC;
+ if (qgroup_inherit) {
+ args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT;
+ args.qgroup_inherit = (struct btrfs_qgroup_inherit *)qgroup_inherit;
+ args.size = (sizeof(*args.qgroup_inherit) +
+ args.qgroup_inherit->num_qgroups *
+ sizeof(args.qgroup_inherit->qgroups[0]));
+ }
+
+ len = strlen(name);
+ if (len >= sizeof(args.name)) {
+ errno = ENAMETOOLONG;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SNAP_CREATE_V2, &args);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SUBVOL_CREATE_FAILED;
+
+ if (async_transid)
+ *async_transid = args.transid;
+
+ if (flags & BTRFS_UTIL_CREATE_SNAPSHOT_RECURSIVE) {
+ err = snapshot_subvolume_children(fd, parent_fd, name,
+ async_transid);
+ if (err)
+ return err;
+ }
+
+ return BTRFS_UTIL_OK;
+}
+
+static enum btrfs_util_error delete_subvolume_children(int parent_fd,
+ const char *name)
+{
+ struct btrfs_util_subvolume_iterator *iter;
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = openat(parent_fd, name, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_create_subvolume_iterator_fd(fd, 0,
+ BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER,
+ &iter);
+ if (err)
+ goto out;
+
+ for (;;) {
+ char child_name[BTRFS_PATH_NAME_MAX + 1];
+ char *child_path;
+ int child_parent_fd;
+
+ err = btrfs_util_subvolume_iterator_next(iter, &child_path,
+ NULL);
+ if (err) {
+ if (err == BTRFS_UTIL_ERROR_STOP_ITERATION)
+ err = BTRFS_UTIL_OK;
+ break;
+ }
+
+ err = openat_parent_and_name(fd, child_path, child_name,
+ sizeof(child_name),
+ &child_parent_fd);
+ free(child_path);
+ if (err)
+ break;
+
+ err = btrfs_util_delete_subvolume_fd(child_parent_fd,
+ child_name, 0);
+ SAVE_ERRNO_AND_CLOSE(child_parent_fd);
+ if (err)
+ break;
+ }
+
+ btrfs_util_destroy_subvolume_iterator(iter);
+out:
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_delete_subvolume(const char *path,
+ int flags)
+{
+ char name[BTRFS_PATH_NAME_MAX + 1];
+ enum btrfs_util_error err;
+ int parent_fd;
+
+ err = openat_parent_and_name(AT_FDCWD, path, name, sizeof(name),
+ &parent_fd);
+ if (err)
+ return err;
+
+ err = btrfs_util_delete_subvolume_fd(parent_fd, name, flags);
+ SAVE_ERRNO_AND_CLOSE(parent_fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_delete_subvolume_fd(int parent_fd,
+ const char *name,
+ int flags)
+{
+ struct btrfs_ioctl_vol_args args = {};
+ enum btrfs_util_error err;
+ size_t len;
+ int ret;
+
+ if (flags & ~BTRFS_UTIL_DELETE_SUBVOLUME_MASK) {
+ errno = EINVAL;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+
+ if (flags & BTRFS_UTIL_DELETE_SUBVOLUME_RECURSIVE) {
+ err = delete_subvolume_children(parent_fd, name);
+ if (err)
+ return err;
+ }
+
+ len = strlen(name);
+ if (len >= sizeof(args.name)) {
+ errno = ENAMETOOLONG;
+ return BTRFS_UTIL_ERROR_INVALID_ARGUMENT;
+ }
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SNAP_DESTROY_FAILED;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC void btrfs_util_destroy_subvolume_iterator(struct btrfs_util_subvolume_iterator *iter)
+{
+ if (iter) {
+ free(iter->cur_path);
+ free(iter->search_stack);
+ if (iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_CLOSE_FD)
+ SAVE_ERRNO_AND_CLOSE(iter->fd);
+ free(iter);
+ }
+}
+
+PUBLIC int btrfs_util_subvolume_iterator_fd(const struct btrfs_util_subvolume_iterator *iter)
+{
+ return iter->fd;
+}
+
+static struct search_stack_entry *top_search_stack_entry(struct btrfs_util_subvolume_iterator *iter)
+{
+ return &iter->search_stack[iter->search_stack_len - 1];
+}
+
+static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_iterator *iter,
+ const struct btrfs_ioctl_search_header *header,
+ const struct btrfs_root_ref *ref,
+ const char *name,
+ size_t *path_len_ret)
+{
+ struct btrfs_ioctl_ino_lookup_args lookup = {
+ .treeid = header->objectid,
+ .objectid = le64_to_cpu(ref->dirid),
+ };
+ struct search_stack_entry *top = top_search_stack_entry(iter);
+ size_t dir_len, name_len, path_len;
+ char *p;
+ int ret;
+
+ ret = ioctl(iter->fd, BTRFS_IOC_INO_LOOKUP, &lookup);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED;
+
+ dir_len = strlen(lookup.name);
+ name_len = le16_to_cpu(ref->name_len);
+
+ path_len = top->path_len;
+ /*
+ * We need a joining slash if we have a current path and a subdirectory.
+ */
+ if (top->path_len && dir_len)
+ path_len++;
+ path_len += dir_len;
+ /*
+ * We need another joining slash if we have a current path and a name,
+ * but not if we have a subdirectory, because the lookup ioctl includes
+ * a trailing slash.
+ */
+ if (top->path_len && !dir_len && name_len)
+ path_len++;
+ path_len += name_len;
+
+ if (path_len > iter->cur_path_capacity) {
+ char *tmp = realloc(iter->cur_path, path_len);
+
+ if (!tmp)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ iter->cur_path = tmp;
+ iter->cur_path_capacity = path_len;
+ }
+
+ p = iter->cur_path + top->path_len;
+ if (top->path_len && dir_len)
+ *p++ = '/';
+ memcpy(p, lookup.name, dir_len);
+ p += dir_len;
+ if (top->path_len && !dir_len && name_len)
+ *p++ = '/';
+ memcpy(p, name, name_len);
+ p += name_len;
+
+ *path_len_ret = path_len;
+
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ uint64_t *id_ret)
+{
+ struct search_stack_entry *top;
+ const struct btrfs_ioctl_search_header *header;
+ const struct btrfs_root_ref *ref;
+ const char *name;
+ enum btrfs_util_error err;
+ size_t path_len;
+ int ret;
+
+ for (;;) {
+ for (;;) {
+ if (iter->search_stack_len == 0)
+ return BTRFS_UTIL_ERROR_STOP_ITERATION;
+
+ top = top_search_stack_entry(iter);
+ if (top->items_pos < top->search.key.nr_items) {
+ break;
+ } else {
+ top->search.key.nr_items = 4096;
+ ret = ioctl(iter->fd, BTRFS_IOC_TREE_SEARCH, &top->search);
+ if (ret == -1)
+ return BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ top->items_pos = 0;
+ top->buf_off = 0;
+
+ if (top->search.key.nr_items == 0) {
+ iter->search_stack_len--;
+ if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) &&
+ iter->search_stack_len)
+ goto out;
+ }
+ }
+ }
+
+ header = (struct btrfs_ioctl_search_header *)(top->search.buf + top->buf_off);
+
+ top->items_pos++;
+ top->buf_off += sizeof(*header) + header->len;
+ top->search.key.min_offset = header->offset + 1;
+
+ /* This shouldn't happen, but handle it just in case. */
+ if (header->type != BTRFS_ROOT_REF_KEY)
+ continue;
+
+ ref = (struct btrfs_root_ref *)(header + 1);
+ name = (const char *)(ref + 1);
+ err = build_subvol_path(iter, header, ref, name, &path_len);
+ if (err)
+ return err;
+
+ err = append_to_search_stack(iter, header->offset, path_len);
+ if (err)
+ return err;
+
+ if (!(iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER)) {
+ top = top_search_stack_entry(iter);
+ goto out;
+ }
+ }
+
+out:
+ if (path_ret) {
+ *path_ret = malloc(top->path_len + 1);
+ if (!*path_ret)
+ return BTRFS_UTIL_ERROR_NO_MEMORY;
+ memcpy(*path_ret, iter->cur_path, top->path_len);
+ (*path_ret)[top->path_len] = '\0';
+ }
+ if (id_ret)
+ *id_ret = top->search.key.min_objectid;
+ return BTRFS_UTIL_OK;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter,
+ char **path_ret,
+ struct btrfs_util_subvolume_info *subvol)
+{
+ enum btrfs_util_error err;
+ uint64_t id;
+
+ err = btrfs_util_subvolume_iterator_next(iter, path_ret, &id);
+ if (err)
+ return err;
+
+ return btrfs_util_subvolume_info_fd(iter->fd, id, subvol);
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path,
+ uint64_t **ids,
+ size_t *n)
+{
+ enum btrfs_util_error err;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return BTRFS_UTIL_ERROR_OPEN_FAILED;
+
+ err = btrfs_util_deleted_subvolumes_fd(fd, ids, n);
+ SAVE_ERRNO_AND_CLOSE(fd);
+ return err;
+}
+
+PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd,
+ uint64_t **ids,
+ size_t *n)
+{
+ size_t capacity = 0;
+ struct btrfs_ioctl_search_args search = {
+ .key = {
+ .tree_id = BTRFS_ROOT_TREE_OBJECTID,
+ .min_objectid = BTRFS_ORPHAN_OBJECTID,
+ .max_objectid = BTRFS_ORPHAN_OBJECTID,
+ .min_type = BTRFS_ORPHAN_ITEM_KEY,
+ .max_type = BTRFS_ORPHAN_ITEM_KEY,
+ .min_offset = 0,
+ .max_offset = UINT64_MAX,
+ .min_transid = 0,
+ .max_transid = UINT64_MAX,
+ .nr_items = 0,
+ },
+ };
+ enum btrfs_util_error err;
+ size_t items_pos = 0, buf_off = 0;
+ int ret;
+
+ *ids = NULL;
+ *n = 0;
+ for (;;) {
+ const struct btrfs_ioctl_search_header *header;
+
+ if (items_pos >= search.key.nr_items) {
+ search.key.nr_items = 4096;
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search);
+ if (ret == -1) {
+ err = BTRFS_UTIL_ERROR_SEARCH_FAILED;
+ goto out;
+ }
+ items_pos = 0;
+ buf_off = 0;
+
+ if (search.key.nr_items == 0)
+ break;
+ }
+
+ header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off);
+
+ /*
+ * The orphan item might be for a free space cache inode, so
+ * check if there's a matching root item.
+ */
+ err = btrfs_util_subvolume_info_fd(fd, header->offset, NULL);
+ if (!err) {
+ if (*n >= capacity) {
+ size_t new_capacity;
+ uint64_t *new_ids;
+
+ new_capacity = capacity ? capacity * 2 : 1;
+ new_ids = reallocarray(*ids, new_capacity,
+ sizeof(**ids));
+ if (!new_ids) {
+ err = BTRFS_UTIL_ERROR_NO_MEMORY;
+ goto out;
+ }
+
+ *ids = new_ids;
+ capacity = new_capacity;
+ }
+ (*ids)[(*n)++] = header->offset;
+ } else if (err != BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) {
+ goto out;
+ }
+
+ items_pos++;
+ buf_off += sizeof(*header) + header->len;
+ search.key.min_offset = header->offset + 1;
+ }
+
+ err = BTRFS_UTIL_OK;
+out:
+ if (err) {
+ free(*ids);
+ *ids = NULL;
+ *n = 0;
+ }
+ return err;
+}