You've already forked linux-apfs-oot
mirror of
https://github.com/linux-apfs/linux-apfs-oot.git
synced 2026-05-01 15:01:20 -07:00
Set up a standalone repository for the APFS module
Copy the code of the APFS module into its own repository, without the rest of the kernel tree. Development will continue upstream, but the intention is to make life easier for potential users. To get the module to build independently, rewrite the Makefile and add a definition for the APFS_SUPER_MAGIC macro. Since the intention is to support a range of kernel versions, use preprocessor checks to handle kernels without statx, without iversion, and without SB_RDONLY. Provide a README file based on the upstream documentation, but with additional build and mount instructions. Add a LICENSE file as well. Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
.*
|
||||||
|
*.swp
|
||||||
|
*.d
|
||||||
|
*.o
|
||||||
|
*.symvers
|
||||||
|
*.mod.c
|
||||||
|
*.order
|
||||||
|
*.ko
|
||||||
|
tags
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) 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
|
||||||
|
this service 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 make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. 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.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
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
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the 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 a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
convey 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 2 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, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision 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, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This 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 Library General
|
||||||
|
Public License instead of this License.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Makefile for the out-of-tree Linux APFS module.
|
||||||
|
#
|
||||||
|
|
||||||
|
obj-m = apfs.o
|
||||||
|
apfs-y = btree.o dir.o extents.o file.o inode.o key.o message.o \
|
||||||
|
namei.o node.o object.o super.o symlink.o unicode.o xattr.o
|
||||||
|
|
||||||
|
default:
|
||||||
|
make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(shell pwd)
|
||||||
|
clean:
|
||||||
|
make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(shell pwd) clean
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
Apple File System
|
||||||
|
=================
|
||||||
|
|
||||||
|
The Apple File System (APFS) is a copy-on-write filesystem apparently intended
|
||||||
|
to replace HFS+ on all Apple devices in the near future. This module provides
|
||||||
|
a degree of experimental read-only support on Linux.
|
||||||
|
|
||||||
|
This repo is a standalone version of <git://github.com/eafer/linux-apfs.git>
|
||||||
|
for the purpose of making it easier to build the module out-of-tree. It is
|
||||||
|
supposed to work with a range of kernel versions starting at 4.9 or before, but
|
||||||
|
only a few of those have actually been tested. If you run into any problem,
|
||||||
|
please send a report to <linux-apfs@googlegroups.com>.
|
||||||
|
|
||||||
|
The long-term goal is to support writes as well. To help with testing, a set
|
||||||
|
of userland tools is under development. The git tree can be retrieved from
|
||||||
|
<git://github.com/eafer/apfsprogs.git>.
|
||||||
|
|
||||||
|
Known limitations
|
||||||
|
=================
|
||||||
|
|
||||||
|
This module is the result of reverse engineering. As it is read-only there
|
||||||
|
should be no risk of data corruption, but do not expect everything to be read
|
||||||
|
correctly. Testing in general has been limited so far, so you may experience
|
||||||
|
crashes. Please report any issues that you find.
|
||||||
|
|
||||||
|
Apple has released other versions of the filesystem to the public before the
|
||||||
|
current one. I would not expect them to be compatible with this module at all,
|
||||||
|
but I am open to fixing that if requested.
|
||||||
|
|
||||||
|
Many features are not yet supported:
|
||||||
|
|
||||||
|
o Encryption.
|
||||||
|
o Compression, though the compressed contents of a file can be read from the
|
||||||
|
'com.apple.decmpfs' and 'com.apple.ResourceFork' xattrs as long as they are
|
||||||
|
under 64k.
|
||||||
|
o Restoring to a snapshot.
|
||||||
|
o Access control lists. This is not a priority.
|
||||||
|
|
||||||
|
Build
|
||||||
|
=====
|
||||||
|
|
||||||
|
In order to build a module out-of-tree, you will first need the Linux kernel
|
||||||
|
headers. On Debian, you can get them by running (as root):
|
||||||
|
|
||||||
|
apt-get install linux-headers-$(uname -r)
|
||||||
|
|
||||||
|
Now you can just cd to the linux-apfs-oot directory and run
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
The resulting module is the apfs.ko file. Before you can use it you must insert
|
||||||
|
it into the kernel, as well as its dependencies. Again as root:
|
||||||
|
|
||||||
|
modprobe libcrc32c
|
||||||
|
insmod apfs.ko
|
||||||
|
|
||||||
|
Mount
|
||||||
|
=====
|
||||||
|
|
||||||
|
Like all filesystems, apfs is mounted with
|
||||||
|
|
||||||
|
mount [-o options] device dir
|
||||||
|
|
||||||
|
where 'device' is the path to your device file or filesystem image, and 'dir'
|
||||||
|
is the mount point. The following options are accepted:
|
||||||
|
|
||||||
|
vol=n
|
||||||
|
Volume number to mount. The default is volume 0.
|
||||||
|
|
||||||
|
uid=n, gid=n
|
||||||
|
Override on-disk inode ownership data with given uid/gid.
|
||||||
|
|
||||||
|
cknodes
|
||||||
|
Verify the checksum on all metadata nodes. Right now this has
|
||||||
|
a severe performance cost, so it's not recommended.
|
||||||
|
|
||||||
|
So for instance, if you want to mount volume number 2, and you want the metadata
|
||||||
|
to be checked, you should run (as root):
|
||||||
|
|
||||||
|
mount -o cknodes,vol=2 device dir
|
||||||
|
|
||||||
|
To unmount it, run
|
||||||
|
|
||||||
|
umount dir
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Originally written by Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>,
|
||||||
|
with several contributions from Gabriel Krisman Bertazi <krisman@collabora.com>
|
||||||
|
and Arnaud Ferraris <arnaud.ferraris@collabora.com>.
|
||||||
|
|
||||||
|
Work was first based on reverse engineering done by others [1][2], and later
|
||||||
|
on the (very incomplete) official specification [3]. Some parts of the code
|
||||||
|
imitate the ext2 module, and to a lesser degree xfs, udf, gfs2 and hfsplus.
|
||||||
|
|
||||||
|
[1] Hansen, K.H., Toolan, F., Decoding the APFS file system, Digital
|
||||||
|
Investigation (2017), http://dx.doi.org/10.1016/j.diin.2017.07.003
|
||||||
|
[2] https://github.com/sgan81/apfs-fuse
|
||||||
|
[3] https://developer.apple.com/support/apple-file-system/Apple-File-System-Reference.pdf
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/apfs.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_H
|
||||||
|
#define _APFS_H
|
||||||
|
|
||||||
|
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||||
|
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inode and file operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* file.c */
|
||||||
|
extern const struct file_operations apfs_file_operations;
|
||||||
|
extern const struct inode_operations apfs_file_inode_operations;
|
||||||
|
|
||||||
|
/* namei.c */
|
||||||
|
extern const struct inode_operations apfs_dir_inode_operations;
|
||||||
|
extern const struct inode_operations apfs_special_inode_operations;
|
||||||
|
extern const struct dentry_operations apfs_dentry_operations;
|
||||||
|
|
||||||
|
/* symlink.c */
|
||||||
|
extern const struct inode_operations apfs_symlink_inode_operations;
|
||||||
|
|
||||||
|
#endif /* _APFS_H */
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/btree.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/buffer_head.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "btree.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
#include "super.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_child_from_query - Read the child id found by a successful nonleaf query
|
||||||
|
* @query: the query that found the record
|
||||||
|
* @child: Return parameter. The child id found.
|
||||||
|
*
|
||||||
|
* Reads the child id in the nonleaf node record into @child and performs a
|
||||||
|
* basic sanity check as a protection against crafted filesystems. Returns 0
|
||||||
|
* on success or -EFSCORRUPTED otherwise.
|
||||||
|
*/
|
||||||
|
static int apfs_child_from_query(struct apfs_query *query, u64 *child)
|
||||||
|
{
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
|
||||||
|
if (query->len != 8) /* The data on a nonleaf node is the child id */
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
*child = le64_to_cpup((__le64 *)(raw + query->off));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_omap_lookup_block - Find the block number of a b-tree node from its id
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @tbl: Root of the object map to be searched
|
||||||
|
* @id: id of the node
|
||||||
|
* @block: on return, the found block number
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative error code in case of failure.
|
||||||
|
*/
|
||||||
|
int apfs_omap_lookup_block(struct super_block *sb, struct apfs_node *tbl,
|
||||||
|
u64 id, u64 *block)
|
||||||
|
{
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_query *query;
|
||||||
|
struct apfs_key key;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
query = apfs_alloc_query(tbl, NULL /* parent */);
|
||||||
|
if (!query)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
apfs_init_omap_key(id, sbi->s_xid, &key);
|
||||||
|
query->key = &key;
|
||||||
|
query->flags |= APFS_QUERY_OMAP;
|
||||||
|
|
||||||
|
ret = apfs_btree_query(sb, &query);
|
||||||
|
if (ret)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
ret = apfs_bno_from_query(query, block);
|
||||||
|
if (ret)
|
||||||
|
apfs_alert(sb, "bad object map leaf block: 0x%llx",
|
||||||
|
query->node->object.block_nr);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
apfs_free_query(sb, query);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_alloc_query - Allocates a query structure
|
||||||
|
* @node: node to be searched
|
||||||
|
* @parent: query for the parent node
|
||||||
|
*
|
||||||
|
* Callers other than apfs_btree_query() should set @parent to NULL, and @node
|
||||||
|
* to the root of the b-tree. They should also initialize most of the query
|
||||||
|
* fields themselves; when @parent is not NULL the query will inherit them.
|
||||||
|
*
|
||||||
|
* Returns the allocated query, or NULL in case of failure.
|
||||||
|
*/
|
||||||
|
struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||||
|
struct apfs_query *parent)
|
||||||
|
{
|
||||||
|
struct apfs_query *query;
|
||||||
|
|
||||||
|
query = kmalloc(sizeof(*query), GFP_KERNEL);
|
||||||
|
if (!query)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* To be released by free_query. */
|
||||||
|
apfs_node_get(node);
|
||||||
|
query->node = node;
|
||||||
|
query->key = parent ? parent->key : NULL;
|
||||||
|
query->flags = parent ?
|
||||||
|
parent->flags & ~(APFS_QUERY_DONE | APFS_QUERY_NEXT) : 0;
|
||||||
|
query->parent = parent;
|
||||||
|
/* Start the search with the last record and go backwards */
|
||||||
|
query->index = node->records;
|
||||||
|
query->depth = parent ? parent->depth + 1 : 0;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_free_query - Free a query structure
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @query: query to free
|
||||||
|
*
|
||||||
|
* Also frees the ancestor queries, if they are kept.
|
||||||
|
*/
|
||||||
|
void apfs_free_query(struct super_block *sb, struct apfs_query *query)
|
||||||
|
{
|
||||||
|
while (query) {
|
||||||
|
struct apfs_query *parent = query->parent;
|
||||||
|
|
||||||
|
apfs_node_put(query->node);
|
||||||
|
kfree(query);
|
||||||
|
query = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_btree_query - Execute a query on a b-tree
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @query: the query to execute
|
||||||
|
*
|
||||||
|
* Searches the b-tree starting at @query->index in @query->node, looking for
|
||||||
|
* the record corresponding to @query->key.
|
||||||
|
*
|
||||||
|
* Returns 0 in case of success and sets the @query->len, @query->off and
|
||||||
|
* @query->index fields to the results of the query. @query->node will now
|
||||||
|
* point to the leaf node holding the record.
|
||||||
|
*
|
||||||
|
* In case of failure returns an appropriate error code.
|
||||||
|
*/
|
||||||
|
int apfs_btree_query(struct super_block *sb, struct apfs_query **query)
|
||||||
|
{
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_node *node;
|
||||||
|
struct apfs_query *parent;
|
||||||
|
u64 child_id, child_blk;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
next_node:
|
||||||
|
if ((*query)->depth >= 12) {
|
||||||
|
/*
|
||||||
|
* We need a maximum depth for the tree so we can't loop
|
||||||
|
* forever if the filesystem is damaged. 12 should be more
|
||||||
|
* than enough to map every block.
|
||||||
|
*/
|
||||||
|
apfs_alert(sb, "b-tree is corrupted");
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = apfs_node_query(sb, *query);
|
||||||
|
if (err == -EAGAIN) {
|
||||||
|
if (!(*query)->parent) /* We are at the root of the tree */
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
/* Move back up one level and continue the query */
|
||||||
|
parent = (*query)->parent;
|
||||||
|
(*query)->parent = NULL; /* Don't free the parent */
|
||||||
|
apfs_free_query(sb, *query);
|
||||||
|
*query = parent;
|
||||||
|
goto next_node;
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
if (apfs_node_is_leaf((*query)->node)) /* All done */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err = apfs_child_from_query(*query, &child_id);
|
||||||
|
if (err) {
|
||||||
|
apfs_alert(sb, "bad index block: 0x%llx",
|
||||||
|
(*query)->node->object.block_nr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The omap maps a node id into a block number. The nodes
|
||||||
|
* of the omap itself do not need this translation.
|
||||||
|
*/
|
||||||
|
if ((*query)->flags & APFS_QUERY_OMAP) {
|
||||||
|
child_blk = child_id;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* we are always performing lookup from omap root. Might
|
||||||
|
* need improvement in the future.
|
||||||
|
*/
|
||||||
|
err = apfs_omap_lookup_block(sb, sbi->s_omap_root,
|
||||||
|
child_id, &child_blk);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now go a level deeper and search the child */
|
||||||
|
node = apfs_read_node(sb, child_blk);
|
||||||
|
if (IS_ERR(node))
|
||||||
|
return PTR_ERR(node);
|
||||||
|
|
||||||
|
if (node->object.oid != child_id)
|
||||||
|
apfs_debug(sb, "corrupt b-tree");
|
||||||
|
|
||||||
|
if ((*query)->flags & APFS_QUERY_MULTIPLE) {
|
||||||
|
/*
|
||||||
|
* We are looking for multiple entries, so we must remember
|
||||||
|
* the parent node and index to continue the search later.
|
||||||
|
*/
|
||||||
|
*query = apfs_alloc_query(node, *query);
|
||||||
|
apfs_node_put(node);
|
||||||
|
} else {
|
||||||
|
/* Reuse the same query structure to search the child */
|
||||||
|
apfs_node_put((*query)->node);
|
||||||
|
(*query)->node = node;
|
||||||
|
(*query)->index = node->records;
|
||||||
|
(*query)->depth++;
|
||||||
|
}
|
||||||
|
goto next_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_omap_read_node - Find and read a node from a b-tree
|
||||||
|
* @id: id for the seeked node
|
||||||
|
*
|
||||||
|
* Returns NULL is case of failure, otherwise a pointer to the resulting
|
||||||
|
* apfs_node structure.
|
||||||
|
*/
|
||||||
|
struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id)
|
||||||
|
{
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_node *result;
|
||||||
|
u64 block;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = apfs_omap_lookup_block(sb, sbi->s_omap_root, id, &block);
|
||||||
|
if (err)
|
||||||
|
return ERR_PTR(err);
|
||||||
|
|
||||||
|
result = apfs_read_node(sb, block);
|
||||||
|
if (IS_ERR(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (result->object.oid != id)
|
||||||
|
apfs_debug(sb, "corrupt b-tree");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/btree.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_BTREE_H
|
||||||
|
#define _APFS_BTREE_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
struct super_block;
|
||||||
|
|
||||||
|
/* Flags for the query structure */
|
||||||
|
#define APFS_QUERY_TREE_MASK 0007 /* Which b-tree we query */
|
||||||
|
#define APFS_QUERY_OMAP 0001 /* This is a b-tree object map query */
|
||||||
|
#define APFS_QUERY_CAT 0002 /* This is a catalog tree query */
|
||||||
|
#define APFS_QUERY_NEXT 0010 /* Find next of multiple matches */
|
||||||
|
#define APFS_QUERY_EXACT 0020 /* Search for an exact match */
|
||||||
|
#define APFS_QUERY_DONE 0040 /* The search at this level is over */
|
||||||
|
#define APFS_QUERY_ANY_NAME 0100 /* Multiple search for any name */
|
||||||
|
#define APFS_QUERY_ANY_NUMBER 0200 /* Multiple search for any number */
|
||||||
|
#define APFS_QUERY_MULTIPLE (APFS_QUERY_ANY_NAME | APFS_QUERY_ANY_NUMBER)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure used to retrieve data from an APFS B-Tree. For now only used
|
||||||
|
* on the calalog and the object map.
|
||||||
|
*/
|
||||||
|
struct apfs_query {
|
||||||
|
struct apfs_node *node; /* Node being searched */
|
||||||
|
struct apfs_key *key; /* What the query is looking for */
|
||||||
|
|
||||||
|
struct apfs_query *parent; /* Query for parent node */
|
||||||
|
unsigned int flags;
|
||||||
|
|
||||||
|
/* Set by the query on success */
|
||||||
|
int index; /* Index of the entry in the node */
|
||||||
|
int key_off; /* Offset of the key in the node */
|
||||||
|
int key_len; /* Length of the key */
|
||||||
|
int off; /* Offset of the data in the node */
|
||||||
|
int len; /* Length of the data */
|
||||||
|
|
||||||
|
int depth; /* Put a limit on recursion */
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct apfs_query *apfs_alloc_query(struct apfs_node *node,
|
||||||
|
struct apfs_query *parent);
|
||||||
|
extern void apfs_free_query(struct super_block *sb, struct apfs_query *query);
|
||||||
|
extern int apfs_btree_query(struct super_block *sb, struct apfs_query **query);
|
||||||
|
extern struct apfs_node *apfs_omap_read_node(struct super_block *sb, u64 id);
|
||||||
|
extern int apfs_omap_lookup_block(struct super_block *sb,
|
||||||
|
struct apfs_node *tbl, u64 id, u64 *block);
|
||||||
|
|
||||||
|
#endif /* _APFS_BTREE_H */
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/dir.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/buffer_head.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "btree.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
#include "super.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_drec_from_query - Read the directory record found by a successful query
|
||||||
|
* @query: the query that found the record
|
||||||
|
* @drec: Return parameter. The directory record found.
|
||||||
|
*
|
||||||
|
* Reads the directory record into @drec and performs some basic sanity checks
|
||||||
|
* as a protection against crafted filesystems. Returns 0 on success or
|
||||||
|
* -EFSCORRUPTED otherwise.
|
||||||
|
*
|
||||||
|
* The caller must not free @query while @drec is in use, because @drec->name
|
||||||
|
* points to data on disk.
|
||||||
|
*/
|
||||||
|
int apfs_drec_from_query(struct apfs_query *query, struct apfs_drec *drec)
|
||||||
|
{
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
struct apfs_drec_hashed_key *de_key;
|
||||||
|
struct apfs_drec_val *de;
|
||||||
|
int namelen = query->key_len - sizeof(*de_key);
|
||||||
|
|
||||||
|
if (namelen < 1)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
if (query->len < sizeof(*de))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
de = (struct apfs_drec_val *)(raw + query->off);
|
||||||
|
de_key = (struct apfs_drec_hashed_key *)(raw + query->key_off);
|
||||||
|
|
||||||
|
if (namelen != (le32_to_cpu(de_key->name_len_and_hash) &
|
||||||
|
APFS_DREC_LEN_MASK))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
/* Filename must be NULL-terminated */
|
||||||
|
if (de_key->name[namelen - 1] != 0)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
drec->name = de_key->name;
|
||||||
|
drec->name_len = namelen - 1; /* Don't count the NULL termination */
|
||||||
|
drec->ino = le64_to_cpu(de->file_id);
|
||||||
|
|
||||||
|
drec->type = le16_to_cpu(de->flags) & APFS_DREC_TYPE_MASK;
|
||||||
|
if (drec->type != DT_FIFO && drec->type & 1) /* Invalid file type */
|
||||||
|
drec->type = DT_UNKNOWN;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_inode_by_name - Find the cnid for a given filename
|
||||||
|
* @dir: parent directory
|
||||||
|
* @child: filename
|
||||||
|
* @ino: on return, the inode number found
|
||||||
|
*
|
||||||
|
* Returns 0 and the inode number (which is the cnid of the file
|
||||||
|
* record); otherwise, return the appropriate error code.
|
||||||
|
*/
|
||||||
|
int apfs_inode_by_name(struct inode *dir, const struct qstr *child, u64 *ino)
|
||||||
|
{
|
||||||
|
struct super_block *sb = dir->i_sb;
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_key key;
|
||||||
|
struct apfs_query *query;
|
||||||
|
struct apfs_drec drec;
|
||||||
|
u64 cnid = dir->i_ino;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
apfs_init_drec_hashed_key(sb, cnid, child->name, &key);
|
||||||
|
|
||||||
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||||
|
if (!query)
|
||||||
|
return -ENOMEM;
|
||||||
|
query->key = &key;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Distinct filenames in the same directory may (rarely) share the same
|
||||||
|
* hash. The query code cannot handle that because their order in the
|
||||||
|
* b-tree would depend on their unnormalized original names. Just get
|
||||||
|
* all the candidates and check them one by one.
|
||||||
|
*/
|
||||||
|
query->flags |= APFS_QUERY_CAT | APFS_QUERY_ANY_NAME | APFS_QUERY_EXACT;
|
||||||
|
do {
|
||||||
|
err = apfs_btree_query(sb, &query);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
err = apfs_drec_from_query(query, &drec);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
} while (unlikely(apfs_filename_cmp(sb, child->name, drec.name)));
|
||||||
|
|
||||||
|
*ino = drec.ino;
|
||||||
|
out:
|
||||||
|
apfs_free_query(sb, query);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int apfs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
|
{
|
||||||
|
struct inode *inode = file_inode(file);
|
||||||
|
struct super_block *sb = inode->i_sb;
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_key key;
|
||||||
|
struct apfs_query *query;
|
||||||
|
u64 cnid = inode->i_ino;
|
||||||
|
loff_t pos;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (ctx->pos == 0) {
|
||||||
|
if (!dir_emit_dot(file, ctx))
|
||||||
|
return 0;
|
||||||
|
ctx->pos++;
|
||||||
|
}
|
||||||
|
if (ctx->pos == 1) {
|
||||||
|
if (!dir_emit_dotdot(file, ctx))
|
||||||
|
return 0;
|
||||||
|
ctx->pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||||
|
if (!query)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* We want all the children for the cnid, regardless of the name */
|
||||||
|
apfs_init_drec_hashed_key(sb, cnid, NULL /* name */, &key);
|
||||||
|
query->key = &key;
|
||||||
|
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
|
||||||
|
|
||||||
|
pos = ctx->pos - 2;
|
||||||
|
while (1) {
|
||||||
|
struct apfs_drec drec;
|
||||||
|
/*
|
||||||
|
* We query for the matching records, one by one. After we
|
||||||
|
* pass ctx->pos we begin to emit them.
|
||||||
|
*
|
||||||
|
* TODO: Faster approach for large directories?
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = apfs_btree_query(sb, &query);
|
||||||
|
if (err == -ENODATA) { /* Got all the records */
|
||||||
|
err = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
break;
|
||||||
|
|
||||||
|
err = apfs_drec_from_query(query, &drec);
|
||||||
|
if (err) {
|
||||||
|
apfs_alert(sb, "bad dentry record in directory 0x%llx",
|
||||||
|
cnid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = 0;
|
||||||
|
if (pos <= 0) {
|
||||||
|
if (!dir_emit(ctx, drec.name, drec.name_len,
|
||||||
|
drec.ino, drec.type))
|
||||||
|
break;
|
||||||
|
ctx->pos++;
|
||||||
|
}
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos < 0)
|
||||||
|
ctx->pos -= pos;
|
||||||
|
apfs_free_query(sb, query);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct file_operations apfs_dir_operations = {
|
||||||
|
.llseek = generic_file_llseek,
|
||||||
|
.read = generic_read_dir,
|
||||||
|
.iterate_shared = apfs_readdir,
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/dir.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_DIR_H
|
||||||
|
#define _APFS_DIR_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
struct inode;
|
||||||
|
struct qstr;
|
||||||
|
struct apfs_query;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the value of a directory entry. This is the data in
|
||||||
|
* the catalog nodes for record type APFS_TYPE_DIR_REC.
|
||||||
|
*/
|
||||||
|
struct apfs_drec_val {
|
||||||
|
__le64 file_id;
|
||||||
|
__le64 date_added;
|
||||||
|
__le16 flags;
|
||||||
|
u8 xfields[];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Directory entry record in memory
|
||||||
|
*/
|
||||||
|
struct apfs_drec {
|
||||||
|
u8 *name;
|
||||||
|
u64 ino;
|
||||||
|
int name_len;
|
||||||
|
unsigned int type;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int apfs_drec_from_query(struct apfs_query *query,
|
||||||
|
struct apfs_drec *drec);
|
||||||
|
extern int apfs_inode_by_name(struct inode *dir, const struct qstr *child,
|
||||||
|
u64 *ino);
|
||||||
|
|
||||||
|
extern const struct file_operations apfs_dir_operations;
|
||||||
|
|
||||||
|
#endif /* _APFS_DIR_H */
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/extents.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/buffer_head.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "btree.h"
|
||||||
|
#include "extents.h"
|
||||||
|
#include "inode.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
#include "super.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_extent_from_query - Read the extent found by a successful query
|
||||||
|
* @query: the query that found the record
|
||||||
|
* @extent: Return parameter. The extent found.
|
||||||
|
*
|
||||||
|
* Reads the extent record into @extent and performs some basic sanity checks
|
||||||
|
* as a protection against crafted filesystems. Returns 0 on success or
|
||||||
|
* -EFSCORRUPTED otherwise.
|
||||||
|
*/
|
||||||
|
int apfs_extent_from_query(struct apfs_query *query,
|
||||||
|
struct apfs_file_extent *extent)
|
||||||
|
{
|
||||||
|
struct super_block *sb = query->node->object.sb;
|
||||||
|
struct apfs_file_extent_val *ext;
|
||||||
|
struct apfs_file_extent_key *ext_key;
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
u64 ext_len;
|
||||||
|
|
||||||
|
if (query->len != sizeof(*ext) || query->key_len != sizeof(*ext_key))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
ext = (struct apfs_file_extent_val *)(raw + query->off);
|
||||||
|
ext_key = (struct apfs_file_extent_key *)(raw + query->key_off);
|
||||||
|
ext_len = le64_to_cpu(ext->len_and_flags) & APFS_FILE_EXTENT_LEN_MASK;
|
||||||
|
|
||||||
|
/* Extent length must be a multiple of the block size */
|
||||||
|
if (ext_len & (sb->s_blocksize - 1))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
extent->logical_addr = le64_to_cpu(ext_key->logical_addr);
|
||||||
|
extent->phys_block_num = le64_to_cpu(ext->phys_block_num);
|
||||||
|
extent->len = ext_len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_extent_read - Read the extent record that covers a block
|
||||||
|
* @inode: inode that owns the record
|
||||||
|
* @iblock: logical number of the wanted block
|
||||||
|
* @extent: Return parameter. The extent found.
|
||||||
|
*
|
||||||
|
* Finds and caches the extent record. On success, returns a pointer to the
|
||||||
|
* cache record; on failure, returns an error code.
|
||||||
|
*/
|
||||||
|
static int apfs_extent_read(struct inode *inode, sector_t iblock,
|
||||||
|
struct apfs_file_extent *extent)
|
||||||
|
{
|
||||||
|
struct super_block *sb = inode->i_sb;
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_inode_info *ai = APFS_I(inode);
|
||||||
|
struct apfs_key key;
|
||||||
|
struct apfs_query *query;
|
||||||
|
struct apfs_file_extent *cache = &ai->i_cached_extent;
|
||||||
|
u64 iaddr = iblock << inode->i_blkbits;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
spin_lock(&ai->i_extent_lock);
|
||||||
|
if (iaddr >= cache->logical_addr &&
|
||||||
|
iaddr < cache->logical_addr + cache->len) {
|
||||||
|
*extent = *cache;
|
||||||
|
spin_unlock(&ai->i_extent_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
spin_unlock(&ai->i_extent_lock);
|
||||||
|
|
||||||
|
/* We will search for the extent that covers iblock */
|
||||||
|
apfs_init_file_extent_key(ai->i_extent_id, iaddr, &key);
|
||||||
|
|
||||||
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||||
|
if (!query)
|
||||||
|
return -ENOMEM;
|
||||||
|
query->key = &key;
|
||||||
|
query->flags = APFS_QUERY_CAT;
|
||||||
|
|
||||||
|
ret = apfs_btree_query(sb, &query);
|
||||||
|
if (ret)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
ret = apfs_extent_from_query(query, extent);
|
||||||
|
if (ret) {
|
||||||
|
apfs_alert(sb, "bad extent record for inode 0x%llx",
|
||||||
|
(unsigned long long) inode->i_ino);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock(&ai->i_extent_lock);
|
||||||
|
*cache = *extent;
|
||||||
|
spin_unlock(&ai->i_extent_lock);
|
||||||
|
|
||||||
|
done:
|
||||||
|
apfs_free_query(sb, query);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int apfs_get_block(struct inode *inode, sector_t iblock,
|
||||||
|
struct buffer_head *bh_result, int create)
|
||||||
|
{
|
||||||
|
struct super_block *sb = inode->i_sb;
|
||||||
|
struct apfs_file_extent ext;
|
||||||
|
u64 blk_off, bno, map_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = apfs_extent_read(inode, iblock, &ext);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Find the block offset of iblock within the extent */
|
||||||
|
blk_off = iblock - (ext.logical_addr >> inode->i_blkbits);
|
||||||
|
|
||||||
|
/* Make sure we don't read past the extent boundaries */
|
||||||
|
map_len = ext.len - (blk_off << inode->i_blkbits);
|
||||||
|
if (bh_result->b_size > map_len)
|
||||||
|
bh_result->b_size = map_len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save the requested mapping length as map_bh() replaces it with
|
||||||
|
* the filesystem block size
|
||||||
|
*/
|
||||||
|
map_len = bh_result->b_size;
|
||||||
|
/* Extents representing holes have block number 0 */
|
||||||
|
if (ext.phys_block_num != 0) {
|
||||||
|
/* Find the block number of iblock within the disk */
|
||||||
|
bno = ext.phys_block_num + blk_off;
|
||||||
|
map_bh(bh_result, sb, bno);
|
||||||
|
}
|
||||||
|
|
||||||
|
bh_result->b_size = map_len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/extents.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _EXTENTS_H
|
||||||
|
#define _EXTENTS_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
struct inode;
|
||||||
|
struct buffer_head;
|
||||||
|
struct apfs_query;
|
||||||
|
|
||||||
|
/* File extent records */
|
||||||
|
#define APFS_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffULL
|
||||||
|
#define APFS_FILE_EXTENT_FLAG_MASK 0xff00000000000000ULL
|
||||||
|
#define APFS_FILE_EXTENT_FLAG_SHIFT 56
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of a file extent record
|
||||||
|
*/
|
||||||
|
struct apfs_file_extent_val {
|
||||||
|
__le64 len_and_flags;
|
||||||
|
__le64 phys_block_num;
|
||||||
|
__le64 crypto_id;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extent record data in memory
|
||||||
|
*/
|
||||||
|
struct apfs_file_extent {
|
||||||
|
u64 logical_addr;
|
||||||
|
u64 phys_block_num;
|
||||||
|
u64 len;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int apfs_extent_from_query(struct apfs_query *query,
|
||||||
|
struct apfs_file_extent *extent);
|
||||||
|
extern int apfs_get_block(struct inode *inode, sector_t iblock,
|
||||||
|
struct buffer_head *bh_result, int create);
|
||||||
|
|
||||||
|
#endif /* _EXTENTS_H */
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/file.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "inode.h"
|
||||||
|
#include "xattr.h"
|
||||||
|
|
||||||
|
const struct file_operations apfs_file_operations = {
|
||||||
|
.llseek = generic_file_llseek,
|
||||||
|
.read_iter = generic_file_read_iter,
|
||||||
|
.mmap = generic_file_readonly_mmap,
|
||||||
|
.open = generic_file_open,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct inode_operations apfs_file_inode_operations = {
|
||||||
|
.getattr = apfs_getattr,
|
||||||
|
.listxattr = apfs_listxattr,
|
||||||
|
};
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/inode.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/buffer_head.h>
|
||||||
|
#include <linux/mpage.h>
|
||||||
|
#include <asm/div64.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "btree.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "extents.h"
|
||||||
|
#include "inode.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
#include "super.h"
|
||||||
|
#include "xattr.h"
|
||||||
|
|
||||||
|
static int apfs_readpage(struct file *file, struct page *page)
|
||||||
|
{
|
||||||
|
return mpage_readpage(page, apfs_get_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int apfs_readpages(struct file *file, struct address_space *mapping,
|
||||||
|
struct list_head *pages, unsigned int nr_pages)
|
||||||
|
{
|
||||||
|
return mpage_readpages(mapping, pages, nr_pages, apfs_get_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
static sector_t apfs_bmap(struct address_space *mapping, sector_t block)
|
||||||
|
{
|
||||||
|
return generic_block_bmap(mapping, block, apfs_get_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct address_space_operations apfs_aops = {
|
||||||
|
.readpage = apfs_readpage,
|
||||||
|
.readpages = apfs_readpages,
|
||||||
|
.bmap = apfs_bmap,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_inode_from_query - Read the inode found by a successful query
|
||||||
|
* @query: the query that found the record
|
||||||
|
* @inode: vfs inode to be filled with the read data
|
||||||
|
*
|
||||||
|
* Reads the inode record into @inode and performs some basic sanity checks,
|
||||||
|
* mostly as a protection against crafted filesystems. Returns 0 on success
|
||||||
|
* or a negative error code otherwise.
|
||||||
|
*/
|
||||||
|
static int apfs_inode_from_query(struct apfs_query *query, struct inode *inode)
|
||||||
|
{
|
||||||
|
struct apfs_inode_info *ai = APFS_I(inode);
|
||||||
|
struct apfs_inode_val *inode_val;
|
||||||
|
struct apfs_dstream *dstream = NULL;
|
||||||
|
struct apfs_xf_blob *xblob;
|
||||||
|
struct apfs_x_field *xfield;
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
int rest, i;
|
||||||
|
u64 secs;
|
||||||
|
|
||||||
|
if (query->len < sizeof(*inode_val))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
inode_val = (struct apfs_inode_val *)(raw + query->off);
|
||||||
|
|
||||||
|
ai->i_extent_id = le64_to_cpu(inode_val->private_id);
|
||||||
|
inode->i_mode = le16_to_cpu(inode_val->mode);
|
||||||
|
i_uid_write(inode, (uid_t)le32_to_cpu(inode_val->owner));
|
||||||
|
i_gid_write(inode, (gid_t)le32_to_cpu(inode_val->group));
|
||||||
|
|
||||||
|
if (S_ISREG(inode->i_mode) || S_ISLNK(inode->i_mode)) {
|
||||||
|
/*
|
||||||
|
* It seems that hard links are only allowed for regular files,
|
||||||
|
* and perhaps for symlinks.
|
||||||
|
*
|
||||||
|
* Directory inodes don't store their link count, so to provide
|
||||||
|
* it we would have to actually count the subdirectories. The
|
||||||
|
* HFS/HFS+ modules just leave it at 1, and so do we, for now.
|
||||||
|
*/
|
||||||
|
set_nlink(inode, le32_to_cpu(inode_val->nlink));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* APFS stores the time as unsigned nanoseconds since the epoch */
|
||||||
|
secs = le64_to_cpu(inode_val->access_time);
|
||||||
|
inode->i_atime.tv_nsec = do_div(secs, NSEC_PER_SEC);
|
||||||
|
inode->i_atime.tv_sec = secs;
|
||||||
|
secs = le64_to_cpu(inode_val->change_time);
|
||||||
|
inode->i_ctime.tv_nsec = do_div(secs, NSEC_PER_SEC);
|
||||||
|
inode->i_ctime.tv_sec = secs;
|
||||||
|
secs = le64_to_cpu(inode_val->mod_time);
|
||||||
|
inode->i_mtime.tv_nsec = do_div(secs, NSEC_PER_SEC);
|
||||||
|
inode->i_mtime.tv_sec = secs;
|
||||||
|
secs = le64_to_cpu(inode_val->create_time);
|
||||||
|
ai->i_crtime.tv_nsec = do_div(secs, NSEC_PER_SEC);
|
||||||
|
ai->i_crtime.tv_sec = secs;
|
||||||
|
|
||||||
|
xblob = (struct apfs_xf_blob *) inode_val->xfields;
|
||||||
|
xfield = (struct apfs_x_field *) xblob->xf_data;
|
||||||
|
rest = query->len - (sizeof(*inode_val) + sizeof(*xblob));
|
||||||
|
rest -= le16_to_cpu(xblob->xf_num_exts) * sizeof(xfield[0]);
|
||||||
|
if (rest < 0)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
for (i = 0; i < le16_to_cpu(xblob->xf_num_exts); ++i) {
|
||||||
|
int attrlen;
|
||||||
|
|
||||||
|
/* Attribute length is padded to a multiple of 8 */
|
||||||
|
attrlen = round_up(le16_to_cpu(xfield[i].x_size), 8);
|
||||||
|
if (attrlen > rest)
|
||||||
|
break;
|
||||||
|
if (xfield[i].x_type == APFS_INO_EXT_TYPE_DSTREAM) {
|
||||||
|
/* The only optional attr we care about, for now */
|
||||||
|
dstream = (struct apfs_dstream *)
|
||||||
|
((char *)inode_val + query->len - rest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rest -= attrlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dstream) {
|
||||||
|
inode->i_size = le64_to_cpu(dstream->size);
|
||||||
|
inode->i_blocks = le64_to_cpu(dstream->alloced_size) >> 9;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* This inode is "empty", but it may actually hold compressed
|
||||||
|
* data in the named attribute com.apple.decmpfs, and sometimes
|
||||||
|
* in com.apple.ResourceFork
|
||||||
|
*/
|
||||||
|
inode->i_size = inode->i_blocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_inode_lookup - Lookup an inode record in the b-tree and read its data
|
||||||
|
* @inode: vfs inode to lookup and fill
|
||||||
|
*
|
||||||
|
* Queries the b-tree for the @inode->i_ino inode record and reads its data to
|
||||||
|
* @inode. Returns 0 on success or a negative error code otherwise.
|
||||||
|
*/
|
||||||
|
static int apfs_inode_lookup(struct inode *inode)
|
||||||
|
{
|
||||||
|
struct super_block *sb = inode->i_sb;
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct apfs_key key;
|
||||||
|
struct apfs_query *query;
|
||||||
|
u64 cnid = inode->i_ino;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
apfs_init_inode_key(cnid, &key);
|
||||||
|
|
||||||
|
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
|
||||||
|
if (!query)
|
||||||
|
return -ENOMEM;
|
||||||
|
query->key = &key;
|
||||||
|
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
|
||||||
|
|
||||||
|
ret = apfs_btree_query(sb, &query);
|
||||||
|
if (ret)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
ret = apfs_inode_from_query(query, inode);
|
||||||
|
if (ret)
|
||||||
|
apfs_alert(sb, "bad inode record for inode 0x%llx", cnid);
|
||||||
|
|
||||||
|
done:
|
||||||
|
apfs_free_query(sb, query);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BITS_PER_LONG == 64
|
||||||
|
#define apfs_iget_locked iget_locked
|
||||||
|
#else /* 64-bit inode numbers may not fit in the vfs inode */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_test_inode - Check if the inode matches a 64-bit inode number
|
||||||
|
* @inode: inode to test
|
||||||
|
* @cnid: pointer to the inode number
|
||||||
|
*/
|
||||||
|
static int apfs_test_inode(struct inode *inode, void *cnid)
|
||||||
|
{
|
||||||
|
struct apfs_inode_info *ai = APFS_I(inode);
|
||||||
|
u64 *ino = cnid;
|
||||||
|
|
||||||
|
return ai->i_ino == *ino;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_set_inode - Set a 64-bit inode number on the given inode
|
||||||
|
* @inode: inode to set
|
||||||
|
* @cnid: pointer to the inode number
|
||||||
|
*/
|
||||||
|
static int apfs_set_inode(struct inode *inode, void *cnid)
|
||||||
|
{
|
||||||
|
struct apfs_inode_info *ai = APFS_I(inode);
|
||||||
|
u64 *ino = cnid;
|
||||||
|
|
||||||
|
ai->i_ino = *ino;
|
||||||
|
inode->i_ino = *ino; /* Just discard the higher bits here... */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_iget_locked - Wrapper for iget5_locked()
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @cnid: 64-bit inode number
|
||||||
|
*
|
||||||
|
* Works the same as iget_locked(), but supports 64-bit inode numbers.
|
||||||
|
*/
|
||||||
|
static struct inode *apfs_iget_locked(struct super_block *sb, u64 cnid)
|
||||||
|
{
|
||||||
|
return iget5_locked(sb, cnid, apfs_test_inode, apfs_set_inode, &cnid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* BITS_PER_LONG == 64 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_iget - Populate inode structures with metadata from disk
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @cnid: inode number
|
||||||
|
*
|
||||||
|
* Populates the vfs inode and the corresponding apfs_inode_info structure.
|
||||||
|
* Returns a pointer to the vfs inode in case of success, or an appropriate
|
||||||
|
* error pointer otherwise.
|
||||||
|
*/
|
||||||
|
struct inode *apfs_iget(struct super_block *sb, u64 cnid)
|
||||||
|
{
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct inode *inode;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
inode = apfs_iget_locked(sb, cnid);
|
||||||
|
if (!inode)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
if (!(inode->i_state & I_NEW))
|
||||||
|
return inode;
|
||||||
|
|
||||||
|
err = apfs_inode_lookup(inode);
|
||||||
|
if (err) {
|
||||||
|
iget_failed(inode);
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow the user to override the ownership */
|
||||||
|
if (sbi->s_flags & APFS_UID_OVERRIDE)
|
||||||
|
inode->i_uid = sbi->s_uid;
|
||||||
|
if (sbi->s_flags & APFS_GID_OVERRIDE)
|
||||||
|
inode->i_gid = sbi->s_gid;
|
||||||
|
|
||||||
|
/* A lot of operations still missing, of course */
|
||||||
|
if (S_ISREG(inode->i_mode)) {
|
||||||
|
inode->i_op = &apfs_file_inode_operations;
|
||||||
|
inode->i_fop = &apfs_file_operations;
|
||||||
|
inode->i_mapping->a_ops = &apfs_aops;
|
||||||
|
} else if (S_ISDIR(inode->i_mode)) {
|
||||||
|
inode->i_op = &apfs_dir_inode_operations;
|
||||||
|
inode->i_fop = &apfs_dir_operations;
|
||||||
|
} else if (S_ISLNK(inode->i_mode)) {
|
||||||
|
inode->i_op = &apfs_symlink_inode_operations;
|
||||||
|
} else {
|
||||||
|
inode->i_op = &apfs_special_inode_operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inode flags are not important for now, leave them at 0 */
|
||||||
|
unlock_new_inode(inode);
|
||||||
|
return inode;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) /* No statx yet... */
|
||||||
|
|
||||||
|
int apfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||||
|
struct kstat *stat)
|
||||||
|
{
|
||||||
|
struct inode *inode = d_inode(dentry);
|
||||||
|
|
||||||
|
generic_fillattr(inode, stat);
|
||||||
|
#if BITS_PER_LONG == 32
|
||||||
|
stat->ino = ai->i_ino;
|
||||||
|
#endif /* BITS_PER_LONG == 32 */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) */
|
||||||
|
|
||||||
|
int apfs_getattr(const struct path *path, struct kstat *stat,
|
||||||
|
u32 request_mask, unsigned int query_flags)
|
||||||
|
{
|
||||||
|
struct inode *inode = d_inode(path->dentry);
|
||||||
|
struct apfs_inode_info *ai = APFS_I(inode);
|
||||||
|
|
||||||
|
stat->result_mask |= STATX_BTIME;
|
||||||
|
stat->btime = ai->i_crtime;
|
||||||
|
|
||||||
|
if (apfs_xattr_get(inode, APFS_XATTR_NAME_COMPRESSED, NULL, 0) >= 0)
|
||||||
|
stat->attributes |= STATX_ATTR_COMPRESSED;
|
||||||
|
|
||||||
|
stat->attributes_mask |= STATX_ATTR_COMPRESSED;
|
||||||
|
|
||||||
|
generic_fillattr(inode, stat);
|
||||||
|
#if BITS_PER_LONG == 32
|
||||||
|
stat->ino = ai->i_ino;
|
||||||
|
#endif /* BITS_PER_LONG == 32 */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) */
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/inode.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_INODE_H
|
||||||
|
#define _APFS_INODE_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include "extents.h"
|
||||||
|
|
||||||
|
/* Inode numbers for special inodes */
|
||||||
|
#define APFS_INVALID_INO_NUM 0
|
||||||
|
|
||||||
|
#define APFS_ROOT_DIR_PARENT 1 /* Root directory parent */
|
||||||
|
#define APFS_ROOT_DIR_INO_NUM 2 /* Root directory */
|
||||||
|
#define APFS_PRIV_DIR_INO_NUM 3 /* Private directory */
|
||||||
|
#define APFS_SNAP_DIR_INO_NUM 6 /* Snapshots metadata */
|
||||||
|
|
||||||
|
/* Smallest inode number available for user content */
|
||||||
|
#define APFS_MIN_USER_INO_NUM 16
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of an inode as stored as a B-tree value
|
||||||
|
*/
|
||||||
|
struct apfs_inode_val {
|
||||||
|
/*00*/ __le64 parent_id;
|
||||||
|
__le64 private_id;
|
||||||
|
/*10*/ __le64 create_time;
|
||||||
|
__le64 mod_time;
|
||||||
|
__le64 change_time;
|
||||||
|
__le64 access_time;
|
||||||
|
/*30*/ __le64 internal_flags;
|
||||||
|
union {
|
||||||
|
__le32 nchildren;
|
||||||
|
__le32 nlink;
|
||||||
|
};
|
||||||
|
__le32 default_protection_class;
|
||||||
|
/*40*/ __le32 write_generation_counter;
|
||||||
|
__le32 bsd_flags;
|
||||||
|
__le32 owner;
|
||||||
|
__le32 group;
|
||||||
|
/*50*/ __le16 mode;
|
||||||
|
__le16 pad1;
|
||||||
|
__le64 pad2;
|
||||||
|
/*5C*/ u8 xfields[];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/* Extended field types */
|
||||||
|
#define APFS_DREC_EXT_TYPE_SIBLING_ID 1
|
||||||
|
|
||||||
|
#define APFS_INO_EXT_TYPE_SNAP_XID 1
|
||||||
|
#define APFS_INO_EXT_TYPE_DELTA_TREE_OID 2
|
||||||
|
#define APFS_INO_EXT_TYPE_DOCUMENT_ID 3
|
||||||
|
#define APFS_INO_EXT_TYPE_NAME 4
|
||||||
|
#define APFS_INO_EXT_TYPE_PREV_FSIZE 5
|
||||||
|
#define APFS_INO_EXT_TYPE_RESERVED_6 6
|
||||||
|
#define APFS_INO_EXT_TYPE_FINDER_INFO 7
|
||||||
|
#define APFS_INO_EXT_TYPE_DSTREAM 8
|
||||||
|
#define APFS_INO_EXT_TYPE_RESERVED_9 9
|
||||||
|
#define APFS_INO_EXT_TYPE_DIR_STATS_KEY 10
|
||||||
|
#define APFS_INO_EXT_TYPE_FS_UUID 11
|
||||||
|
#define APFS_INO_EXT_TYPE_RESERVED_12 12
|
||||||
|
#define APFS_INO_EXT_TYPE_SPARSE_BYTES 13
|
||||||
|
#define APFS_INO_EXT_TYPE_RDEV 14
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure used to store the number and size of extended fields of an inode
|
||||||
|
*/
|
||||||
|
struct apfs_xf_blob {
|
||||||
|
__le16 xf_num_exts;
|
||||||
|
__le16 xf_used_data;
|
||||||
|
u8 xf_data[];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure used to store an inode's extended field
|
||||||
|
*/
|
||||||
|
struct apfs_x_field {
|
||||||
|
u8 x_type;
|
||||||
|
u8 x_flags;
|
||||||
|
__le16 x_size;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of a data stream record
|
||||||
|
*/
|
||||||
|
struct apfs_dstream_id_val {
|
||||||
|
__le32 refcnt;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure used to store information about a data stream
|
||||||
|
*/
|
||||||
|
struct apfs_dstream {
|
||||||
|
__le64 size;
|
||||||
|
__le64 alloced_size;
|
||||||
|
__le64 default_crypto_id;
|
||||||
|
__le64 total_bytes_written;
|
||||||
|
__le64 total_bytes_read;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* APFS inode data in memory
|
||||||
|
*/
|
||||||
|
struct apfs_inode_info {
|
||||||
|
u64 i_extent_id; /* ID of the extent records */
|
||||||
|
struct apfs_file_extent i_cached_extent; /* Latest extent record */
|
||||||
|
spinlock_t i_extent_lock; /* Protects i_cached_extent */
|
||||||
|
struct timespec64 i_crtime; /* Time of creation */
|
||||||
|
|
||||||
|
#if BITS_PER_LONG == 32
|
||||||
|
/* This is the actual inode number; vfs_inode.i_ino could overflow */
|
||||||
|
u64 i_ino;
|
||||||
|
#endif
|
||||||
|
struct inode vfs_inode;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline struct apfs_inode_info *APFS_I(struct inode *inode)
|
||||||
|
{
|
||||||
|
return container_of(inode, struct apfs_inode_info, vfs_inode);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern struct inode *apfs_iget(struct super_block *sb, u64 cnid);
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) /* No statx yet... */
|
||||||
|
extern int apfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||||
|
struct kstat *stat);
|
||||||
|
#else
|
||||||
|
extern int apfs_getattr(const struct path *path, struct kstat *stat,
|
||||||
|
u32 request_mask, unsigned int query_flags);
|
||||||
|
#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) */
|
||||||
|
|
||||||
|
#endif /* _APFS_INODE_H */
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/key.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/crc32c.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "super.h"
|
||||||
|
#include "unicode.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_cat_type - Read the record type of a catalog key
|
||||||
|
* @key: the raw catalog key
|
||||||
|
*
|
||||||
|
* The record type is stored in the last byte of the cnid field; this function
|
||||||
|
* returns that value.
|
||||||
|
*/
|
||||||
|
static inline int apfs_cat_type(struct apfs_key_header *key)
|
||||||
|
{
|
||||||
|
return (le64_to_cpu(key->obj_id_and_type) & APFS_OBJ_TYPE_MASK)
|
||||||
|
>> APFS_OBJ_TYPE_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_cat_cnid - Read the cnid value on a catalog key
|
||||||
|
* @key: the raw catalog key
|
||||||
|
*
|
||||||
|
* The cnid value shares the its field with the record type. This function
|
||||||
|
* masks that part away and returns the result.
|
||||||
|
*/
|
||||||
|
static inline u64 apfs_cat_cnid(struct apfs_key_header *key)
|
||||||
|
{
|
||||||
|
return le64_to_cpu(key->obj_id_and_type) & APFS_OBJ_ID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_filename_cmp - Normalize and compare two APFS filenames
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @name1, @name2: names to compare
|
||||||
|
*
|
||||||
|
* returns 0 if @name1 and @name2 are equal
|
||||||
|
* < 0 if @name1 comes before @name2
|
||||||
|
* > 0 if @name1 comes after @name2
|
||||||
|
*/
|
||||||
|
int apfs_filename_cmp(struct super_block *sb,
|
||||||
|
const char *name1, const char *name2)
|
||||||
|
{
|
||||||
|
struct apfs_unicursor cursor1, cursor2;
|
||||||
|
bool case_fold = apfs_is_case_insensitive(sb);
|
||||||
|
|
||||||
|
apfs_init_unicursor(&cursor1, name1);
|
||||||
|
apfs_init_unicursor(&cursor2, name2);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
unicode_t uni1, uni2;
|
||||||
|
|
||||||
|
uni1 = apfs_normalize_next(&cursor1, case_fold);
|
||||||
|
uni2 = apfs_normalize_next(&cursor2, case_fold);
|
||||||
|
|
||||||
|
if (uni1 != uni2)
|
||||||
|
return uni1 < uni2 ? -1 : 1;
|
||||||
|
if (!uni1)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_keycmp - Compare two keys
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @k1, @k2: keys to compare
|
||||||
|
*
|
||||||
|
* returns 0 if @k1 and @k2 are equal
|
||||||
|
* < 0 if @k1 comes before @k2 in the btree
|
||||||
|
* > 0 if @k1 comes after @k2 in the btree
|
||||||
|
*/
|
||||||
|
int apfs_keycmp(struct super_block *sb,
|
||||||
|
struct apfs_key *k1, struct apfs_key *k2)
|
||||||
|
{
|
||||||
|
if (k1->id != k2->id)
|
||||||
|
return k1->id < k2->id ? -1 : 1;
|
||||||
|
if (k1->type != k2->type)
|
||||||
|
return k1->type < k2->type ? -1 : 1;
|
||||||
|
if (k1->number != k2->number)
|
||||||
|
return k1->number < k2->number ? -1 : 1;
|
||||||
|
if (!k1->name)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Normalization seems to be ignored here, even for directory records */
|
||||||
|
return strcmp(k1->name, k2->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_read_cat_key - Parse an on-disk catalog key
|
||||||
|
* @raw: pointer to the raw key
|
||||||
|
* @size: size of the raw key
|
||||||
|
* @key: apfs_key structure to store the result
|
||||||
|
*
|
||||||
|
* Returns 0 on success, or a negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int apfs_read_cat_key(void *raw, int size, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
if (size < sizeof(struct apfs_key_header))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
key->id = apfs_cat_cnid((struct apfs_key_header *)raw);
|
||||||
|
key->type = apfs_cat_type((struct apfs_key_header *)raw);
|
||||||
|
|
||||||
|
switch (key->type) {
|
||||||
|
case APFS_TYPE_DIR_REC:
|
||||||
|
if (size < sizeof(struct apfs_drec_hashed_key) + 1 ||
|
||||||
|
*((char *)raw + size - 1) != 0) {
|
||||||
|
/* Filename must have NULL-termination */
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name length is not used in key comparisons, only the hash */
|
||||||
|
key->number = le32_to_cpu(
|
||||||
|
((struct apfs_drec_hashed_key *)raw)->name_len_and_hash) &
|
||||||
|
APFS_DREC_HASH_MASK;
|
||||||
|
|
||||||
|
key->name = ((struct apfs_drec_hashed_key *)raw)->name;
|
||||||
|
break;
|
||||||
|
case APFS_TYPE_XATTR:
|
||||||
|
if (size < sizeof(struct apfs_xattr_key) + 1 ||
|
||||||
|
*((char *)raw + size - 1) != 0) {
|
||||||
|
/* xattr name must have NULL-termination */
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
|
key->number = 0;
|
||||||
|
key->name = ((struct apfs_xattr_key *)raw)->name;
|
||||||
|
break;
|
||||||
|
case APFS_TYPE_FILE_EXTENT:
|
||||||
|
if (size != sizeof(struct apfs_file_extent_key))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
key->number = le64_to_cpu(
|
||||||
|
((struct apfs_file_extent_key *)raw)->logical_addr);
|
||||||
|
key->name = NULL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
key->number = 0;
|
||||||
|
key->name = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_read_omap_key - Parse an on-disk object map key
|
||||||
|
* @raw: pointer to the raw key
|
||||||
|
* @size: size of the raw key
|
||||||
|
* @key: apfs_key structure to store the result
|
||||||
|
*
|
||||||
|
* Returns 0 on success, or a negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int apfs_read_omap_key(void *raw, int size, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
if (size < sizeof(struct apfs_omap_key))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
key->id = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_oid);
|
||||||
|
key->type = 0;
|
||||||
|
key->number = le64_to_cpu(((struct apfs_omap_key *)raw)->ok_xid);
|
||||||
|
key->name = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_init_drec_hashed_key - Initialize an in-memory key for a dentry query
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @ino: inode number of the parent directory
|
||||||
|
* @name: filename (NULL for a multiple query)
|
||||||
|
* @key: apfs_key structure to initialize
|
||||||
|
*/
|
||||||
|
void apfs_init_drec_hashed_key(struct super_block *sb, u64 ino,
|
||||||
|
const char *name, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
struct apfs_unicursor cursor;
|
||||||
|
bool case_fold = apfs_is_case_insensitive(sb);
|
||||||
|
u32 hash = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
key->id = ino;
|
||||||
|
key->type = APFS_TYPE_DIR_REC;
|
||||||
|
|
||||||
|
/* To respect normalization, queries can only consider the hash */
|
||||||
|
key->name = NULL;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
key->number = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apfs_init_unicursor(&cursor, name);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
unicode_t utf32;
|
||||||
|
|
||||||
|
utf32 = apfs_normalize_next(&cursor, case_fold);
|
||||||
|
if (!utf32)
|
||||||
|
break;
|
||||||
|
|
||||||
|
hash = crc32c(hash, &utf32, sizeof(utf32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The filename length doesn't matter, so it's left as zero */
|
||||||
|
key->number = hash << APFS_DREC_HASH_SHIFT;
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/key.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_KEY_H
|
||||||
|
#define _APFS_KEY_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
struct super_block;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of a key in an object map B-tree
|
||||||
|
*/
|
||||||
|
struct apfs_omap_key {
|
||||||
|
__le64 ok_oid;
|
||||||
|
__le64 ok_xid;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/* Catalog records types */
|
||||||
|
enum {
|
||||||
|
APFS_TYPE_ANY = 0,
|
||||||
|
APFS_TYPE_SNAP_METADATA = 1,
|
||||||
|
APFS_TYPE_EXTENT = 2,
|
||||||
|
APFS_TYPE_INODE = 3,
|
||||||
|
APFS_TYPE_XATTR = 4,
|
||||||
|
APFS_TYPE_SIBLING_LINK = 5,
|
||||||
|
APFS_TYPE_DSTREAM_ID = 6,
|
||||||
|
APFS_TYPE_CRYPTO_STATE = 7,
|
||||||
|
APFS_TYPE_FILE_EXTENT = 8,
|
||||||
|
APFS_TYPE_DIR_REC = 9,
|
||||||
|
APFS_TYPE_DIR_STATS = 10,
|
||||||
|
APFS_TYPE_SNAP_NAME = 11,
|
||||||
|
APFS_TYPE_SIBLING_MAP = 12,
|
||||||
|
APFS_TYPE_MAX_VALID = 12,
|
||||||
|
APFS_TYPE_MAX = 15,
|
||||||
|
APFS_TYPE_INVALID = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Bit masks for the 'obj_id_and_type' field of a key header */
|
||||||
|
#define APFS_OBJ_ID_MASK 0x0fffffffffffffffULL
|
||||||
|
#define APFS_OBJ_TYPE_MASK 0xf000000000000000ULL
|
||||||
|
#define APFS_OBJ_TYPE_SHIFT 60
|
||||||
|
|
||||||
|
/* Key header for filesystem-object keys */
|
||||||
|
struct apfs_key_header {
|
||||||
|
__le64 obj_id_and_type;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the key for an inode record
|
||||||
|
*/
|
||||||
|
struct apfs_inode_key {
|
||||||
|
struct apfs_key_header hdr;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the key for a file extent record
|
||||||
|
*/
|
||||||
|
struct apfs_file_extent_key {
|
||||||
|
struct apfs_key_header hdr;
|
||||||
|
__le64 logical_addr;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the key for a data stream record
|
||||||
|
*/
|
||||||
|
struct apfs_dstream_id_key {
|
||||||
|
struct apfs_key_header hdr;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/* Bit masks for the 'name_len_and_hash' field of a directory entry */
|
||||||
|
#define APFS_DREC_LEN_MASK 0x000003ff
|
||||||
|
#define APFS_DREC_HASH_MASK 0xfffffc00
|
||||||
|
#define APFS_DREC_HASH_SHIFT 10
|
||||||
|
|
||||||
|
/* The name length in the catalog key counts the terminating null byte. */
|
||||||
|
#define APFS_NAME_LEN (APFS_DREC_LEN_MASK - 1)
|
||||||
|
|
||||||
|
/* Bit masks for the 'type' field of a directory entry */
|
||||||
|
enum {
|
||||||
|
APFS_DREC_TYPE_MASK = 0x000f,
|
||||||
|
APFS_DREC_RESERVED_10 = 0x0010
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the key for a directory entry, including a precomputed
|
||||||
|
* hash of its name
|
||||||
|
*/
|
||||||
|
struct apfs_drec_hashed_key {
|
||||||
|
struct apfs_key_header hdr;
|
||||||
|
__le32 name_len_and_hash;
|
||||||
|
u8 name[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structure of the key for an extended attributes record
|
||||||
|
*/
|
||||||
|
struct apfs_xattr_key {
|
||||||
|
struct apfs_key_header hdr;
|
||||||
|
__le16 name_len;
|
||||||
|
u8 name[0];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In-memory representation of a key, as relevant for a b-tree query.
|
||||||
|
*/
|
||||||
|
struct apfs_key {
|
||||||
|
u64 id;
|
||||||
|
u64 number; /* Extent offset, name hash or transaction id */
|
||||||
|
const char *name; /* On-disk name string */
|
||||||
|
u8 type; /* Record type (0 for the omap) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_init_omap_key - Initialize an in-memory key for an omap query
|
||||||
|
* @oid: object id
|
||||||
|
* @xid: latest transaction id
|
||||||
|
* @key: apfs_key structure to initialize
|
||||||
|
*/
|
||||||
|
static inline void apfs_init_omap_key(u64 oid, u64 xid, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
key->id = oid;
|
||||||
|
key->type = 0;
|
||||||
|
key->number = xid;
|
||||||
|
key->name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_init_inode_key - Initialize an in-memory key for an inode query
|
||||||
|
* @ino: inode number
|
||||||
|
* @key: apfs_key structure to initialize
|
||||||
|
*/
|
||||||
|
static inline void apfs_init_inode_key(u64 ino, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
key->id = ino;
|
||||||
|
key->type = APFS_TYPE_INODE;
|
||||||
|
key->number = 0;
|
||||||
|
key->name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_init_file_extent_key - Initialize an in-memory key for an extent query
|
||||||
|
* @id: extent id
|
||||||
|
* @offset: logical address (0 for a multiple query)
|
||||||
|
* @key: apfs_key structure to initialize
|
||||||
|
*/
|
||||||
|
static inline void apfs_init_file_extent_key(u64 id, u64 offset,
|
||||||
|
struct apfs_key *key)
|
||||||
|
{
|
||||||
|
key->id = id;
|
||||||
|
key->type = APFS_TYPE_FILE_EXTENT;
|
||||||
|
key->number = offset;
|
||||||
|
key->name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void apfs_init_drec_hashed_key(struct super_block *sb, u64 ino,
|
||||||
|
const char *name, struct apfs_key *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_init_xattr_key - Initialize an in-memory key for a xattr query
|
||||||
|
* @ino: inode number of the parent file
|
||||||
|
* @name: xattr name (NULL for a multiple query)
|
||||||
|
* @key: apfs_key structure to initialize
|
||||||
|
*/
|
||||||
|
static inline void apfs_init_xattr_key(u64 ino, const char *name,
|
||||||
|
struct apfs_key *key)
|
||||||
|
{
|
||||||
|
key->id = ino;
|
||||||
|
key->type = APFS_TYPE_XATTR;
|
||||||
|
key->number = 0;
|
||||||
|
key->name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int apfs_filename_cmp(struct super_block *sb,
|
||||||
|
const char *name1, const char *name2);
|
||||||
|
extern int apfs_keycmp(struct super_block *sb,
|
||||||
|
struct apfs_key *k1, struct apfs_key *k2);
|
||||||
|
extern int apfs_read_cat_key(void *raw, int size, struct apfs_key *key);
|
||||||
|
extern int apfs_read_omap_key(void *raw, int size, struct apfs_key *key);
|
||||||
|
|
||||||
|
#endif /* _APFS_KEY_H */
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/message.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include "message.h"
|
||||||
|
|
||||||
|
void apfs_msg(struct super_block *sb, const char *prefix, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
struct va_format vaf;
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
vaf.fmt = fmt;
|
||||||
|
vaf.va = &args;
|
||||||
|
|
||||||
|
printk("%sAPFS (%s): %pV\n", prefix, sb->s_id, &vaf);
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/message.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _APFS_MESSAGE_H
|
||||||
|
#define _APFS_MESSAGE_H
|
||||||
|
|
||||||
|
struct super_block;
|
||||||
|
|
||||||
|
extern __printf(3, 4)
|
||||||
|
void apfs_msg(struct super_block *sb, const char *prefix, const char *fmt, ...);
|
||||||
|
|
||||||
|
#define apfs_emerg(sb, fmt, ...) apfs_msg(sb, KERN_EMERG, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_alert(sb, fmt, ...) apfs_msg(sb, KERN_ALERT, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_crit(sb, fmt, ...) apfs_msg(sb, KERN_CRIT, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_err(sb, fmt, ...) apfs_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_warn(sb, fmt, ...) apfs_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_notice(sb, fmt, ...) apfs_msg(sb, KERN_NOTICE, fmt, ##__VA_ARGS__)
|
||||||
|
#define apfs_info(sb, fmt, ...) apfs_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#ifdef CONFIG_APFS_DEBUG
|
||||||
|
#define apfs_debug(sb, fmt, ...) apfs_msg(sb, KERN_DEBUG, fmt, ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define apfs_debug(sb, fmt, ...) no_printk(fmt, ##__VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _APFS_MESSAGE_H */
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/namei.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "inode.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "super.h"
|
||||||
|
#include "unicode.h"
|
||||||
|
#include "xattr.h"
|
||||||
|
|
||||||
|
static struct dentry *apfs_lookup(struct inode *dir, struct dentry *dentry,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
struct inode *inode = NULL;
|
||||||
|
u64 ino = 0;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (dentry->d_name.len > APFS_NAME_LEN)
|
||||||
|
return ERR_PTR(-ENAMETOOLONG);
|
||||||
|
|
||||||
|
err = apfs_inode_by_name(dir, &dentry->d_name, &ino);
|
||||||
|
if (err && err != -ENODATA)
|
||||||
|
return ERR_PTR(err);
|
||||||
|
|
||||||
|
if (!err) {
|
||||||
|
inode = apfs_iget(dir->i_sb, ino);
|
||||||
|
if (IS_ERR(inode))
|
||||||
|
return ERR_CAST(inode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return d_splice_alias(inode, dentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct inode_operations apfs_dir_inode_operations = {
|
||||||
|
.lookup = apfs_lookup,
|
||||||
|
.getattr = apfs_getattr,
|
||||||
|
.listxattr = apfs_listxattr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct inode_operations apfs_special_inode_operations = {
|
||||||
|
.getattr = apfs_getattr,
|
||||||
|
.listxattr = apfs_listxattr,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int apfs_dentry_hash(const struct dentry *dir, struct qstr *child)
|
||||||
|
{
|
||||||
|
struct apfs_unicursor cursor;
|
||||||
|
unsigned long hash;
|
||||||
|
bool case_fold = apfs_is_case_insensitive(dir->d_sb);
|
||||||
|
|
||||||
|
apfs_init_unicursor(&cursor, child->name);
|
||||||
|
hash = init_name_hash(dir);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int i;
|
||||||
|
unicode_t utf32;
|
||||||
|
|
||||||
|
utf32 = apfs_normalize_next(&cursor, case_fold);
|
||||||
|
if (!utf32)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Hash the unicode character one byte at a time */
|
||||||
|
for (i = 0; i < 4; ++i) {
|
||||||
|
hash = partial_name_hash((u8)utf32, hash);
|
||||||
|
utf32 = utf32 >> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child->hash = end_name_hash(hash);
|
||||||
|
|
||||||
|
/* TODO: return error instead of truncating invalid UTF-8? */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int apfs_dentry_compare(const struct dentry *dentry, unsigned int len,
|
||||||
|
const char *str, const struct qstr *name)
|
||||||
|
{
|
||||||
|
return apfs_filename_cmp(dentry->d_sb, name->name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct dentry_operations apfs_dentry_operations = {
|
||||||
|
.d_hash = apfs_dentry_hash,
|
||||||
|
.d_compare = apfs_dentry_compare,
|
||||||
|
};
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* linux/fs/apfs/node.c
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/buffer_head.h>
|
||||||
|
#include "apfs.h"
|
||||||
|
#include "btree.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "super.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_node_is_valid - Check basic sanity of the node index
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @node: node to check
|
||||||
|
*
|
||||||
|
* Verifies that the node index fits in a single block, and that the number
|
||||||
|
* of records fits in the index. Without this check a crafted filesystem could
|
||||||
|
* pretend to have too many records, and calls to apfs_node_locate_key() and
|
||||||
|
* apfs_node_locate_data() would read beyond the limits of the node.
|
||||||
|
*/
|
||||||
|
static bool apfs_node_is_valid(struct super_block *sb,
|
||||||
|
struct apfs_node *node)
|
||||||
|
{
|
||||||
|
int records = node->records;
|
||||||
|
int index_size = node->key - sizeof(struct apfs_btree_node_phys);
|
||||||
|
int entry_size;
|
||||||
|
|
||||||
|
if (!records) /* Empty nodes could keep a multiple query spinning */
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (node->key > sb->s_blocksize)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
entry_size = (apfs_node_has_fixed_kv_size(node)) ?
|
||||||
|
sizeof(struct apfs_kvoff) : sizeof(struct apfs_kvloc);
|
||||||
|
|
||||||
|
return records * entry_size <= index_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apfs_node_release(struct kref *kref)
|
||||||
|
{
|
||||||
|
struct apfs_node *node =
|
||||||
|
container_of(kref, struct apfs_node, refcount);
|
||||||
|
|
||||||
|
brelse(node->object.bh);
|
||||||
|
kfree(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apfs_node_get(struct apfs_node *node)
|
||||||
|
{
|
||||||
|
kref_get(&node->refcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apfs_node_put(struct apfs_node *node)
|
||||||
|
{
|
||||||
|
kref_put(&node->refcount, apfs_node_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_read_node - Read a node header from disk
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @block: number of the block where the node is stored
|
||||||
|
*
|
||||||
|
* Returns ERR_PTR in case of failure, otherwise return a pointer to the
|
||||||
|
* resulting apfs_node structure with the initial reference taken.
|
||||||
|
*
|
||||||
|
* For now we assume the node has not been read before.
|
||||||
|
*/
|
||||||
|
struct apfs_node *apfs_read_node(struct super_block *sb, u64 block)
|
||||||
|
{
|
||||||
|
struct apfs_sb_info *sbi = APFS_SB(sb);
|
||||||
|
struct buffer_head *bh;
|
||||||
|
struct apfs_btree_node_phys *raw;
|
||||||
|
struct apfs_node *node;
|
||||||
|
|
||||||
|
bh = sb_bread(sb, block);
|
||||||
|
if (!bh) {
|
||||||
|
apfs_err(sb, "unable to read node");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
raw = (struct apfs_btree_node_phys *) bh->b_data;
|
||||||
|
|
||||||
|
node = kmalloc(sizeof(*node), GFP_KERNEL);
|
||||||
|
if (!node) {
|
||||||
|
brelse(bh);
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
node->flags = le16_to_cpu(raw->btn_flags);
|
||||||
|
node->records = le32_to_cpu(raw->btn_nkeys);
|
||||||
|
node->key = sizeof(*raw) + le16_to_cpu(raw->btn_table_space.off)
|
||||||
|
+ le16_to_cpu(raw->btn_table_space.len);
|
||||||
|
node->free = node->key + le16_to_cpu(raw->btn_free_space.off);
|
||||||
|
node->data = node->free + le16_to_cpu(raw->btn_free_space.len);
|
||||||
|
|
||||||
|
node->object.sb = sb;
|
||||||
|
node->object.block_nr = block;
|
||||||
|
node->object.oid = le64_to_cpu(raw->btn_o.o_oid);
|
||||||
|
node->object.bh = bh;
|
||||||
|
|
||||||
|
kref_init(&node->refcount);
|
||||||
|
|
||||||
|
if (sbi->s_flags & APFS_CHECK_NODES &&
|
||||||
|
!apfs_obj_verify_csum(sb, &raw->btn_o)) {
|
||||||
|
apfs_alert(sb, "bad checksum for node in block 0x%llx", block);
|
||||||
|
apfs_node_put(node);
|
||||||
|
return ERR_PTR(-EFSBADCRC);
|
||||||
|
}
|
||||||
|
if (!apfs_node_is_valid(sb, node)) {
|
||||||
|
apfs_alert(sb, "bad node in block 0x%llx", block);
|
||||||
|
apfs_node_put(node);
|
||||||
|
return ERR_PTR(-EFSCORRUPTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_node_locate_key - Locate the key of a node record
|
||||||
|
* @node: node to be searched
|
||||||
|
* @index: number of the entry to locate
|
||||||
|
* @off: on return will hold the offset in the block
|
||||||
|
*
|
||||||
|
* Returns the length of the key, or 0 in case of failure. The function checks
|
||||||
|
* that this length fits within the block; callers must use the returned value
|
||||||
|
* to make sure they never operate outside its bounds.
|
||||||
|
*/
|
||||||
|
static int apfs_node_locate_key(struct apfs_node *node, int index, int *off)
|
||||||
|
{
|
||||||
|
struct super_block *sb = node->object.sb;
|
||||||
|
struct apfs_btree_node_phys *raw;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (index >= node->records)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
|
||||||
|
if (apfs_node_has_fixed_kv_size(node)) {
|
||||||
|
struct apfs_kvoff *entry;
|
||||||
|
|
||||||
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
||||||
|
len = 16;
|
||||||
|
/* Translate offset in key area to offset in block */
|
||||||
|
*off = node->key + le16_to_cpu(entry->k);
|
||||||
|
} else {
|
||||||
|
/* These node types have variable length keys and data */
|
||||||
|
struct apfs_kvloc *entry;
|
||||||
|
|
||||||
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
||||||
|
len = le16_to_cpu(entry->k.len);
|
||||||
|
/* Translate offset in key area to offset in block */
|
||||||
|
*off = node->key + le16_to_cpu(entry->k.off);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*off + len > sb->s_blocksize) {
|
||||||
|
/* Avoid out-of-bounds read if corrupted */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_node_locate_data - Locate the data of a node record
|
||||||
|
* @node: node to be searched
|
||||||
|
* @index: number of the entry to locate
|
||||||
|
* @off: on return will hold the offset in the block
|
||||||
|
*
|
||||||
|
* Returns the length of the data, or 0 in case of failure. The function checks
|
||||||
|
* that this length fits within the block; callers must use the returned value
|
||||||
|
* to make sure they never operate outside its bounds.
|
||||||
|
*/
|
||||||
|
static int apfs_node_locate_data(struct apfs_node *node, int index, int *off)
|
||||||
|
{
|
||||||
|
struct super_block *sb = node->object.sb;
|
||||||
|
struct apfs_btree_node_phys *raw;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (index >= node->records)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
raw = (struct apfs_btree_node_phys *)node->object.bh->b_data;
|
||||||
|
if (apfs_node_has_fixed_kv_size(node)) {
|
||||||
|
/* These node types have fixed length keys and data */
|
||||||
|
struct apfs_kvoff *entry;
|
||||||
|
|
||||||
|
entry = (struct apfs_kvoff *)raw->btn_data + index;
|
||||||
|
/* Node type decides length */
|
||||||
|
len = apfs_node_is_leaf(node) ? 16 : 8;
|
||||||
|
/*
|
||||||
|
* Data offsets are counted backwards from the end of the
|
||||||
|
* block, or from the beginning of the footer when it exists
|
||||||
|
*/
|
||||||
|
if (apfs_node_is_root(node)) /* has footer */
|
||||||
|
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
|
||||||
|
- le16_to_cpu(entry->v);
|
||||||
|
else
|
||||||
|
*off = sb->s_blocksize - le16_to_cpu(entry->v);
|
||||||
|
} else {
|
||||||
|
/* These node types have variable length keys and data */
|
||||||
|
struct apfs_kvloc *entry;
|
||||||
|
|
||||||
|
entry = (struct apfs_kvloc *)raw->btn_data + index;
|
||||||
|
len = le16_to_cpu(entry->v.len);
|
||||||
|
/*
|
||||||
|
* Data offsets are counted backwards from the end of the
|
||||||
|
* block, or from the beginning of the footer when it exists
|
||||||
|
*/
|
||||||
|
if (apfs_node_is_root(node)) /* has footer */
|
||||||
|
*off = sb->s_blocksize - sizeof(struct apfs_btree_info)
|
||||||
|
- le16_to_cpu(entry->v.off);
|
||||||
|
else
|
||||||
|
*off = sb->s_blocksize - le16_to_cpu(entry->v.off);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*off < 0 || *off + len > sb->s_blocksize) {
|
||||||
|
/* Avoid out-of-bounds read if corrupted */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_key_from_query - Read the current key from a query structure
|
||||||
|
* @query: the query, with @query->key_off and @query->key_len already set
|
||||||
|
* @key: return parameter for the key
|
||||||
|
*
|
||||||
|
* Reads the key into @key and performs some basic sanity checks as a
|
||||||
|
* protection against crafted filesystems. Returns 0 on success or a
|
||||||
|
* negative error code otherwise.
|
||||||
|
*/
|
||||||
|
static int apfs_key_from_query(struct apfs_query *query, struct apfs_key *key)
|
||||||
|
{
|
||||||
|
struct super_block *sb = query->node->object.sb;
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
void *raw_key = (void *)(raw + query->key_off);
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
switch (query->flags & APFS_QUERY_TREE_MASK) {
|
||||||
|
case APFS_QUERY_CAT:
|
||||||
|
err = apfs_read_cat_key(raw_key, query->key_len, key);
|
||||||
|
break;
|
||||||
|
case APFS_QUERY_OMAP:
|
||||||
|
err = apfs_read_omap_key(raw_key, query->key_len, key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Not implemented yet */
|
||||||
|
err = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
apfs_alert(sb, "bad node key in block 0x%llx",
|
||||||
|
query->node->object.block_nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A multiple query must ignore some of these fields */
|
||||||
|
if (query->flags & APFS_QUERY_ANY_NAME)
|
||||||
|
key->name = NULL;
|
||||||
|
if (query->flags & APFS_QUERY_ANY_NUMBER)
|
||||||
|
key->number = 0;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_node_next - Find the next matching record in the current node
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @query: multiple query in execution
|
||||||
|
*
|
||||||
|
* Returns 0 on success, -EAGAIN if the next record is in another node,
|
||||||
|
* -ENODATA if no more matching records exist, or another negative error
|
||||||
|
* code in case of failure.
|
||||||
|
*/
|
||||||
|
static int apfs_node_next(struct super_block *sb, struct apfs_query *query)
|
||||||
|
{
|
||||||
|
struct apfs_node *node = query->node;
|
||||||
|
struct apfs_key curr_key;
|
||||||
|
int cmp, err;
|
||||||
|
|
||||||
|
if (query->flags & APFS_QUERY_DONE)
|
||||||
|
/* Nothing left to search; the query failed */
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
if (!query->index) /* The next record may be in another node */
|
||||||
|
return -EAGAIN;
|
||||||
|
--query->index;
|
||||||
|
|
||||||
|
query->key_len = apfs_node_locate_key(node, query->index,
|
||||||
|
&query->key_off);
|
||||||
|
err = apfs_key_from_query(query, &curr_key);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
cmp = apfs_keycmp(sb, &curr_key, query->key);
|
||||||
|
|
||||||
|
if (cmp > 0) /* Records are out of order */
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
if (cmp != 0 && apfs_node_is_leaf(node) &&
|
||||||
|
query->flags & APFS_QUERY_EXACT)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||||
|
if (query->len == 0)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
if (cmp != 0) {
|
||||||
|
/*
|
||||||
|
* This is the last entry that can be relevant in this node.
|
||||||
|
* Keep searching the children, but don't return to this level.
|
||||||
|
*/
|
||||||
|
query->flags |= APFS_QUERY_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_node_query - Execute a query on a single node
|
||||||
|
* @sb: filesystem superblock
|
||||||
|
* @query: the query to execute
|
||||||
|
*
|
||||||
|
* The search will start at index @query->index, looking for the key that comes
|
||||||
|
* right before @query->key, according to the order given by apfs_keycmp().
|
||||||
|
*
|
||||||
|
* The @query->index will be updated to the last index checked. This is
|
||||||
|
* important when searching for multiple entries, since the query may need
|
||||||
|
* to remember where it was on this level. If we are done with this node, the
|
||||||
|
* query will be flagged as APFS_QUERY_DONE, and the search will end in failure
|
||||||
|
* as soon as we return to this level. The function may also return -EAGAIN,
|
||||||
|
* to signal that the search should go on in a different branch.
|
||||||
|
*
|
||||||
|
* On success returns 0; the offset of the data within the block will be saved
|
||||||
|
* in @query->off, and its length in @query->len. The function checks that this
|
||||||
|
* length fits within the block; callers must use the returned value to make
|
||||||
|
* sure they never operate outside its bounds.
|
||||||
|
*
|
||||||
|
* -ENODATA will be returned if no appropriate entry was found, -EFSCORRUPTED
|
||||||
|
* in case of corruption.
|
||||||
|
*/
|
||||||
|
int apfs_node_query(struct super_block *sb, struct apfs_query *query)
|
||||||
|
{
|
||||||
|
struct apfs_node *node = query->node;
|
||||||
|
int left, right;
|
||||||
|
int cmp;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (query->flags & APFS_QUERY_NEXT)
|
||||||
|
return apfs_node_next(sb, query);
|
||||||
|
|
||||||
|
/* Search by bisection */
|
||||||
|
cmp = 1;
|
||||||
|
left = 0;
|
||||||
|
do {
|
||||||
|
struct apfs_key curr_key;
|
||||||
|
if (cmp > 0) {
|
||||||
|
right = query->index - 1;
|
||||||
|
if (right < left)
|
||||||
|
return -ENODATA;
|
||||||
|
query->index = (left + right) / 2;
|
||||||
|
} else {
|
||||||
|
left = query->index;
|
||||||
|
query->index = DIV_ROUND_UP(left + right, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
query->key_len = apfs_node_locate_key(node, query->index,
|
||||||
|
&query->key_off);
|
||||||
|
err = apfs_key_from_query(query, &curr_key);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
cmp = apfs_keycmp(sb, &curr_key, query->key);
|
||||||
|
if (cmp == 0 && !(query->flags & APFS_QUERY_MULTIPLE))
|
||||||
|
break;
|
||||||
|
} while (left != right);
|
||||||
|
|
||||||
|
if (cmp > 0)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
if (cmp != 0 && apfs_node_is_leaf(query->node) &&
|
||||||
|
query->flags & APFS_QUERY_EXACT)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
if (query->flags & APFS_QUERY_MULTIPLE) {
|
||||||
|
if (cmp != 0) /* Last relevant entry in level */
|
||||||
|
query->flags |= APFS_QUERY_DONE;
|
||||||
|
query->flags |= APFS_QUERY_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
query->len = apfs_node_locate_data(node, query->index, &query->off);
|
||||||
|
if (query->len == 0)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apfs_bno_from_query - Read the block number found by a successful omap query
|
||||||
|
* @query: the query that found the record
|
||||||
|
* @bno: Return parameter. The block number found.
|
||||||
|
*
|
||||||
|
* Reads the block number in the omap record into @bno and performs a basic
|
||||||
|
* sanity check as a protection against crafted filesystems. Returns 0 on
|
||||||
|
* success or -EFSCORRUPTED otherwise.
|
||||||
|
*/
|
||||||
|
int apfs_bno_from_query(struct apfs_query *query, u64 *bno)
|
||||||
|
{
|
||||||
|
struct apfs_omap_val *omap_val;
|
||||||
|
char *raw = query->node->object.bh->b_data;
|
||||||
|
|
||||||
|
if (query->len != sizeof(*omap_val))
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
omap_val = (struct apfs_omap_val *)(raw + query->off);
|
||||||
|
*bno = le64_to_cpu(omap_val->ov_paddr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user