mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
Add files via upload
This commit is contained in:
parent
9114b2bb2b
commit
afa6bf1b98
340
include/libusbhsfs/LICENSE_GPLv2+.md
Normal file
340
include/libusbhsfs/LICENSE_GPLv2+.md
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin Street, 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 Street, 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.
|
340
include/libusbhsfs/LICENSE_GPLv2.md
Normal file
340
include/libusbhsfs/LICENSE_GPLv2.md
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
51 Franklin Street, 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 Street, 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.
|
7
include/libusbhsfs/LICENSE_ISC.md
Normal file
7
include/libusbhsfs/LICENSE_ISC.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
Copyright (c) 2020-2021, XorTroll.
|
||||||
|
Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
211
include/libusbhsfs/Makefile
Normal file
211
include/libusbhsfs/Makefile
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
.SUFFIXES:
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEVKITPRO)),)
|
||||||
|
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(DEVKITPRO)/libnx/switch_rules
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# TARGET is the name of the output
|
||||||
|
# SOURCES is a list of directories containing source code
|
||||||
|
# DATA is a list of directories containing data files
|
||||||
|
# INCLUDES is a list of directories containing header files
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
TARGET := usbhsfs
|
||||||
|
SOURCES := source source/fatfs source/sxos
|
||||||
|
DATA := data
|
||||||
|
INCLUDES := include
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# options for code generation
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec
|
||||||
|
|
||||||
|
CFLAGS := -g -Wall -Wextra -Werror -Wno-implicit-fallthrough -Wno-unused-function -ffunction-sections -fdata-sections $(ARCH) $(BUILD_CFLAGS) $(INCLUDE)
|
||||||
|
CFLAGS += -DLIB_TITLE=\"lib$(TARGET)\"
|
||||||
|
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||||
|
|
||||||
|
ASFLAGS := -g $(ARCH)
|
||||||
|
|
||||||
|
ifeq ($(filter $(MAKECMDGOALS),clean dist-src fs-libs),)
|
||||||
|
# Check BUILD_TYPE flag
|
||||||
|
ifneq ($(origin BUILD_TYPE),undefined)
|
||||||
|
ifeq (${BUILD_TYPE},ISC)
|
||||||
|
# Do nothing
|
||||||
|
else
|
||||||
|
ifeq (${BUILD_TYPE},GPL)
|
||||||
|
# Update sources, set GPL_BUILD definition
|
||||||
|
# We'll just assume the user has already installed the necessary libraries
|
||||||
|
SOURCES += source/ntfs-3g source/lwext4
|
||||||
|
CFLAGS += -DGPL_BUILD
|
||||||
|
else
|
||||||
|
$(error Invalid value for BUILD_TYPE flag. Expected ISC or GPL)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
$(error BUILD_TYPE flag not set)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
# include and lib
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
|
# rules for different file extensions
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||||
|
|
||||||
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# use CXX for linking C++ projects, CC for standard C
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifeq ($(strip $(CPPFILES)),)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CC)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CXX)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||||
|
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
|
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||||
|
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||||
|
|
||||||
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
|
-I$(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
.PHONY: clean all release release-dir debug debug-dir lib-dir example
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
LIB_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
LIB_HASH := $(shell git rev-parse --short HEAD)
|
||||||
|
LIB_REV := $(LIB_BRANCH)-$(LIB_HASH)
|
||||||
|
|
||||||
|
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||||
|
LIB_REV := $(LIB_REV)-dirty
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(eval LIB_VERSION_MAJOR = $(shell grep 'define LIBUSBHSFS_VERSION_MAJOR\b' include/usbhsfs.h | tr -s [:blank:] | cut -d' ' -f3))
|
||||||
|
$(eval LIB_VERSION_MINOR = $(shell grep 'define LIBUSBHSFS_VERSION_MINOR\b' include/usbhsfs.h | tr -s [:blank:] | cut -d' ' -f3))
|
||||||
|
$(eval LIB_VERSION_MICRO = $(shell grep 'define LIBUSBHSFS_VERSION_MICRO\b' include/usbhsfs.h | tr -s [:blank:] | cut -d' ' -f3))
|
||||||
|
$(eval LIB_VERSION = $(LIB_VERSION_MAJOR).$(LIB_VERSION_MINOR).$(LIB_VERSION_MICRO)-$(LIB_REV))
|
||||||
|
|
||||||
|
ifeq (${BUILD_TYPE},ISC)
|
||||||
|
LIB_LICENSE := ISC
|
||||||
|
else
|
||||||
|
LIB_LICENSE := GPLv2
|
||||||
|
ifeq ($(MAKECMDGOALS),install)
|
||||||
|
lib/lib$(TARGET).a: fs-libs
|
||||||
|
lib/lib$(TARGET)d.a: fs-libs
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: release debug
|
||||||
|
|
||||||
|
release: lib/lib$(TARGET).a
|
||||||
|
|
||||||
|
release-dir:
|
||||||
|
@mkdir -p release
|
||||||
|
|
||||||
|
debug: lib/lib$(TARGET)d.a
|
||||||
|
|
||||||
|
debug-dir:
|
||||||
|
@mkdir -p debug
|
||||||
|
|
||||||
|
lib-dir:
|
||||||
|
@mkdir -p lib
|
||||||
|
|
||||||
|
example: all
|
||||||
|
@$(MAKE) BUILD_TYPE=${BUILD_TYPE} --no-print-directory -C example
|
||||||
|
|
||||||
|
fs-libs:
|
||||||
|
@echo Installing ntfs-3g
|
||||||
|
@$(MAKE) -C libntfs-3g
|
||||||
|
|
||||||
|
@echo Installing lwext4
|
||||||
|
@$(MAKE) -C liblwext4
|
||||||
|
|
||||||
|
lib/lib$(TARGET).a: release-dir lib-dir $(SOURCES) $(INCLUDES)
|
||||||
|
@echo release
|
||||||
|
@$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \
|
||||||
|
BUILD_CFLAGS="-DNDEBUG=1 -O2" \
|
||||||
|
DEPSDIR=$(CURDIR)/release \
|
||||||
|
--no-print-directory -C release \
|
||||||
|
-f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
lib/lib$(TARGET)d.a: debug-dir lib-dir $(SOURCES) $(INCLUDES)
|
||||||
|
@echo debug
|
||||||
|
@$(MAKE) BUILD=debug OUTPUT=$(CURDIR)/$@ \
|
||||||
|
BUILD_CFLAGS="-DDEBUG=1 -Og" \
|
||||||
|
DEPSDIR=$(CURDIR)/debug \
|
||||||
|
--no-print-directory -C debug \
|
||||||
|
-f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
dist-bin: example
|
||||||
|
@cp example/libusbhsfs-example.nro libusbhsfs-example.nro
|
||||||
|
@tar --exclude=*~ -cjf lib$(TARGET)_$(LIB_VERSION)_$(LIB_LICENSE).tar.bz2 include lib LICENSE_$(LIB_LICENSE).md README.md libusbhsfs-example.nro
|
||||||
|
@rm libusbhsfs-example.nro
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo clean ...
|
||||||
|
@rm -fr release debug lib *.bz2
|
||||||
|
@$(MAKE) --no-print-directory -C example clean
|
||||||
|
|
||||||
|
dist-src:
|
||||||
|
@tar --exclude=*~ -cjf lib$(TARGET)_$(LIB_VERSION)-src.tar.bz2 \
|
||||||
|
--exclude='example/build' --exclude='example/*.elf' --exclude='example/*.nacp' --exclude='example/*.nro' \
|
||||||
|
--exclude='libntfs-3g/*.tgz' --exclude='libntfs-3g/*.tar.*' --exclude='libntfs-3g/pkg' --exclude='libntfs-3g/src' \
|
||||||
|
--exclude='liblwext4/*.zip' --exclude='liblwext4/*.tar.*' --exclude='liblwext4/pkg' --exclude='liblwext4/src' \
|
||||||
|
example include libntfs-3g liblwext4 source LICENSE_ISC.md LICENSE_GPLv2.md Makefile README.md
|
||||||
|
|
||||||
|
dist: dist-src dist-bin
|
||||||
|
|
||||||
|
install: dist-bin
|
||||||
|
@bzip2 -cd lib$(TARGET)_$(LIB_VERSION)_$(LIB_LICENSE).tar.bz2 | tar -xf - -C $(PORTLIBS) --exclude='*.md' --exclude='*.nro'
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
|
||||||
|
DEPENDS := $(OFILES:.o=.d)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# main targets
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
$(OUTPUT) : $(OFILES)
|
||||||
|
|
||||||
|
$(OFILES_SRC) : $(HFILES)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
%_bin.h %.bin.o : %.bin
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
@echo $(notdir $<)
|
||||||
|
@$(bin2o)
|
||||||
|
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
|
|
344
include/libusbhsfs/README.md
Normal file
344
include/libusbhsfs/README.md
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
# libusbhsfs
|
||||||
|
USB Mass Storage Class Host + Filesystem Mounter static library for Nintendo Switch homebrew applications.
|
||||||
|
|
||||||
|
Main features
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* Supports USB Mass Storage (UMS) devices that implement at least one USB interface descriptor with the following properties:
|
||||||
|
* bInterfaceClass: 0x08 (USB Mass Storage Class).
|
||||||
|
* bInterfaceSubClass: 0x06 (SCSI Transparent Command Set SubClass).
|
||||||
|
* bInterfaceProtocol: 0x50 (Bulk-Only Transport [BOT] Protocol).
|
||||||
|
* Bulk-Only Transport (BOT) driver written from scratch, which implements the most common SCSI Primary Command Set (SPC) commands as well as BOT class-specific requests.
|
||||||
|
* Supported SPC commands:
|
||||||
|
* TEST UNIT READY (0x00).
|
||||||
|
* REQUEST SENSE (0x03).
|
||||||
|
* INQUIRY (0x12).
|
||||||
|
* MODE SENSE (6) (0x1A).
|
||||||
|
* START STOP UNIT (0x1B).
|
||||||
|
* PREVENT ALLOW MEDIUM REMOVAL (0x1E).
|
||||||
|
* READ CAPACITY (10) (0x25).
|
||||||
|
* READ (10) (0x28).
|
||||||
|
* WRITE (10) (0x2A).
|
||||||
|
* MODE SENSE (10) (0x5A).
|
||||||
|
* READ (16) (0x88).
|
||||||
|
* WRITE (16) (0x8A).
|
||||||
|
* SERVICE ACTION IN (0x9E).
|
||||||
|
* Supported SERVICE ACTION IN actions:
|
||||||
|
* READ CAPACITY (16) (0x10).
|
||||||
|
* Supported BOT class-specific requests:
|
||||||
|
* Get Max LUN (0xFE).
|
||||||
|
* Bulk-Only Mass Storage Reset (0xFF).
|
||||||
|
* Supports UMS devices with long logical block addresses (64-bit LBAs) and variable logical block sizes (512 - 4096 bytes).
|
||||||
|
* Background thread that takes care of starting all available logical units from each newly connected UMS device, as well as mounting the available filesystems from each one whenever possible.
|
||||||
|
* Supported partitioning schemes:
|
||||||
|
* Super Floppy Drive (SFD) (Volume Boot Record @ LBA 0).
|
||||||
|
* Master Boot Record (MBR).
|
||||||
|
* Extended Boot Record (EBR).
|
||||||
|
* GUID Partition Table (GPT) + protective MBR.
|
||||||
|
* Supported filesystems:
|
||||||
|
* FAT12/FAT16/FAT32/exFAT (via FatFs).
|
||||||
|
* NTFS (via NTFS-3G).
|
||||||
|
* EXT2/3/4 (via lwext4).
|
||||||
|
* Completely possible to add support for additional filesystems, as long as their libraries are ported over to Switch.
|
||||||
|
* Uses devoptab virtual device interface to provide a way to use standard I/O calls from libc (e.g. `fopen()`, `opendir()`, etc.) on mounted filesystems from the available logical units.
|
||||||
|
* Easy to use library interface:
|
||||||
|
* Provides an autoclear user event that is signaled each time a status change is detected by the background thread (new device mounted, device removed).
|
||||||
|
* Painless listing of mounted partitions using a simple struct that provides the devoptab device name, as well as other interesting information (filesystem index, filesystem type, write protection, raw logical unit capacity, etc.).
|
||||||
|
* Provides a way to safely unmount UMS devices at runtime.
|
||||||
|
* Supports the `usbfs` service from SX OS.
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* Bulk-Only Transport (BOT) driver:
|
||||||
|
* Up to 32 different USB Mass Storage Class interfaces can be used at the same time. Increasing this limit isn't harmful, but makes the library take up additional heap memory.
|
||||||
|
* Only a single SCSI operation can be performed at any given time per UMS device, regardless of their number of logical units. This is an official limitation of the BOT protocol. Mutexes are used to avoid multiple SCSI operations from taking place at the same time on the same UMS device.
|
||||||
|
* Filesystem libraries:
|
||||||
|
* FatFs:
|
||||||
|
* Up to 64 FAT volumes can be mounted at the same time across all available UMS devices. Original limit was 10, but FatFs was slightly modified to allow for more volumes to be mounted simultaneously.
|
||||||
|
* NTFS-3G:
|
||||||
|
* Crypto operations aren't supported.
|
||||||
|
* Security contexts are always ignored.
|
||||||
|
* Only partial journaling is supported, so unexpected crashes or power loss can leave the mounted NTFS volume in an inconsistent state. In cases where there has been heavy activity prior to the crash or power loss, it is recommended to plug the UMS device into a Windows PC and let it replay the journal properly before remounting with NTFS-3G, in order to prevent possible data loss and/or corruption.
|
||||||
|
* Symbolic links are transparent. This means that when a symbolic link in encountered, its hard link will be used instead.
|
||||||
|
* lwext4:
|
||||||
|
* Up to 8 EXT volumes can be mounted at the same time across all available UMS devices. This is because lwext4 uses an internal, stack-based registry of mount points and block devices, and increasing the limit can potentially exhaust the stack memory from the thread libusbhsfs runs under.
|
||||||
|
* For the rest of the limitations, please take a look at the [README](https://github.com/gkostka/lwext4/blob/master/README.md) from the lwext4 repository.
|
||||||
|
* Stack and/or heap memory consumption:
|
||||||
|
* This library is *not* suitable for custom sysmodules and/or service MITM projects. It allocates a 8 MiB buffer per each UMS device, which is used for command and data transfers. It also relies heavily on libnx features, which are not always compatible with sysmodule/MITM program contexts.
|
||||||
|
* Switch-specific FS features:
|
||||||
|
* Concatenation files aren't supported.
|
||||||
|
* `usbfs` service from SX OS:
|
||||||
|
* Only a single FAT volume from a single drive can be mounted. No other filesystem types are supported.
|
||||||
|
* Relative paths aren't supported.
|
||||||
|
* `chdir()`, `rename()`, `dirreset()` and `utimes()` aren't supported.
|
||||||
|
* There are probably other limitations we don't even know about, due to the closed-source nature of this CFW.
|
||||||
|
|
||||||
|
Licensing
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Dual licensing is provided for this project depending on the way it is built:
|
||||||
|
|
||||||
|
* If the library is built using the `BUILD_TYPE=ISC` parameter with `make`, it is distributed under the terms of the ISC License. You can find a copy of this license in the [LICENSE_ISC.md file](https://github.com/DarkMatterCore/libusbhsfs/blob/main/LICENSE_ISC.md).
|
||||||
|
* ISC licensed builds only provide support for FAT filesystems via FatFs, which is licensed under the [FatFs license](http://elm-chan.org/fsw/ff/doc/appnote.html#license).
|
||||||
|
* If the library is built using the `BUILD_TYPE=GPL` parameter with `make`, it is distributed 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. You can find a copy of this license in the [LICENSE_GPLv2+.md file](https://github.com/DarkMatterCore/libusbhsfs/blob/main/LICENSE_GPLv2+.md). GPLv2+ licensed builds provide support for:
|
||||||
|
* FAT filesystems via FatFs, which is licensed under the [FatFs license](http://elm-chan.org/fsw/ff/doc/appnote.html#license).
|
||||||
|
* NTFS via NTFS-3G, which is licensed under the [GPLv2+ license](https://github.com/tuxera/ntfs-3g/blob/edge/COPYING).
|
||||||
|
* EXT filesystems via lwext4, which is licensed under the [GPLv2 license](https://github.com/gkostka/lwext4/blob/master/LICENSE).
|
||||||
|
|
||||||
|
How to install
|
||||||
|
--------------
|
||||||
|
|
||||||
|
This section assumes you've already installed devkitA64, libnx and devkitPro pacman. If not, please follow the steps from the [devkitPro wiki](https://devkitpro.org/wiki/Getting_Started).
|
||||||
|
|
||||||
|
* **ISC licensed build**: run `make BUILD_TYPE=ISC install` on the root directory from the project.
|
||||||
|
|
||||||
|
* **GPLv2+ licensed build**:
|
||||||
|
1. Run `make fs-libs` on the root directory from the project to manually build and install the NTFS-3G and lwext4 libraries into the `portlibs` directory from devkitPro.
|
||||||
|
2. Run `make BUILD_TYPE=GPL install` afterwards.
|
||||||
|
|
||||||
|
Regardless of the build type you choose, libusbhsfs will be installed into the `portlibs` directory from devkitPro, and it'll be ready to use by any homebrew application.
|
||||||
|
|
||||||
|
If you use the GPLv2+ licensed build, please note that in order to potentially speed up the building process, the NTFS-3G and lwext4 libraries \**are not*\* compiled every time libusbhsfs itself is built, thus making it necessary to manually run `make fs-libs` every time the libraries are updated within libusbhsfs' codebase. In other words, if you run into issues trying to build a newer version of libusbhsfs, try rebuilding and reinstalling the dependencies first.
|
||||||
|
|
||||||
|
Building and installing the NTFS-3G and lwext4 libraries beforehand isn't needed if you intend to use the ISC licensed build -- it is guaranteed to not use any GPL licensed code and/or dependency at all.
|
||||||
|
|
||||||
|
How to use
|
||||||
|
--------------
|
||||||
|
|
||||||
|
This section assumes you've already built the library by following the steps from the previous section.
|
||||||
|
|
||||||
|
* Update the `Makefile` from your homebrew application to reference the library.
|
||||||
|
* Two different builds can be generated: a release build (`-lusbhsfs`) and a debug build with logging enabled (`-lusbhsfsd`).
|
||||||
|
* If you're using a GPLv2+ licensed build, you'll also need to link your application against both NTFS-3G and lwext4: `-lusbhsfs -lntfs-3g -llwext4`.
|
||||||
|
* In case you need to report any bugs, please make sure you're using the debug build and provide its logfile.
|
||||||
|
* Include the `usbhsfs.h` header file somewhere in your code.
|
||||||
|
* Initialize the USB Mass Storage Class Host interface with `usbHsFsInitialize()`.
|
||||||
|
* Retrieve a pointer to the user-mode UMS status change event with `usbHsFsGetStatusChangeUserEvent()` and wait for that event to be signaled (e.g. under a different thread).
|
||||||
|
* Get the mounted device count with `usbHsFsGetMountedDeviceCount()`.
|
||||||
|
* List mounted devices with `usbHsFsListMountedDevices()`.
|
||||||
|
* Perform I/O operations using the returned mount names from the listed devices.
|
||||||
|
* If, for some reason, you need to safely unmount a UMS device at runtime before disconnecting it and without shutting down the whole library interface, use `usbHsFsUnmountDevice()`.
|
||||||
|
* Close the USB Mass Storage Class Host interface with `usbHsFsExit()` when you're done.
|
||||||
|
|
||||||
|
Please check both the header file located at `/include/usbhsfs.h` and the provided test application in `/example` for additional information.
|
||||||
|
|
||||||
|
Relative path support
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**Disclaimer #1:** all `fsdevMount*()` calls from libnx (and any wrappers around them) **can** and **will** override the default devoptab device if used after a successful `chdir()` call using an absolute path from a mounted volume in a UMS device. If such thing occurs, and you still need to perform additional operations with relative paths, just call `chdir()` again.
|
||||||
|
|
||||||
|
**Disclaimer #2:** relative path support is not available under SX OS!
|
||||||
|
|
||||||
|
A `chdir()` call using an absolute path to a directory from a mounted volume (e.g. `"ums0:/"`) must be issued to change both the default devoptab device and the current working directory. This will effectively place you at the provided directory, and all I/O operations performed with relative paths shall work on it.
|
||||||
|
|
||||||
|
The SD card will be set as the new default devoptab device under two different conditions:
|
||||||
|
|
||||||
|
* If the UMS device that holds the volume set as the default devoptab device is removed from the console.
|
||||||
|
* If the USB Mass Storage Class Host interface is closed via `usbHsFsExit()` and a volume from an available UMS device was set as the default devoptab device.
|
||||||
|
|
||||||
|
For an example, please check the provided test application in `/example`.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* [DarkMatterCore](https://github.com/DarkMatterCore): UMS device LUN/FS management, Bulk-Only Transport (BOT) driver, library interface.
|
||||||
|
* [XorTroll](https://github.com/XorTroll): FS mounting system, devoptab device (un)registration, example test application.
|
||||||
|
* [Rhys Koedijk](https://github.com/rhyskoedijk): NTFS support.
|
||||||
|
* Lots of SPC/BOT docs across the Internet - these have been referenced in multiple files from the codebase.
|
||||||
|
|
||||||
|
Thanks to
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* ChaN, for the [FatFs module](http://elm-chan.org/fsw/ff/00index_e.html).
|
||||||
|
* Tuxera and NTFS-3G contributors, for the [NTFS-3G library](https://github.com/tuxera/ntfs-3g).
|
||||||
|
* Grzegorz Kostka and lwext4 contributors, for the [lwext4 library](https://github.com/gkostka/lwext4).
|
||||||
|
* Switchbrew and libnx contributors. Code from libnx was used for devoptab device management and path handling.
|
||||||
|
* [blawar](https://github.com/blawar), for providing the updated `usbfs` SX OS service calls.
|
||||||
|
* [Whovian9369](https://github.com/Whovian9369). I literally would have dropped Switch homebrew development altogether some months ago, if not for you. Thanks, mate.
|
||||||
|
* [ITotalJustice](https://github.com/ITotalJustice), for testing the partition table parsing algorithm.
|
||||||
|
* [FennecTECH](https://github.com/fennectech), for breaking stuff on a regular basis.
|
||||||
|
* All the Alpha Testers and Super Users from the nxdumptool Discord server, for being a constant source of ideas (and memes).
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**v0.2.8:**
|
||||||
|
|
||||||
|
* **lib**: add `usbHsFsGetPhysicalDeviceCount()`, which returns the number of physical UMS devices currently connected to the console with at least one underlying filesystem mounted as a virtual device.
|
||||||
|
* **fs-libs**:
|
||||||
|
* Update FatFs to `R0.15 w/patch2`.
|
||||||
|
* Furthermore, FatFs is now modified to check a runtime read-only flag for any mounted filesystems, making it possible to use the `UsbHsFsMountFlags_ReadOnly` mount flag on FAT volumes for write-free access.
|
||||||
|
* Update NTFS-3G to `2022.10.3`.
|
||||||
|
* Update lwext4 to `58bcf89a121b72d4fb66334f1693d3b30e4cb9c5` with cherrypicked patches.
|
||||||
|
* Improve Makefile scripts for both NTFS-3G and lwext4 by checking if `makepkg` and `(dkp-)pacman` binaries are actually available, as well as automatically removing `pkg-config` if its available and installing `dkp-toolchain-vars` as part of the required dependencies.
|
||||||
|
|
||||||
|
**v0.2.7:**
|
||||||
|
|
||||||
|
* **log**: use UTC timestamp generated at build time instead of `__DATE__` and `__TIME__` macros.
|
||||||
|
* **fs-libs**: add missing Windows-specific dependencies to the Makefiles.
|
||||||
|
* **fat**: update FatFs to latest patch from `2022-04-04`.
|
||||||
|
* **ntfs**:
|
||||||
|
* Update NTFS-3G to `2022.5.17`.
|
||||||
|
* Create LRU caches while mounting new NTFS volumes.
|
||||||
|
* Use `ntfs_volume_get_free_space()` while mounting new NTFS volumes to speed up subsequent calls to `statvfs()` made by the user.
|
||||||
|
* Let NTFS-3G take care of filtering system files and hidden files using `ntfs_set_shown_files()`, instead of filtering them in the library's `dirnext()` implementation.
|
||||||
|
* Use `NVolFreeSpaceKnown()` in the library's `statvfs()` implementation to check if the number of free NTFS clusters has already been retrieved.
|
||||||
|
* **ext**: apply cherrypicked bugfixes to lwext4 (https://github.com/cyyynthia/lwext4/commit/bf68d176d7e0a1369a0ca2b35aaad0f700f2e716, https://github.com/wzx-ipads/lwext4/commit/06b64aabc9b445f6b28a9850ed1fcf715edad418 and https://github.com/mudita/lwext4/commit/2869807352fb7c9c2ab69e8442efa0b2ce404673).
|
||||||
|
|
||||||
|
**v0.2.6:**
|
||||||
|
|
||||||
|
* Updated codebase to use `localtime_r()` instead of `localtime()` to avoid possible race conditions with other threads.
|
||||||
|
* Fixed `fs-libs` building under Linux distros with pacman. Thanks to [ITotalJustice](https://github.com/ITotalJustice) for reporting this issue!
|
||||||
|
* Implemented support for UMS devices that don't byteswap the Command Status Wrapper signature before sending back SCSI command responses.Thanks to [rdmrocha](https://github.com/rdmrocha) for reporting this issue!
|
||||||
|
|
||||||
|
**v0.2.5:**
|
||||||
|
|
||||||
|
* Updated lwext4 patch to fix mountpoint corruption issues if a mountpoint name is reused after a previous call to `ext4_mount` failed.
|
||||||
|
* This fixes a data abort discovered by [phisch](https://github.com/phisch). Thanks for the report!
|
||||||
|
* The fix is based on [HenriChataing](https://github.com/HenriChataing)'s [pull request in lwext4's repository](https://github.com/gkostka/lwext4/pull/51), but also adds an additional `memset` call to `ext4_umount` to fully clear every unmounted mountpoint.
|
||||||
|
* A note to all developers using the GPL-licensed version of the library: update the `switch-lwext4` package by running `make fs-libs` in your libusbhsfs clone *before* building your project.
|
||||||
|
|
||||||
|
**v0.2.4:**
|
||||||
|
|
||||||
|
* Updated FatFs to R0.14b.
|
||||||
|
* The backup GPT header from a drive is now retrieved and used if the main GPT header is corrupted, as long as it's available.
|
||||||
|
* Slightly improved debug logging code.
|
||||||
|
* Rewrote mutex handling throughout the code to use a small, macro-based scoped lock implementation whenever possible.
|
||||||
|
* Removed superfluous memory operations by using dynamic pointer arrays to manage logical unit / filesystem contexts.
|
||||||
|
* Added missing `splInitialize` / `splExit` calls while checking if a service is running.
|
||||||
|
* Furthermore, the Exosphère API version, which is used to determine if TIPC serialization is needed instead of CMIF, is now saved during the first service check.
|
||||||
|
|
||||||
|
**v0.2.3:**
|
||||||
|
|
||||||
|
* Improvements to the USB manager:
|
||||||
|
* Refactored USB control request functions to work with libnx USB datatypes instead of drive / logical unit contexts.
|
||||||
|
* Implemented `GET_DESCRIPTOR` control requests for configuration and string descriptors.
|
||||||
|
* Improvements to the BOT driver:
|
||||||
|
* If `usbHsEpPostBuffer()` fails, only the endpoint the library is currently working with will be cleared. Furthermore, the result from this operation no longer affects the return code.
|
||||||
|
* If `usbHsFsRequestPostBuffer()` fails, the library now tries to retrieve a CSW right away - if it succeeds, a Request Sense command will be issued immediately to the block device.
|
||||||
|
* Mode Sense (6) / Mode Sense (10) command success is no longer mandatory in `usbHsFsScsiStartDriveLogicalUnit()`.
|
||||||
|
* SPC standard version is now validated.
|
||||||
|
* Improvements to the PKGBUILD scripts for NTFS-3G and lwext4:
|
||||||
|
* Made it possible to build and install all three libraries using the Makefile - for more information, please refer to the **How to install** section from the README.
|
||||||
|
* Proper library path is now forced while building NTFS-3G. Fixes issues in some Linux systems. Thanks to [sigmaboy](https://github.com/sigmaboy) for the correction.
|
||||||
|
* Other minor improvements.
|
||||||
|
* Library API changes:
|
||||||
|
* Added `vid` and `pid` fields to `UsbHsFsDevice`. Useful if the application needs to implement a device filter on its own.
|
||||||
|
* `vendor_id`, `product_id` and `product_revision` fields in `UsbHsFsDevice` have been replaced with `manufacturer`, `product_name` and `serial_number` fields, which represent UTF-8 conversions of string descriptors referenced by the USB device descriptor.
|
||||||
|
* Strings from SCP INQUIRY data are still used as a fallback method for `manufacturer` and `product_name` fields if the USB device descriptor holds no references to string descriptors.
|
||||||
|
* Miscellaneous changes:
|
||||||
|
* Renamed `ff_rename()` from FatFs to avoid issues fix conflicts in applications linked against FFmpeg. Thanks to [Cpasjuste](https://github.com/Cpasjuste) for letting us know.
|
||||||
|
* The `has_journal` flag from the superblock in EXT filesystems is now verified before calling journal-related functions.
|
||||||
|
* EXT filesystem version is now retrieved only once, while mounting the volume.
|
||||||
|
* The `AtmosphereHasService` sm API extension available in Atmosphère and Atmosphère-based CFWs is now being used to check if a specific service is running.
|
||||||
|
* HOS 12.0.x / AMS 0.19.x support is provided by using TIPC serialization to dispatch the IPC request, if needed.
|
||||||
|
* Improved logfile code and simplified binary data logging throughout the codebase.
|
||||||
|
* Changes under the hood (currently unused, but may change in the future):
|
||||||
|
* Implemented SYNCHRONIZE CACHE (10) and SYNCHRONIZE CACHE (16) SCP commands.
|
||||||
|
* Modified drive and logical unit contexts to prepare for UASP support.
|
||||||
|
* Added extra code to handle USB Attached SCSI Protocol (UASP) interface descriptors under both USB 2.0 and 3.0 modes.
|
||||||
|
|
||||||
|
**v0.2.2:**
|
||||||
|
|
||||||
|
* By popular demand, the NTFS journal is now rebuilt by default for NTFS volumes that have not been properly unmounted, which lets the library mount them right away without having to use a Windows PC. Please bear in mind this process may cause inconsistencies - always try to safely remove your storage devices.
|
||||||
|
* Nonetheless, this should be a relatively safe operation - default behaviour in NTFS-3G changed [some years ago](https://linux.die.net/man/8/mount.ntfs-3g).
|
||||||
|
* This change also affects EXT volume mounting. The EXT journal will now always try be recovered - if the process fails, the EXT volume won't be mounted.
|
||||||
|
|
||||||
|
**v0.2.1:**
|
||||||
|
|
||||||
|
* Bugfix: mount name IDs are now properly freed while destroying filesystem contexts.
|
||||||
|
* Library API: added a helper preprocessor macro to generate strings based on the supported filesystem type values.
|
||||||
|
* Makefile: branch name is now retrieved using `rev-parse` instead of `symbolic-ref`. Fixes `ref HEAD is not a symbolic ref` errors while building the library when the repository is used as a git submodule.
|
||||||
|
|
||||||
|
**v0.2.0:**
|
||||||
|
|
||||||
|
* Built using libnx v4.0.0.
|
||||||
|
* Implemented EXT2/3/4 support (GPL build only).
|
||||||
|
* This means applications using the GPL build of the library must now be linked against libusbhsfs, NTFS-3G and lwext4. Please read the **How to build** section from the README to know how to build both NTFS-3G and lwext4 and install them into the `portlibs` directory from devkitPro.
|
||||||
|
* Certain limitations apply. Please read the **Limitations** section from the README for more information.
|
||||||
|
* Dot directory entries "." and ".." are now filtered in NTFS volumes. They are no longer displayed as part of the output from readdir().
|
||||||
|
* Minor code cleanup.
|
||||||
|
* The example test application is now linked against lwext4 as well.
|
||||||
|
|
||||||
|
**v0.1.0:**
|
||||||
|
|
||||||
|
* Built using libnx commit `c51918a`.
|
||||||
|
* Implemented partition table parsing (MBR/GPT/VBR). The library now takes care of looking for boot sectors and/or partition tables on its own, and just passes volume LBAs to filesystem libraries. This makes it possible to mount multiple partitions from the same logical unit as individual devoptab devices.
|
||||||
|
* Implemented NTFS support. Big thanks to [Rhys Koedijk](https://github.com/rhyskoedijk)!
|
||||||
|
* You must link your application against both libusbhsfs and NTFS-3G if you wish to use NTFS support. Please read the **How to build** section from the README to know how to build NTFS-3G and install it into the `portlibs` directory from devkitPro.
|
||||||
|
* Certain limitations apply. Please read the **Limitations** section from the README for more information.
|
||||||
|
* Dual licensing (ISC / GPLv2+) is now provided as a way to allow projects that don't comply with the GPLv2+ license from NTFS-3G to keep using libusbhsfs, albeit with FAT support only. Please read the **Licensing** section from the readme for more information.
|
||||||
|
* Improved safety checks in all internal devoptab functions.
|
||||||
|
* Library API:
|
||||||
|
* `usbHsFsUnmountDevice()` is now provided as a way to manually/safely unmount UMS devices at runtime before disconnecting them.
|
||||||
|
* This has been always been automatically handled by `usbHsFsExit()` if there are any mounted UMS devices when the library interface is closed. So, depending on what you need, you should only call `usbHsFsUnmountDevice()` when absolutely necessary.
|
||||||
|
* `usbHsFsGetFileSystemMountFlags()` and `usbHsFsSetFileSystemMountFlags()` are now provided as a way to get/set filesystem mount flags.
|
||||||
|
* Please read `include/usbhsfs.h` for more information about these flags and what they do.
|
||||||
|
* These flags only affect NTFS volume mounting at this moment, so they have no effect under ISC licensed builds of the library.
|
||||||
|
* Furthermore, these functions have no effect at all under SX OS.
|
||||||
|
* BOT driver:
|
||||||
|
* Inquiry SCSI command is now retried if an unexpected CSW with no sense data is received.
|
||||||
|
* Both peripheral qualifier and peripheral device type values from Inquiry data are now filtered. Thanks to [ginkuji](https://github.com/ginkuji) for reporting this issue.
|
||||||
|
* Logical unit startup now returns right away if an optional SCSI command fails and a `Medium Not Present` additional sense code is reported by the UMS device.
|
||||||
|
* A bus reset is now performed on all UMS devices that are already available when `usbHsFsInitialize()` is called. Fixes logical unit startup for drives that were stopped during a previous session, but not removed from the console. Thanks to [FlyingBananaTree](https://github.com/FlyingBananaTree) for reporting this issue.
|
||||||
|
* Fixed potential memory corruption issues that could have taken place due to not updating LUN/FS context references after reallocating their buffers.
|
||||||
|
* Debug build:
|
||||||
|
* Implemented proper caching into debug logging code, making debug builds a lot faster now.
|
||||||
|
* The logfile is now flushed each time a public API function that generates log messages is called.
|
||||||
|
* SX OS:
|
||||||
|
* The status change user-mode event is now signaled on every `usbfs` status change.
|
||||||
|
* Example test application:
|
||||||
|
* Updated to reflect all these changes.
|
||||||
|
* Added more filesystem tests.
|
||||||
|
* Rewrote input handling to match the new `pad` API from libnx.
|
||||||
|
* Now using usbHsFsUnmountDevice() to safely unmount any UMS devices that have already been tested.
|
||||||
|
|
||||||
|
**v0.0.3:**
|
||||||
|
|
||||||
|
* Added support for a custom event index passed to `usbHsFsInitialize()`, which is internally used with `usbHsCreateInterfaceAvailableEvent()` / `usbHsDestroyInterfaceAvailableEvent()`. Developers listening for other specific USB interfaces on their own should no longer have issues with the library.
|
||||||
|
* Added fsp-usb check. `usbHsFsInitialize()` will now fail on purpose if fsp-usb is running in the background.
|
||||||
|
* Renamed FatFs library functions to avoid linking errors in homebrew apps that already depend on it.
|
||||||
|
* Fixed FatFs warnings when building the library with `-O3`. Thanks to [ITotalJustice](https://github.com/ITotalJustice)!
|
||||||
|
* Changes to relative path support:
|
||||||
|
* Modified FatFs to remove all references to `ff_chdrive()`, `ff_chdir()`, `ff_getcwd()` and `FF_FS_RPATH`. We take care of handling the current working directory and only pass absolute paths to FatFs. The code to resolve paths with dot entries wasn't removed.
|
||||||
|
* `ffdev_chdir()` now just opens the directory from the provided path to make sure it exists, then closes it immediately.
|
||||||
|
* The default devoptab device is now set by the `chdir()` function from devoptab interfaces, using `usbHsFsMountSetDefaultDevoptabDevice()`. This means it's effectively possible to change the current directory and the default devoptab device in one go, just by calling `chdir()` with an absolute path (e.g. `chdir("ums0:/")`).
|
||||||
|
* It's possible to `chdir()` back to the SD card to change the default devoptab device (e.g. `chdir("sdmc:/)`).
|
||||||
|
* If the UMS device that holds the volume set as the default devoptab device is removed from the console, the SD card will be set as the new default devoptab device.
|
||||||
|
* Removed `usbHsFsSetDefaultDevice()`, `usbHsFsGetDefaultDevice()` and `usbHsFsUnsetDefaultDevice()` - just use `chdir()` now.
|
||||||
|
* Limitations regarding `fsdevMount*()` calls from libnx still apply. Can't do anything about it.
|
||||||
|
* Please read the **Relative path support** section from the README for more information.
|
||||||
|
* BOT driver:
|
||||||
|
* Added support for unexpected CSWs received through an input endpoint during data transfer stages. Thanks to [duckbill007](https://github.com/duckbill007) for reporting this issue!
|
||||||
|
* Always issue a Request Sense command if an unexpected CSW is received.
|
||||||
|
* Make sure write protection is disabled before issuing any SCP WRITE commands.
|
||||||
|
* Reduced wait time if a "Not Ready" sense key is received after issuing a Request Sense command.
|
||||||
|
* Added support for the `usbfs` service from SX OS. Thanks to [blawar](https://github.com/blawar) for providing the updated `usbfs` service calls!
|
||||||
|
* Please read the **Limitations** section from the README for more information.
|
||||||
|
* Updated test application to reflect all these changes.
|
||||||
|
* It is now also capable of performing a test file copy to the UMS filesystem if `test.file` is available at the SD card root directory.
|
||||||
|
|
||||||
|
**v0.0.2:**
|
||||||
|
|
||||||
|
* Relicensed library under the ISC License. We really want you people to adopt it and freely use it in your projects.
|
||||||
|
* Fixed distribution package version string generation in `Makefile`.
|
||||||
|
* `LICENSE.md` and `README.md` are stored in the generated distribution packages.
|
||||||
|
* Added support for relative paths.
|
||||||
|
* Please read the **Relative path support** section from the README for more information.
|
||||||
|
* A trailing colon is now added to the returned mount names from `UsbHsFsDevice` elements.
|
||||||
|
* Fixed devoptab device unregistration.
|
||||||
|
* Bulk-Only Transport (BOT) driver:
|
||||||
|
* `usbHsFsRequestGetMaxLogicalUnits()` now clears the STALL status from both endpoints on its own if it fails.
|
||||||
|
* Likewise, `usbHsFsRequestPostBuffer()` now attempts to clear the STALL status from both endpoints if it fails.
|
||||||
|
* FatFs devoptab interface:
|
||||||
|
* Fixed error code translations for some FatFs errors.
|
||||||
|
* Created an unified `ffdev_fill_stat()` function for both `ffdev_stat()` and `ffdev_dirnext()`.
|
||||||
|
* Fixed POSIX timestamp conversions from DOS timestamps.
|
||||||
|
* Debug build:
|
||||||
|
* Debug messages from `usbHsFsScsiReadLogicalUnitBlocks()` and `usbHsFsScsiReadLogicalUnitBlocks()` now include the total number of bytes to transfer per each loop iteration.
|
||||||
|
* Added debug messages to the FatFs devoptab interface.
|
||||||
|
|
||||||
|
**v0.0.1:**
|
||||||
|
|
||||||
|
* Initial release. Only capable of mounting one FAT filesystem per logical unit from each connected UMS device.
|
253
include/libusbhsfs/example/Makefile
Normal file
253
include/libusbhsfs/example/Makefile
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
.SUFFIXES:
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ifeq ($(strip $(DEVKITPRO)),)
|
||||||
|
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||||
|
endif
|
||||||
|
|
||||||
|
TOPDIR ?= $(CURDIR)
|
||||||
|
include $(DEVKITPRO)/libnx/switch_rules
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# TARGET is the name of the output
|
||||||
|
# BUILD is the directory where object files & intermediate files will be placed
|
||||||
|
# SOURCES is a list of directories containing source code
|
||||||
|
# DATA is a list of directories containing data files
|
||||||
|
# INCLUDES is a list of directories containing header files
|
||||||
|
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||||
|
#
|
||||||
|
# NO_ICON: if set to anything, do not use icon.
|
||||||
|
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||||
|
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||||
|
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||||
|
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||||
|
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||||
|
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||||
|
# If not set, it attempts to use one of the following (in this order):
|
||||||
|
# - <Project name>.jpg
|
||||||
|
# - icon.jpg
|
||||||
|
# - <libnx folder>/default_icon.jpg
|
||||||
|
#
|
||||||
|
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||||
|
# If not set, it attempts to use one of the following (in this order):
|
||||||
|
# - <Project name>.json
|
||||||
|
# - config.json
|
||||||
|
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||||
|
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||||
|
# NACP building is skipped as well.
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BUILD_TIMESTAMP := $(strip $(shell date --utc '+%Y-%m-%d %T UTC'))
|
||||||
|
|
||||||
|
VERSION_MAJOR := 0
|
||||||
|
VERSION_MINOR := 0
|
||||||
|
VERSION_MICRO := 1
|
||||||
|
|
||||||
|
APP_TITLE := libusbhsfs-example
|
||||||
|
APP_AUTHOR := DarkMatterCore, XorTroll, Rhys Koedijk
|
||||||
|
APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}
|
||||||
|
|
||||||
|
TARGET := ${APP_TITLE}
|
||||||
|
BUILD := build
|
||||||
|
SOURCES := source
|
||||||
|
DATA := data
|
||||||
|
INCLUDES := include
|
||||||
|
#ROMFS := romfs
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# options for code generation
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
|
||||||
|
CFLAGS := -g -Wall -Wextra -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__
|
||||||
|
CFLAGS += -DBUILD_TIMESTAMP="\"${BUILD_TIMESTAMP}\"" -DAPP_TITLE=\"${APP_TITLE}\"
|
||||||
|
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||||
|
|
||||||
|
ASFLAGS := -g $(ARCH)
|
||||||
|
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
|
# Use -lusbhsfsd instead of -lusbhsfs to use the library build with debug logging enabled
|
||||||
|
LIBS := -lusbhsfsd
|
||||||
|
|
||||||
|
ifeq ($(filter $(MAKECMDGOALS),clean),)
|
||||||
|
# Check BUILD_TYPE flag
|
||||||
|
ifneq ($(origin BUILD_TYPE),undefined)
|
||||||
|
ifeq (${BUILD_TYPE},ISC)
|
||||||
|
# Do nothing
|
||||||
|
else
|
||||||
|
ifeq (${BUILD_TYPE},GPL)
|
||||||
|
# Update libs
|
||||||
|
# We'll just assume the user has already installed the necessary libraries
|
||||||
|
LIBS += -lntfs-3g -llwext4
|
||||||
|
else
|
||||||
|
$(error Invalid value for BUILD_TYPE flag. Expected ISC or GPL)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
$(error BUILD_TYPE flag not set)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
LIBS += -lnx
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# list of directories containing libraries, this must be the top level containing
|
||||||
|
# include and lib
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/..
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# no real need to edit anything past this point unless you need to add additional
|
||||||
|
# rules for different file extensions
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
|
export TOPDIR := $(CURDIR)
|
||||||
|
|
||||||
|
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||||
|
|
||||||
|
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# use CXX for linking C++ projects, CC for standard C
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifeq ($(strip $(CPPFILES)),)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CC)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
export LD := $(CXX)
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||||
|
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
|
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||||
|
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||||
|
|
||||||
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
|
-I$(CURDIR)/$(BUILD)
|
||||||
|
|
||||||
|
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||||
|
|
||||||
|
ifeq ($(strip $(CONFIG_JSON)),)
|
||||||
|
jsons := $(wildcard *.json)
|
||||||
|
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||||
|
else
|
||||||
|
ifneq (,$(findstring config.json,$(jsons)))
|
||||||
|
export APP_JSON := $(TOPDIR)/config.json
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(ICON)),)
|
||||||
|
icons := $(wildcard *.jpg)
|
||||||
|
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||||
|
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||||
|
else
|
||||||
|
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||||
|
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(NO_ICON)),)
|
||||||
|
export NROFLAGS += --icon=$(APP_ICON)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(strip $(NO_NACP)),)
|
||||||
|
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(APP_TITLEID),)
|
||||||
|
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(ROMFS),)
|
||||||
|
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: $(BUILD) clean all
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
all: $(BUILD)
|
||||||
|
|
||||||
|
$(BUILD):
|
||||||
|
@[ -d $@ ] || mkdir -p $@
|
||||||
|
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
clean:
|
||||||
|
@echo clean ...
|
||||||
|
ifeq ($(strip $(APP_JSON)),)
|
||||||
|
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||||
|
else
|
||||||
|
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
.PHONY: all
|
||||||
|
|
||||||
|
DEPENDS := $(OFILES:.o=.d)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# main targets
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
ifeq ($(strip $(APP_JSON)),)
|
||||||
|
|
||||||
|
all : $(OUTPUT).nro
|
||||||
|
|
||||||
|
ifeq ($(strip $(NO_NACP)),)
|
||||||
|
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
|
||||||
|
else
|
||||||
|
$(OUTPUT).nro : $(OUTPUT).elf
|
||||||
|
endif
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
all : $(OUTPUT).nsp
|
||||||
|
|
||||||
|
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||||
|
|
||||||
|
$(OUTPUT).nso : $(OUTPUT).elf
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(OUTPUT).elf : $(OFILES)
|
||||||
|
|
||||||
|
$(OFILES_SRC) : $(HFILES_BIN)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
# you need a rule like this for each extension you use as binary data
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
%.bin.o %_bin.h : %.bin
|
||||||
|
#---------------------------------------------------------------------------------
|
||||||
|
@echo $(notdir $<)
|
||||||
|
@$(bin2o)
|
||||||
|
|
||||||
|
-include $(DEPENDS)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------------------------
|
||||||
|
endif
|
||||||
|
#---------------------------------------------------------------------------------------
|
445
include/libusbhsfs/example/source/main.c
Normal file
445
include/libusbhsfs/example/source/main.c
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <threads.h>
|
||||||
|
#include <usbhsfs.h>
|
||||||
|
|
||||||
|
static UEvent *g_statusChangeEvent = NULL, g_exitEvent = {0};
|
||||||
|
|
||||||
|
static u32 g_usbDeviceCount = 0;
|
||||||
|
static UsbHsFsDevice *g_usbDevices = NULL;
|
||||||
|
|
||||||
|
void usbMscFileSystemTest(UsbHsFsDevice *device)
|
||||||
|
{
|
||||||
|
if (!device) return;
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH] = {0}, tmp[0x40] = {0}, new_path[FS_MAX_PATH] = {0}, *ptr = NULL;
|
||||||
|
const char *test_str = "Hello world!";
|
||||||
|
size_t test_str_len = strlen(test_str);
|
||||||
|
|
||||||
|
FILE *fd = NULL, *ums_fd = NULL;
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
DIR *dp = NULL;
|
||||||
|
struct dirent *dt = NULL;
|
||||||
|
|
||||||
|
struct statvfs fsinfo = {0};
|
||||||
|
|
||||||
|
bool copy_failed = false;
|
||||||
|
|
||||||
|
int ret = -1, val = 0;
|
||||||
|
|
||||||
|
u8 *buf = NULL;
|
||||||
|
size_t blksize = 0x800000;
|
||||||
|
|
||||||
|
sprintf(path, "%s/test_dir", device->name);
|
||||||
|
|
||||||
|
/* Create directory. */
|
||||||
|
printf("\t\t- Create directory (\"%s\"): ", path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!mkdir(path, 0))
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Write data to file. */
|
||||||
|
strcat(path, "/" APP_TITLE ".txt");
|
||||||
|
printf("\t\t- Write data to file (\"%s\") (\"%s\"): ", path, test_str);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
fd = fopen(path, "w");
|
||||||
|
if (fd)
|
||||||
|
{
|
||||||
|
val = fprintf(fd, test_str);
|
||||||
|
if (val == (int)test_str_len)
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d, %d).\n", errno, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Read data from file. */
|
||||||
|
printf("\t\t- Read data from file (\"%s\"): ", path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
fd = fopen(path, "r");
|
||||||
|
if (fd)
|
||||||
|
{
|
||||||
|
if (fgets(tmp, test_str_len + 1, fd) != NULL)
|
||||||
|
{
|
||||||
|
printf("OK! (\"%s\").\n", tmp);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* File stats. */
|
||||||
|
printf("\t\t- File stats (\"%s\"): ", path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!stat(path, &st))
|
||||||
|
{
|
||||||
|
printf("OK!\n\t\t\t- ID: %i.\n\t\t\t- Type: %s.\n\t\t\t- Size: %lu.\n\t\t\t- Timestamp: %lu.\n", st.st_ino, st.st_mode & S_IFREG ? "file" : "dir", st.st_size, st.st_mtime);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Rename file. */
|
||||||
|
ptr = strrchr(path, '/');
|
||||||
|
sprintf(new_path, "%.*s/test.txt", (int)(ptr - path), path);
|
||||||
|
printf("\t\t- Rename file (\"%s\" -> \"%s\"): ", path, new_path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!rename(path, new_path))
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Change directory. */
|
||||||
|
*ptr = '\0';
|
||||||
|
printf("\t\t- Change directory (\"%s\"): ", path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
ret = chdir(path);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
|
||||||
|
/* Directory listing. */
|
||||||
|
printf("\t\t- Directory listing (\".\"): ");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
dp = opendir("."); /* Open current directory. */
|
||||||
|
if (dp)
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
while((dt = readdir(dp)))
|
||||||
|
{
|
||||||
|
printf("\t\t\t- [%c] ./%s\n", (dt->d_type & DT_DIR) ? 'D' : 'F', dt->d_name);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dp);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Delete file. */
|
||||||
|
printf("\t\t- Delete file (\"%s\"): ", new_path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!unlink(new_path))
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Delete directory. */
|
||||||
|
printf("\t\t- Delete directory (\"%s\"): ", path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!rmdir(path))
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Filesystem stats. */
|
||||||
|
printf("\t\t- Filesystem stats: ");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!statvfs(".", &fsinfo))
|
||||||
|
{
|
||||||
|
u64 fsid = (u64)fsinfo.f_fsid;
|
||||||
|
u64 total_size = ((u64)fsinfo.f_blocks * (u64)fsinfo.f_frsize);
|
||||||
|
u64 free_space = ((u64)fsinfo.f_bfree * (u64)fsinfo.f_frsize);
|
||||||
|
|
||||||
|
printf("OK!\n\t\t\t- ID: %lu.\n\t\t\t- Total FS size: 0x%lX bytes.\n\t\t\t- Free FS space: 0x%lX bytes.\n", fsid, total_size, free_space);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* File copy. */
|
||||||
|
sprintf(path, "sdmc:/test.file");
|
||||||
|
sprintf(new_path, "%s/test.file", device->name);
|
||||||
|
printf("\t\t- File copy (\"%s\" -> \"%s\"): ", path, new_path);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
fd = fopen(path, "rb");
|
||||||
|
ums_fd = fopen(new_path, "wb");
|
||||||
|
buf = malloc(blksize);
|
||||||
|
|
||||||
|
if (fd && ums_fd && buf)
|
||||||
|
{
|
||||||
|
printf("OK!\n");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
fseek(fd, 0, SEEK_END);
|
||||||
|
size_t file_size = ftell(fd);
|
||||||
|
rewind(fd);
|
||||||
|
|
||||||
|
printf("\t\t\t- File size (\"%s\"): 0x%lX bytes. Please wait.\n", path, file_size);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
time_t start = time(NULL), now = start;
|
||||||
|
|
||||||
|
for(size_t off = 0; off < file_size; off += blksize)
|
||||||
|
{
|
||||||
|
if (blksize > (file_size - off)) blksize = (file_size - off);
|
||||||
|
|
||||||
|
fread(buf, 1, blksize, fd);
|
||||||
|
fwrite(buf, 1, blksize, ums_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
now = time(NULL);
|
||||||
|
printf("\t\t\t- Process completed in %lu seconds.\n", now - start);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
} else {
|
||||||
|
printf("FAILED! (%d).\n", errno);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
copy_failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf) free(buf);
|
||||||
|
|
||||||
|
if (ums_fd)
|
||||||
|
{
|
||||||
|
fclose(ums_fd);
|
||||||
|
if (copy_failed) unlink(new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd) fclose(fd);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int usbMscThreadFunc(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
int idx = 0;
|
||||||
|
u32 listed_device_count = 0;
|
||||||
|
|
||||||
|
/* Generate waiters for our user events. */
|
||||||
|
Waiter status_change_event_waiter = waiterForUEvent(g_statusChangeEvent);
|
||||||
|
Waiter exit_event_waiter = waiterForUEvent(&g_exitEvent);
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
/* Wait until an event is triggered. */
|
||||||
|
rc = waitMulti(&idx, -1, status_change_event_waiter, exit_event_waiter);
|
||||||
|
if (R_FAILED(rc)) continue;
|
||||||
|
|
||||||
|
/* Free mounted devices buffer. */
|
||||||
|
if (g_usbDevices)
|
||||||
|
{
|
||||||
|
free(g_usbDevices);
|
||||||
|
g_usbDevices = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exit event triggered. */
|
||||||
|
if (idx == 1)
|
||||||
|
{
|
||||||
|
printf("Exit event triggered!\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get mounted device count. */
|
||||||
|
g_usbDeviceCount = usbHsFsGetMountedDeviceCount();
|
||||||
|
|
||||||
|
printf("USB Mass Storage status change event triggered!\nMounted USB Mass Storage device count: %u.\n\n", g_usbDeviceCount);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
if (!g_usbDeviceCount) continue;
|
||||||
|
|
||||||
|
/* Allocate mounted devices buffer. */
|
||||||
|
g_usbDevices = calloc(g_usbDeviceCount, sizeof(UsbHsFsDevice));
|
||||||
|
if (!g_usbDevices)
|
||||||
|
{
|
||||||
|
printf("Failed to allocate memory for mounted USB Mass Storage devices buffer!\n\n");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List mounted devices. */
|
||||||
|
if (!(listed_device_count = usbHsFsListMountedDevices(g_usbDevices, g_usbDeviceCount)))
|
||||||
|
{
|
||||||
|
printf("Failed to list mounted USB Mass Storage devices!\n\n");
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print info from mounted devices. */
|
||||||
|
for(u32 i = 0; i < listed_device_count; i++)
|
||||||
|
{
|
||||||
|
UsbHsFsDevice *device = &(g_usbDevices[i]);
|
||||||
|
|
||||||
|
printf("Device #%u:\n" \
|
||||||
|
"\t- USB interface ID: %d.\n" \
|
||||||
|
"\t- Logical Unit Number: %u.\n" \
|
||||||
|
"\t- Filesystem index: %u.\n" \
|
||||||
|
"\t- Write protected: %s.\n" \
|
||||||
|
"\t- Vendor ID: 0x%04X.\n" \
|
||||||
|
"\t- Product ID: 0x%04X.\n" \
|
||||||
|
"\t- Manufacturer: \"%s\".\n" \
|
||||||
|
"\t- Product Name: \"%s\".\n" \
|
||||||
|
"\t- Serial Number: \"%s\".\n" \
|
||||||
|
"\t- Logical Unit Capacity: 0x%lX bytes.\n" \
|
||||||
|
"\t- Mount name: \"%s\".\n" \
|
||||||
|
"\t- Filesystem type: %s.\n" \
|
||||||
|
"\t- Mount flags: 0x%08X.\n" \
|
||||||
|
"\t- Filesystem tests:\n", \
|
||||||
|
i + 1, \
|
||||||
|
device->usb_if_id, \
|
||||||
|
device->lun, \
|
||||||
|
device->fs_idx, \
|
||||||
|
device->write_protect ? "yes" : "no", \
|
||||||
|
device->vid, \
|
||||||
|
device->pid, \
|
||||||
|
device->manufacturer, \
|
||||||
|
device->product_name, \
|
||||||
|
device->serial_number, \
|
||||||
|
device->capacity, \
|
||||||
|
device->name, \
|
||||||
|
LIBUSBHSFS_FS_TYPE_STR(device->fs_type), \
|
||||||
|
device->flags);
|
||||||
|
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Perform filesystem tests on current device. */
|
||||||
|
usbMscFileSystemTest(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmount devices. */
|
||||||
|
for(u32 i = 0; i < listed_device_count; i++)
|
||||||
|
{
|
||||||
|
UsbHsFsDevice *device = &(g_usbDevices[i]);
|
||||||
|
usbHsFsUnmountDevice(device, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%u device(s) safely unmounted. You may now disconnect them from the console.\n\n", listed_device_count);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exit thread. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
int ret = 0;
|
||||||
|
thrd_t thread = {0};
|
||||||
|
PadState pad = {0};
|
||||||
|
|
||||||
|
/* Initialize console output. */
|
||||||
|
consoleInit(NULL);
|
||||||
|
|
||||||
|
/* Configure our supported input layout: a single player with full controller styles. */
|
||||||
|
padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl);
|
||||||
|
|
||||||
|
/* Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller. */
|
||||||
|
padInitializeDefault(&pad);
|
||||||
|
|
||||||
|
printf(APP_TITLE ". Built on " BUILD_TIMESTAMP ".\nLibrary version: %u.%u.%u.\nPress + to exit.\n\n", LIBUSBHSFS_VERSION_MAJOR, LIBUSBHSFS_VERSION_MINOR, LIBUSBHSFS_VERSION_MICRO);
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Initialize USB Mass Storage Host interface. */
|
||||||
|
rc = usbHsFsInitialize(0);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
printf("usbHsFsInitialize() failed! (0x%08X).\n", rc);
|
||||||
|
ret = - 1;
|
||||||
|
goto end1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get USB Mass Storage status change event. */
|
||||||
|
g_statusChangeEvent = usbHsFsGetStatusChangeUserEvent();
|
||||||
|
|
||||||
|
/* Create usermode thread exit event. */
|
||||||
|
ueventCreate(&g_exitEvent, true);
|
||||||
|
|
||||||
|
/* Create thread. */
|
||||||
|
thrd_create(&thread, usbMscThreadFunc, NULL);
|
||||||
|
|
||||||
|
while(appletMainLoop())
|
||||||
|
{
|
||||||
|
padUpdate(&pad);
|
||||||
|
|
||||||
|
u64 keys_down = padGetButtonsDown(&pad);
|
||||||
|
if (keys_down & HidNpadButton_Plus)
|
||||||
|
{
|
||||||
|
/* Signal background thread. */
|
||||||
|
ueventSignal(&g_exitEvent);
|
||||||
|
|
||||||
|
/* Wait for the background thread to exit on its own. */
|
||||||
|
thrd_join(thread, NULL);
|
||||||
|
|
||||||
|
/* Break out of this loop. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deinitialize USB Mass Storage Host interface. */
|
||||||
|
usbHsFsExit();
|
||||||
|
|
||||||
|
end1:
|
||||||
|
/* Update console output. */
|
||||||
|
consoleUpdate(NULL);
|
||||||
|
|
||||||
|
/* Wait some time (3 seconds). */
|
||||||
|
svcSleepThread(3000000000ULL);
|
||||||
|
|
||||||
|
/* Deinitialize console output. */
|
||||||
|
consoleExit(NULL);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
130
include/libusbhsfs/include/usbhsfs.h
Normal file
130
include/libusbhsfs/include/usbhsfs.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_H__
|
||||||
|
#define __USBHSFS_H__
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Library version.
|
||||||
|
#define LIBUSBHSFS_VERSION_MAJOR 0
|
||||||
|
#define LIBUSBHSFS_VERSION_MINOR 2
|
||||||
|
#define LIBUSBHSFS_VERSION_MICRO 8
|
||||||
|
|
||||||
|
/// Helper macro to generate a string based on a filesystem type value.
|
||||||
|
#define LIBUSBHSFS_FS_TYPE_STR(x) ((x) == UsbHsFsDeviceFileSystemType_FAT12 ? "FAT12" : ((x) == UsbHsFsDeviceFileSystemType_FAT16 ? "FAT16" : ((x) == UsbHsFsDeviceFileSystemType_FAT32 ? "FAT32" : \
|
||||||
|
((x) == UsbHsFsDeviceFileSystemType_exFAT ? "exFAT" : ((x) == UsbHsFsDeviceFileSystemType_NTFS ? "NTFS" : ((x) == UsbHsFsDeviceFileSystemType_EXT2 ? "EXT2" : \
|
||||||
|
((x) == UsbHsFsDeviceFileSystemType_EXT3 ? "EXT3" : ((x) == UsbHsFsDeviceFileSystemType_EXT4 ? "EXT4" : "Invalid"))))))))
|
||||||
|
|
||||||
|
/// Used to identify the filesystem type from a mounted filesystem (e.g. filesize limitations, etc.).
|
||||||
|
typedef enum {
|
||||||
|
UsbHsFsDeviceFileSystemType_Invalid = 0,
|
||||||
|
UsbHsFsDeviceFileSystemType_FAT12 = 1,
|
||||||
|
UsbHsFsDeviceFileSystemType_FAT16 = 2,
|
||||||
|
UsbHsFsDeviceFileSystemType_FAT32 = 3,
|
||||||
|
UsbHsFsDeviceFileSystemType_exFAT = 4,
|
||||||
|
UsbHsFsDeviceFileSystemType_NTFS = 5, ///< Only returned by the GPL build of the library.
|
||||||
|
UsbHsFsDeviceFileSystemType_EXT2 = 6, ///< Only returned by the GPL build of the library.
|
||||||
|
UsbHsFsDeviceFileSystemType_EXT3 = 7, ///< Only returned by the GPL build of the library.
|
||||||
|
UsbHsFsDeviceFileSystemType_EXT4 = 8 ///< Only returned by the GPL build of the library.
|
||||||
|
} UsbHsFsDeviceFileSystemType;
|
||||||
|
|
||||||
|
/// Filesystem mount flags.
|
||||||
|
/// Not all supported filesystems are compatible with these flags.
|
||||||
|
/// The default mount bitmask is `UsbHsFsMountFlags_UpdateAccessTimes | UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ReplayJournal`.
|
||||||
|
/// It can be overriden via usbHsFsSetFileSystemMountFlags() (see below).
|
||||||
|
typedef enum {
|
||||||
|
UsbHsFsMountFlags_None = 0x00000000, ///< No special action is taken.
|
||||||
|
UsbHsFsMountFlags_IgnoreCaseSensitivity = 0x00000001, ///< NTFS only. Case sensitivity is ignored for all filesystem operations.
|
||||||
|
UsbHsFsMountFlags_UpdateAccessTimes = 0x00000002, ///< NTFS only. File/directory access times are updated after each successful R/W operation.
|
||||||
|
UsbHsFsMountFlags_ShowHiddenFiles = 0x00000004, ///< NTFS only. Hidden file entries are returned while enumerating directories.
|
||||||
|
UsbHsFsMountFlags_ShowSystemFiles = 0x00000008, ///< NTFS only. System file entries are returned while enumerating directories.
|
||||||
|
UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute = 0x00000010, ///< NTFS only. Allows writing to files even if they are marked as read-only.
|
||||||
|
UsbHsFsMountFlags_ReadOnly = 0x00000100, ///< Filesystem is mounted as read-only.
|
||||||
|
UsbHsFsMountFlags_ReplayJournal = 0x00000200, ///< NTFS and EXT only. Replays the log/journal to restore filesystem consistency (e.g. fix unsafe device ejections).
|
||||||
|
UsbHsFsMountFlags_IgnoreHibernation = 0x00010000, ///< NTFS only. Filesystem is mounted even if it's in a hibernated state. The saved Windows session is completely lost.
|
||||||
|
|
||||||
|
///< Pre-generated bitmasks provided for convenience.
|
||||||
|
UsbHsFsMountFlags_SuperUser = (UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ShowSystemFiles | UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute),
|
||||||
|
UsbHsFsMountFlags_Force = (UsbHsFsMountFlags_ReplayJournal | UsbHsFsMountFlags_IgnoreHibernation)
|
||||||
|
} UsbHsFsMountFlags;
|
||||||
|
|
||||||
|
/// Struct used to list filesystems that have been mounted as virtual devices via devoptab.
|
||||||
|
/// Everything but the manufacturer, product_name and name fields is empty/zeroed-out under SX OS.
|
||||||
|
typedef struct {
|
||||||
|
s32 usb_if_id; ///< USB interface ID. Internal use.
|
||||||
|
u8 lun; ///< Logical unit. Internal use.
|
||||||
|
u32 fs_idx; ///< Filesystem index. Internal use.
|
||||||
|
bool write_protect; ///< Set to true if the logical unit is protected against write operations.
|
||||||
|
u16 vid; ///< Vendor ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application.
|
||||||
|
u16 pid; ///< Product ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application.
|
||||||
|
char manufacturer[64]; ///< UTF-8 encoded manufacturer string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty.
|
||||||
|
char product_name[64]; ///< UTF-8 encoded product name string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty.
|
||||||
|
char serial_number[64]; ///< UTF-8 encoded serial number string. Retrieved from the device descriptor. May be empty.
|
||||||
|
u64 capacity; ///< Raw capacity from the logical unit this filesystem belongs to. Use statvfs() to get the actual filesystem capacity. May be shared with other UsbHsFsDevice entries.
|
||||||
|
char name[32]; ///< Mount name used by the devoptab virtual device interface (e.g. "ums0:"). Use it as a prefix in libcstd I/O calls to perform operations on this filesystem.
|
||||||
|
u8 fs_type; ///< UsbHsFsDeviceFileSystemType.
|
||||||
|
u32 flags; ///< UsbHsFsMountFlags bitmask used at mount time.
|
||||||
|
} UsbHsFsDevice;
|
||||||
|
|
||||||
|
/// Initializes the USB Mass Storage Host interface.
|
||||||
|
/// `event_idx` represents the event index to use with usbHsCreateInterfaceAvailableEvent() / usbHsDestroyInterfaceAvailableEvent(). Must be within the [0, 2] range.
|
||||||
|
/// If you're not using any usb:hs interface available events on your own, set this value to 0. If running under SX OS, this value will be ignored.
|
||||||
|
/// This function will fail if the deprecated fsp-usb service is running in the background.
|
||||||
|
Result usbHsFsInitialize(u8 event_idx);
|
||||||
|
|
||||||
|
/// Closes the USB Mass Storage Host interface.
|
||||||
|
/// If there are any UMS devices with mounted filesystems connected to the console when this function is called, their filesystems will be unmounted and their logical units will be stopped.
|
||||||
|
void usbHsFsExit(void);
|
||||||
|
|
||||||
|
/// Returns a pointer to the user-mode status change event (with autoclear enabled).
|
||||||
|
/// Useful to wait for USB Mass Storage status changes without having to constantly poll the interface.
|
||||||
|
/// Returns NULL if the USB Mass Storage Host interface hasn't been initialized.
|
||||||
|
UEvent *usbHsFsGetStatusChangeUserEvent(void);
|
||||||
|
|
||||||
|
/// Returns the number of physical UMS devices currently connected to the console with at least one underlying filesystem mounted as a virtual device.
|
||||||
|
u32 usbHsFsGetPhysicalDeviceCount(void);
|
||||||
|
|
||||||
|
/// Returns the total number of filesystems across all available UMS devices currently mounted as virtual devices via devoptab.
|
||||||
|
/// Must be used before calling usbHsFsListMountedDevices().
|
||||||
|
u32 usbHsFsGetMountedDeviceCount(void);
|
||||||
|
|
||||||
|
/// Lists up to `max_count` mounted virtual devices and stores their information in the provided UsbHsFsDevice array.
|
||||||
|
/// Returns the total number of written entries.
|
||||||
|
u32 usbHsFsListMountedDevices(UsbHsFsDevice *out, u32 max_count);
|
||||||
|
|
||||||
|
/// Unmounts all filesystems from the UMS device with a USB interface ID that matches the one from the provided UsbHsFsDevice, and stops all of its logical units.
|
||||||
|
/// Can be used to safely unmount a UMS device at runtime, if that's needed for some reason. Calling this function before usbHsFsExit() isn't mandatory.
|
||||||
|
/// If multiple UsbHsFsDevice entries are returned for the same physical UMS device, any of them can be used as the input argument for this function.
|
||||||
|
/// If successful, and `signal_status_event` is true, this will also fire the user-mode status change event returned by usbHsFsGetStatusChangeUserEvent().
|
||||||
|
/// This function has no effect at all under SX OS.
|
||||||
|
bool usbHsFsUnmountDevice(UsbHsFsDevice *device, bool signal_status_event);
|
||||||
|
|
||||||
|
/// Returns a bitmask with the current filesystem mount flags.
|
||||||
|
/// Can be used even if the USB Mass Storage Host interface hasn't been initialized.
|
||||||
|
/// This function has no effect at all under SX OS.
|
||||||
|
u32 usbHsFsGetFileSystemMountFlags(void);
|
||||||
|
|
||||||
|
/// Takes an input bitmask with the desired filesystem mount flags, which will be used for all mount operations.
|
||||||
|
/// Can be used even if the USB Mass Storage Host interface hasn't been initialized.
|
||||||
|
/// This function has no effect at all under SX OS.
|
||||||
|
void usbHsFsSetFileSystemMountFlags(u32 flags);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_H__ */
|
Binary file not shown.
16
include/libusbhsfs/liblwext4/Makefile
Normal file
16
include/libusbhsfs/liblwext4/Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
MAKEPKG := makepkg
|
||||||
|
all: deps
|
||||||
|
else
|
||||||
|
ifeq (, $(shell which makepkg))
|
||||||
|
MAKEPKG := dkp-makepkg
|
||||||
|
else
|
||||||
|
MAKEPKG := makepkg
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
all:
|
||||||
|
@$(MAKEPKG) -c -C -f -i -s --noconfirm > /dev/null
|
||||||
|
|
||||||
|
deps:
|
||||||
|
@pacman -S --needed --noconfirm patch cmake > /dev/null
|
51
include/libusbhsfs/liblwext4/PKGBUILD
Normal file
51
include/libusbhsfs/liblwext4/PKGBUILD
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Maintainer: DarkMatterCore <pabloacurielz@gmail.com>
|
||||||
|
|
||||||
|
pkgbasename=lwext4
|
||||||
|
pkgname=switch-${pkgbasename}
|
||||||
|
pkgver=58bcf89a121b72d4fb66334f1693d3b30e4cb9c5
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='ext2/ext3/ext4 filesystem library for microcontrollers'
|
||||||
|
url='https://github.com/gkostka/lwext4'
|
||||||
|
license=('GPL')
|
||||||
|
arch=('any')
|
||||||
|
options=(!strip libtool staticlibs)
|
||||||
|
makedepends=('devkitA64' 'dkp-toolchain-vars' 'switch-cmake')
|
||||||
|
groups=('switch-portlibs')
|
||||||
|
source=(
|
||||||
|
"${url}/archive/${pkgver}.zip"
|
||||||
|
"${pkgname}-${pkgver}.patch"
|
||||||
|
)
|
||||||
|
sha256sums=(
|
||||||
|
'bf1de2bd3430d11173ffb6482e2d0d38f991318d8bc9c5dc4051c33b2c407911'
|
||||||
|
'c303c83a78a27f42c8474f3a35d05693f120dcd5568e4c2a2399ac9d8c63dd49'
|
||||||
|
)
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
patch -Np1 -i "${srcdir}/${pkgname}-${pkgver}.patch"
|
||||||
|
|
||||||
|
source "${DEVKITPRO}/switchvars.sh"
|
||||||
|
|
||||||
|
mv include lwext4
|
||||||
|
mkdir -p include
|
||||||
|
mv lwext4 include/lwext4
|
||||||
|
|
||||||
|
make lib_only
|
||||||
|
|
||||||
|
cp -fr build_lib_only/include/lwext4/generated include/lwext4/generated
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
make -C build_lib_only
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
|
||||||
|
install -Dm644 build_lib_only/src/liblwext4.a "${pkgdir}${PORTLIBS_PREFIX}/lib/liblwext4.a"
|
||||||
|
install -Dm644 LICENSE "${pkgdir}${PORTLIBS_PREFIX}/licenses/${pkgname}/LICENSE"
|
||||||
|
|
||||||
|
cp -fr include "${pkgdir}${PORTLIBS_PREFIX}/include"
|
||||||
|
chmod -R u=rwX,go=rX "${pkgdir}${PORTLIBS_PREFIX}/include"
|
||||||
|
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
16
include/libusbhsfs/libntfs-3g/Makefile
Normal file
16
include/libusbhsfs/libntfs-3g/Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
MAKEPKG := makepkg
|
||||||
|
all: deps
|
||||||
|
else
|
||||||
|
ifeq (, $(shell which makepkg))
|
||||||
|
MAKEPKG := dkp-makepkg
|
||||||
|
else
|
||||||
|
MAKEPKG := makepkg
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
all:
|
||||||
|
@$(MAKEPKG) -c -C -f -i -s --noconfirm > /dev/null
|
||||||
|
|
||||||
|
deps:
|
||||||
|
@pacman -S --needed --noconfirm patch > /dev/null
|
53
include/libusbhsfs/libntfs-3g/PKGBUILD
Normal file
53
include/libusbhsfs/libntfs-3g/PKGBUILD
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Maintainer: Rhys Koedijk <rhys@koedijk.co.nz>
|
||||||
|
# Maintainer: DarkMatterCore <pabloacurielz@gmail.com>
|
||||||
|
|
||||||
|
pkgbasename=ntfs-3g_ntfsprogs
|
||||||
|
pkgname=switch-libntfs-3g
|
||||||
|
pkgver=2022.10.3
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='Read-Write NTFS Driver'
|
||||||
|
baseurl='https://www.tuxera.com'
|
||||||
|
url="${baseurl}/community/open-source-ntfs-3g/"
|
||||||
|
license=('GPL')
|
||||||
|
arch=('any')
|
||||||
|
options=(!strip libtool staticlibs)
|
||||||
|
makedepends=('devkitA64' 'dkp-toolchain-vars')
|
||||||
|
groups=('switch-portlibs')
|
||||||
|
source=(
|
||||||
|
"${baseurl}/opensource/${pkgbasename}-${pkgver}.tgz"
|
||||||
|
"${pkgname}-${pkgver}.patch"
|
||||||
|
)
|
||||||
|
sha256sums=(
|
||||||
|
'f20e36ee68074b845e3629e6bced4706ad053804cbaf062fbae60738f854170c'
|
||||||
|
'586cab5c8fc9b93078547c4b41625d99aea0e25fdeba1378385456c06be7391f'
|
||||||
|
)
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
patch -Np1 -i "${srcdir}/${pkgname}-${pkgver}.patch"
|
||||||
|
|
||||||
|
source "${DEVKITPRO}/switchvars.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
|
||||||
|
aclocal
|
||||||
|
automake
|
||||||
|
|
||||||
|
./configure --prefix="${PORTLIBS_PREFIX}" --libdir="${PORTLIBS_PREFIX}/lib" --host=aarch64-none-elf \
|
||||||
|
--disable-shared --enable-static --disable-device-default-io-ops \
|
||||||
|
--disable-ntfs-3g --disable-ntfsprogs --disable-plugins --disable-crypto \
|
||||||
|
--without-uuid --without-hd
|
||||||
|
|
||||||
|
make
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "${pkgbasename}-${pkgver}"
|
||||||
|
|
||||||
|
make DESTDIR="${pkgdir}" install
|
||||||
|
|
||||||
|
install -Dm644 COPYING "${pkgdir}${PORTLIBS_PREFIX}/licenses/${pkgname}/LICENSE"
|
||||||
|
install -Dm644 config.h "${pkgdir}${PORTLIBS_PREFIX}/include/ntfs-3g/config.h"
|
||||||
|
}
|
BIN
include/libusbhsfs/libntfs-3g/ntfs-3g_ntfsprogs-2022.10.3.tgz
Normal file
BIN
include/libusbhsfs/libntfs-3g/ntfs-3g_ntfsprogs-2022.10.3.tgz
Normal file
Binary file not shown.
Binary file not shown.
@ -0,0 +1,89 @@
|
|||||||
|
diff --git a/include/ntfs-3g/ntfstime.h b/include/ntfs-3g/ntfstime.h
|
||||||
|
index f3a89dd8..ce0d4586 100644
|
||||||
|
--- a/include/ntfs-3g/ntfstime.h
|
||||||
|
+++ b/include/ntfs-3g/ntfstime.h
|
||||||
|
@@ -36,6 +36,10 @@
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
+#ifdef _SYS__TIMESPEC_H_
|
||||||
|
+#define __timespec_defined
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
/*
|
||||||
|
* assume "struct timespec" is not defined if st_mtime is not defined
|
||||||
|
*/
|
||||||
|
diff --git a/include/ntfs-3g/param.h b/include/ntfs-3g/param.h
|
||||||
|
index ebc8a0ae..6c43c230 100644
|
||||||
|
--- a/include/ntfs-3g/param.h
|
||||||
|
+++ b/include/ntfs-3g/param.h
|
||||||
|
@@ -22,6 +22,22 @@
|
||||||
|
#ifndef _NTFS_PARAM_H
|
||||||
|
#define _NTFS_PARAM_H
|
||||||
|
|
||||||
|
+#ifndef MINORBITS
|
||||||
|
+#define MINORBITS 20
|
||||||
|
+#endif
|
||||||
|
+#ifndef MINORMASK
|
||||||
|
+#define MINORMASK ((1U << MINORBITS) - 1)
|
||||||
|
+#endif
|
||||||
|
+#ifndef major
|
||||||
|
+#define major(dev) ((unsigned int) ((dev) >> MINORBITS))
|
||||||
|
+#endif
|
||||||
|
+#ifndef minor
|
||||||
|
+#define minor(dev) ((unsigned int) ((dev) & MINORMASK))
|
||||||
|
+#endif
|
||||||
|
+#ifndef makedev
|
||||||
|
+#define makedev(ma,mi) (((ma) << MINORBITS) | (mi))
|
||||||
|
+#endif
|
||||||
|
+
|
||||||
|
#define CACHE_INODE_SIZE 32 /* inode cache, zero or >= 3 and not too big */
|
||||||
|
#define CACHE_NIDATA_SIZE 64 /* idata cache, zero or >= 3 and not too big */
|
||||||
|
#define CACHE_LOOKUP_SIZE 64 /* lookup cache, zero or >= 3 and not too big */
|
||||||
|
diff --git a/libntfs-3g/Makefile.am b/libntfs-3g/Makefile.am
|
||||||
|
index 6feba7d7..83493c3a 100644
|
||||||
|
--- a/libntfs-3g/Makefile.am
|
||||||
|
+++ b/libntfs-3g/Makefile.am
|
||||||
|
@@ -55,23 +55,7 @@ libntfs_3g_la_SOURCES += unix_io.c
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
-# We may need to move .so files to root
|
||||||
|
-# And create ldscript or symbolic link from /usr
|
||||||
|
install-exec-hook: install-rootlibLTLIBRARIES
|
||||||
|
-if INSTALL_LIBRARY
|
||||||
|
- if [ ! "$(rootlibdir)" -ef "$(libdir)" ]; then \
|
||||||
|
- $(MV) -f "$(DESTDIR)/$(libdir)"/libntfs-3g.so* "$(DESTDIR)/$(rootlibdir)"; \
|
||||||
|
- fi
|
||||||
|
-if GENERATE_LDSCRIPT
|
||||||
|
- if [ ! "$(rootlibdir)" -ef "$(libdir)" ]; then \
|
||||||
|
- $(install_sh_PROGRAM) "libntfs-3g.script.so" "$(DESTDIR)/$(libdir)/libntfs-3g.so"; \
|
||||||
|
- fi
|
||||||
|
-else
|
||||||
|
- if [ ! "$(rootlibdir)" -ef "$(libdir)" ]; then \
|
||||||
|
- $(LN_S) "$(rootlibdir)/libntfs-3g.so" "$(DESTDIR)/$(libdir)/libntfs-3g.so"; \
|
||||||
|
- fi
|
||||||
|
-endif
|
||||||
|
-endif
|
||||||
|
|
||||||
|
uninstall-local:
|
||||||
|
if INSTALL_LIBRARY
|
||||||
|
@@ -81,4 +65,3 @@ endif
|
||||||
|
if ENABLE_NTFSPROGS
|
||||||
|
libs: $(lib_LTLIBRARIES)
|
||||||
|
endif
|
||||||
|
-
|
||||||
|
diff --git a/libntfs-3g/ioctl.c b/libntfs-3g/ioctl.c
|
||||||
|
index b059a53f..a3a56722 100644
|
||||||
|
--- a/libntfs-3g/ioctl.c
|
||||||
|
+++ b/libntfs-3g/ioctl.c
|
||||||
|
@@ -48,7 +48,9 @@
|
||||||
|
#ifdef HAVE_LIMITS_H
|
||||||
|
#include <limits.h>
|
||||||
|
#endif
|
||||||
|
+#ifdef HAVE_SYSLOG_H
|
||||||
|
#include <syslog.h>
|
||||||
|
+#endif
|
||||||
|
#ifdef HAVE_SYS_TYPES_H
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
150
include/libusbhsfs/source/fatfs/diskio.c
Normal file
150
include/libusbhsfs/source/fatfs/diskio.c
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2019 */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* If a working storage control module is available, it should be */
|
||||||
|
/* attached to the FatFs via a glue function rather than modifying it. */
|
||||||
|
/* This is an example of glue functions to attach various exsisting */
|
||||||
|
/* storage control modules to the FatFs module with a defined API. */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include "ff.h" /* Obtains integer types */
|
||||||
|
#include "diskio.h" /* Declarations of disk functions */
|
||||||
|
|
||||||
|
#include "../usbhsfs_utils.h"
|
||||||
|
#include "../usbhsfs_manager.h"
|
||||||
|
#include "../usbhsfs_scsi.h"
|
||||||
|
|
||||||
|
/* Reference for needed FATFS impl functions: http://irtos.sourceforge.net/FAT32_ChaN/doc/en/appnote.html#port */
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Get Drive Status */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
DSTATUS ff_disk_status (
|
||||||
|
BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
(void)pdrv;
|
||||||
|
|
||||||
|
/* We take care of this. */
|
||||||
|
return RES_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Inidialize a Drive */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
DSTATUS ff_disk_initialize (
|
||||||
|
BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
(void)pdrv;
|
||||||
|
|
||||||
|
/* We take care of this. */
|
||||||
|
return RES_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Read Sector(s) */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
DRESULT ff_disk_read (
|
||||||
|
BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||||
|
BYTE *buff, /* Data buffer to store read data */
|
||||||
|
LBA_t sector, /* Start sector in LBA */
|
||||||
|
UINT count /* Number of sectors to read */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = NULL;
|
||||||
|
DRESULT ret = RES_PARERR;
|
||||||
|
|
||||||
|
/* Get LUN context and read logical blocks. */
|
||||||
|
lun_ctx = usbHsFsManagerGetLogicalUnitContextForFatFsDriveNumber(pdrv);
|
||||||
|
if (lun_ctx && usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, buff, sector, count)) ret = RES_OK;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Write Sector(s) */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
DRESULT ff_disk_write (
|
||||||
|
BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||||
|
const BYTE *buff, /* Data to be written */
|
||||||
|
LBA_t sector, /* Start sector in LBA */
|
||||||
|
UINT count /* Number of sectors to write */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = NULL;
|
||||||
|
DRESULT ret = RES_PARERR;
|
||||||
|
|
||||||
|
/* Get LUN context and write logical blocks. */
|
||||||
|
lun_ctx = usbHsFsManagerGetLogicalUnitContextForFatFsDriveNumber(pdrv);
|
||||||
|
if (lun_ctx && usbHsFsScsiWriteLogicalUnitBlocks(lun_ctx, buff, sector, count)) ret = RES_OK;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
/* Miscellaneous Functions */
|
||||||
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
DRESULT ff_disk_ioctl (
|
||||||
|
BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||||
|
BYTE cmd, /* Control code */
|
||||||
|
void *buff /* Buffer to send/receive control data */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = NULL;
|
||||||
|
DRESULT ret = RES_PARERR;
|
||||||
|
|
||||||
|
/* Get LUN context. */
|
||||||
|
lun_ctx = usbHsFsManagerGetLogicalUnitContextForFatFsDriveNumber(pdrv);
|
||||||
|
if (lun_ctx)
|
||||||
|
{
|
||||||
|
/* Process control code. */
|
||||||
|
switch(cmd)
|
||||||
|
{
|
||||||
|
case CTRL_SYNC:
|
||||||
|
ret = RES_OK;
|
||||||
|
break;
|
||||||
|
case GET_SECTOR_COUNT:
|
||||||
|
*(LBA_t*)buff = lun_ctx->block_count;
|
||||||
|
ret = RES_OK;
|
||||||
|
break;
|
||||||
|
case GET_SECTOR_SIZE:
|
||||||
|
*(WORD*)buff = lun_ctx->block_length;
|
||||||
|
ret = RES_OK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !FF_FS_NORTC /* Get system time */
|
||||||
|
DWORD get_fattime(void)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 timestamp = 0;
|
||||||
|
struct tm timeinfo = {0};
|
||||||
|
DWORD output = FAT_TIMESTAMP(FF_NORTC_YEAR, FF_NORTC_MON, FF_NORTC_MDAY, 0, 0, 0); /* Use FF_NORTC values by default. */
|
||||||
|
|
||||||
|
/* Try to retrieve time from time services. */
|
||||||
|
rc = timeGetCurrentTime(TimeType_LocalSystemClock, ×tamp);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
localtime_r((time_t*)×tamp, &timeinfo);
|
||||||
|
output = FAT_TIMESTAMP(timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
#endif
|
77
include/libusbhsfs/source/fatfs/diskio.h
Normal file
77
include/libusbhsfs/source/fatfs/diskio.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*-----------------------------------------------------------------------/
|
||||||
|
/ Low level disk interface modlue include file (C)ChaN, 2019 /
|
||||||
|
/-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#ifndef _DISKIO_DEFINED
|
||||||
|
#define _DISKIO_DEFINED
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Status of Disk Functions */
|
||||||
|
typedef BYTE DSTATUS;
|
||||||
|
|
||||||
|
/* Results of Disk Functions */
|
||||||
|
typedef enum {
|
||||||
|
RES_OK = 0, /* 0: Successful */
|
||||||
|
RES_ERROR, /* 1: R/W Error */
|
||||||
|
RES_WRPRT, /* 2: Write Protected */
|
||||||
|
RES_NOTRDY, /* 3: Not Ready */
|
||||||
|
RES_PARERR /* 4: Invalid Parameter */
|
||||||
|
} DRESULT;
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------------*/
|
||||||
|
/* Prototypes for disk control functions */
|
||||||
|
|
||||||
|
|
||||||
|
DSTATUS ff_disk_initialize (BYTE pdrv);
|
||||||
|
DSTATUS ff_disk_status (BYTE pdrv);
|
||||||
|
DRESULT ff_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
|
||||||
|
DRESULT ff_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
|
||||||
|
DRESULT ff_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
|
||||||
|
|
||||||
|
|
||||||
|
/* Disk Status Bits (DSTATUS) */
|
||||||
|
|
||||||
|
#define STA_NOINIT 0x01 /* Drive not initialized */
|
||||||
|
#define STA_NODISK 0x02 /* No medium in the drive */
|
||||||
|
#define STA_PROTECT 0x04 /* Write protected */
|
||||||
|
|
||||||
|
|
||||||
|
/* Command code for ff_disk_ioctrl fucntion */
|
||||||
|
|
||||||
|
/* Generic command (Used by FatFs) */
|
||||||
|
#define CTRL_SYNC 0 /* Complete pending write process */
|
||||||
|
#define GET_SECTOR_COUNT 1 /* Get media size */
|
||||||
|
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
|
||||||
|
#define GET_BLOCK_SIZE 3 /* Get erase block size */
|
||||||
|
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
|
||||||
|
|
||||||
|
/* Generic command (Not used by FatFs) */
|
||||||
|
#define CTRL_POWER 5 /* Get/Set power status */
|
||||||
|
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
|
||||||
|
#define CTRL_EJECT 7 /* Eject media */
|
||||||
|
#define CTRL_FORMAT 8 /* Create physical format on the media */
|
||||||
|
|
||||||
|
/* MMC/SDC specific ioctl command */
|
||||||
|
#define MMC_GET_TYPE 10 /* Get card type */
|
||||||
|
#define MMC_GET_CSD 11 /* Get CSD */
|
||||||
|
#define MMC_GET_CID 12 /* Get CID */
|
||||||
|
#define MMC_GET_OCR 13 /* Get OCR */
|
||||||
|
#define MMC_GET_SDSTAT 14 /* Get SD status */
|
||||||
|
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
|
||||||
|
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
|
||||||
|
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
|
||||||
|
|
||||||
|
/* ATA/CF specific ioctl command */
|
||||||
|
#define ATA_GET_REV 20 /* Get F/W revision */
|
||||||
|
#define ATA_GET_MODEL 21 /* Get model name */
|
||||||
|
#define ATA_GET_SN 22 /* Get serial number */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
5917
include/libusbhsfs/source/fatfs/ff.c
Normal file
5917
include/libusbhsfs/source/fatfs/ff.c
Normal file
File diff suppressed because it is too large
Load Diff
396
include/libusbhsfs/source/fatfs/ff.h
Normal file
396
include/libusbhsfs/source/fatfs/ff.h
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
/*----------------------------------------------------------------------------/
|
||||||
|
/ FatFs - Generic FAT Filesystem module R0.15 /
|
||||||
|
/-----------------------------------------------------------------------------/
|
||||||
|
/
|
||||||
|
/ Copyright (C) 2022, ChaN, all right reserved.
|
||||||
|
/
|
||||||
|
/ FatFs module is an open source software. Redistribution and use of FatFs in
|
||||||
|
/ source and binary forms, with or without modification, are permitted provided
|
||||||
|
/ that the following condition is met:
|
||||||
|
|
||||||
|
/ 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
/ this condition and the following disclaimer.
|
||||||
|
/
|
||||||
|
/ This software is provided by the copyright holder and contributors "AS IS"
|
||||||
|
/ and any warranties related to this software are DISCLAIMED.
|
||||||
|
/ The copyright owner or contributors be NOT LIABLE for any damages caused
|
||||||
|
/ by use of this software.
|
||||||
|
/
|
||||||
|
/----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FF_DEFINED
|
||||||
|
#define FF_DEFINED 80286 /* Revision ID */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ffconf.h" /* FatFs configuration options */
|
||||||
|
|
||||||
|
#if FF_DEFINED != FFCONF_DEF
|
||||||
|
#error Wrong configuration file (ffconf.h).
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Integer types used for FatFs API */
|
||||||
|
|
||||||
|
#if defined(_WIN32) /* Windows VC++ (for development only) */
|
||||||
|
#define FF_INTDEF 2
|
||||||
|
#include <windows.h>
|
||||||
|
typedef unsigned __int64 QWORD;
|
||||||
|
#include <float.h>
|
||||||
|
#define isnan(v) _isnan(v)
|
||||||
|
#define isinf(v) (!_finite(v))
|
||||||
|
|
||||||
|
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
|
||||||
|
#define FF_INTDEF 2
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||||
|
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||||
|
typedef uint16_t WORD; /* 16-bit unsigned integer */
|
||||||
|
typedef uint32_t DWORD; /* 32-bit unsigned integer */
|
||||||
|
typedef uint64_t QWORD; /* 64-bit unsigned integer */
|
||||||
|
typedef WORD WCHAR; /* UTF-16 character type */
|
||||||
|
|
||||||
|
#else /* Earlier than C99 */
|
||||||
|
#define FF_INTDEF 1
|
||||||
|
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||||
|
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||||
|
typedef unsigned short WORD; /* 16-bit unsigned integer */
|
||||||
|
typedef unsigned long DWORD; /* 32-bit unsigned integer */
|
||||||
|
typedef WORD WCHAR; /* UTF-16 character type */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Type of file size and LBA variables */
|
||||||
|
|
||||||
|
#if FF_FS_EXFAT
|
||||||
|
#if FF_INTDEF != 2
|
||||||
|
#error exFAT feature wants C99 or later
|
||||||
|
#endif
|
||||||
|
typedef QWORD FSIZE_t;
|
||||||
|
#if FF_LBA64
|
||||||
|
typedef QWORD LBA_t;
|
||||||
|
#else
|
||||||
|
typedef DWORD LBA_t;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#if FF_LBA64
|
||||||
|
#error exFAT needs to be enabled when enable 64-bit LBA
|
||||||
|
#endif
|
||||||
|
typedef DWORD FSIZE_t;
|
||||||
|
typedef DWORD LBA_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Type of path name strings on FatFs API (TCHAR) */
|
||||||
|
|
||||||
|
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
|
||||||
|
typedef WCHAR TCHAR;
|
||||||
|
#define _T(x) L ## x
|
||||||
|
#define _TEXT(x) L ## x
|
||||||
|
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
|
||||||
|
typedef char TCHAR;
|
||||||
|
#define _T(x) u8 ## x
|
||||||
|
#define _TEXT(x) u8 ## x
|
||||||
|
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
|
||||||
|
typedef DWORD TCHAR;
|
||||||
|
#define _T(x) U ## x
|
||||||
|
#define _TEXT(x) U ## x
|
||||||
|
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
|
||||||
|
#error Wrong FF_LFN_UNICODE setting
|
||||||
|
#else /* ANSI/OEM code in SBCS/DBCS */
|
||||||
|
typedef char TCHAR;
|
||||||
|
#define _T(x) x
|
||||||
|
#define _TEXT(x) x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Filesystem object structure (FATFS) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BYTE fs_type; /* Filesystem type (0:not mounted) */
|
||||||
|
BYTE pdrv; /* Volume hosting physical drive */
|
||||||
|
#if FF_FS_REENTRANT
|
||||||
|
BYTE ldrv; /* Logical drive number */
|
||||||
|
#endif
|
||||||
|
BYTE ro_flag; /* Read-only flag */
|
||||||
|
BYTE n_fats; /* Number of FATs (1 or 2) */
|
||||||
|
BYTE wflag; /* win[] status (b0:dirty) */
|
||||||
|
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
|
||||||
|
WORD id; /* Volume mount ID */
|
||||||
|
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
|
||||||
|
WORD csize; /* Cluster size [sectors] */
|
||||||
|
#if FF_MAX_SS != FF_MIN_SS
|
||||||
|
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
|
||||||
|
#endif
|
||||||
|
#if FF_USE_LFN
|
||||||
|
WCHAR* lfnbuf; /* LFN working buffer */
|
||||||
|
#endif
|
||||||
|
#if FF_FS_EXFAT
|
||||||
|
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
|
||||||
|
#endif
|
||||||
|
DWORD last_clst; /* Last allocated cluster */
|
||||||
|
DWORD free_clst; /* Number of free clusters */
|
||||||
|
DWORD cdir; /* Current directory start cluster (0:root) */
|
||||||
|
#if FF_FS_EXFAT
|
||||||
|
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
|
||||||
|
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
|
||||||
|
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
|
||||||
|
#endif
|
||||||
|
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
|
||||||
|
DWORD fsize; /* Number of sectors per FAT */
|
||||||
|
LBA_t volbase; /* Volume base sector */
|
||||||
|
LBA_t fatbase; /* FAT base sector */
|
||||||
|
LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
|
||||||
|
LBA_t database; /* Data base sector */
|
||||||
|
#if FF_FS_EXFAT
|
||||||
|
LBA_t bitbase; /* Allocation bitmap base sector */
|
||||||
|
#endif
|
||||||
|
LBA_t winsect; /* Current sector appearing in the win[] */
|
||||||
|
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
|
||||||
|
} FATFS;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Object ID and allocation information (FFOBJID) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FATFS* fs; /* Pointer to the hosting volume of this object */
|
||||||
|
WORD id; /* Hosting volume's mount ID */
|
||||||
|
BYTE attr; /* Object attribute */
|
||||||
|
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
|
||||||
|
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
|
||||||
|
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
|
||||||
|
#if FF_FS_EXFAT
|
||||||
|
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
|
||||||
|
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
|
||||||
|
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
|
||||||
|
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
|
||||||
|
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
|
||||||
|
#endif
|
||||||
|
#if FF_FS_LOCK
|
||||||
|
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
|
||||||
|
#endif
|
||||||
|
} FFOBJID;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* File object structure (FIL) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
|
||||||
|
BYTE flag; /* File status flags */
|
||||||
|
BYTE err; /* Abort flag (error code) */
|
||||||
|
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
|
||||||
|
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
|
||||||
|
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
|
||||||
|
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
|
||||||
|
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
|
||||||
|
#if FF_USE_FASTSEEK
|
||||||
|
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
|
||||||
|
#endif
|
||||||
|
#if !FF_FS_TINY
|
||||||
|
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
|
||||||
|
#endif
|
||||||
|
} FIL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Directory object structure (DIR) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FFOBJID obj; /* Object identifier */
|
||||||
|
DWORD dptr; /* Current read/write offset */
|
||||||
|
DWORD clust; /* Current cluster */
|
||||||
|
LBA_t sect; /* Current sector (0:Read operation has terminated) */
|
||||||
|
BYTE* dir; /* Pointer to the directory item in the win[] */
|
||||||
|
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
|
||||||
|
#if FF_USE_LFN
|
||||||
|
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
|
||||||
|
#endif
|
||||||
|
#if FF_USE_FIND
|
||||||
|
const TCHAR* pat; /* Pointer to the name matching pattern */
|
||||||
|
#endif
|
||||||
|
} DIR;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* File information structure (FILINFO) */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FSIZE_t fsize; /* File size */
|
||||||
|
WORD fdate; /* Modified date */
|
||||||
|
WORD ftime; /* Modified time */
|
||||||
|
BYTE fattrib; /* File attribute */
|
||||||
|
#if FF_USE_LFN
|
||||||
|
TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */
|
||||||
|
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
|
||||||
|
#else
|
||||||
|
TCHAR fname[12 + 1]; /* File name */
|
||||||
|
#endif
|
||||||
|
} FILINFO;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* File function return code (FRESULT) */
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FR_OK = 0, /* (0) Succeeded */
|
||||||
|
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
|
||||||
|
FR_INT_ERR, /* (2) Assertion failed */
|
||||||
|
FR_NOT_READY, /* (3) The physical drive cannot work */
|
||||||
|
FR_NO_FILE, /* (4) Could not find the file */
|
||||||
|
FR_NO_PATH, /* (5) Could not find the path */
|
||||||
|
FR_INVALID_NAME, /* (6) The path name format is invalid */
|
||||||
|
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
|
||||||
|
FR_EXIST, /* (8) Access denied due to prohibited access */
|
||||||
|
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
|
||||||
|
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
|
||||||
|
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
|
||||||
|
FR_NOT_ENABLED, /* (12) The volume has no work area */
|
||||||
|
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
|
||||||
|
FR_TIMEOUT, /* (14) Could not get a grant to access the volume within defined period */
|
||||||
|
FR_LOCKED, /* (15) The operation is rejected according to the file sharing policy */
|
||||||
|
FR_NOT_ENOUGH_CORE, /* (16) LFN working buffer could not be allocated */
|
||||||
|
FR_TOO_MANY_OPEN_FILES, /* (17) Number of open files > FF_FS_LOCK */
|
||||||
|
FR_INVALID_PARAMETER /* (18) Given parameter is invalid */
|
||||||
|
} FRESULT;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
/* FatFs Module Application Interface */
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
FRESULT ff_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
|
||||||
|
FRESULT ff_close (FIL* fp); /* Close an open file object */
|
||||||
|
FRESULT ff_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
|
||||||
|
FRESULT ff_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
|
||||||
|
FRESULT ff_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
|
||||||
|
FRESULT ff_truncate (FIL* fp); /* Truncate the file */
|
||||||
|
FRESULT ff_sync (FIL* fp); /* Flush cached data of the writing file */
|
||||||
|
FRESULT ff_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
|
||||||
|
FRESULT ff_closedir (DIR* dp); /* Close an open directory */
|
||||||
|
FRESULT ff_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
|
||||||
|
FRESULT ff_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
|
||||||
|
FRESULT ff_findnext (DIR* dp, FILINFO* fno); /* Find next file */
|
||||||
|
FRESULT ff_mkdir (const TCHAR* path); /* Create a sub directory */
|
||||||
|
FRESULT ff_unlink (const TCHAR* path); /* Delete an existing file or directory */
|
||||||
|
FRESULT _ff_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
|
||||||
|
FRESULT ff_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
|
||||||
|
FRESULT ff_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
|
||||||
|
FRESULT ff_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
|
||||||
|
FRESULT ff_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
|
||||||
|
FRESULT ff_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
|
||||||
|
FRESULT ff_setlabel (const TCHAR* label); /* Set volume label */
|
||||||
|
FRESULT ff_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
|
||||||
|
FRESULT ff_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
|
||||||
|
FRESULT ff_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
|
||||||
|
FRESULT ff_setcp (WORD cp); /* Set current code page */
|
||||||
|
int ff_putc (TCHAR c, FIL* fp); /* Put a character to the file */
|
||||||
|
int ff_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
|
||||||
|
int ff_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
|
||||||
|
TCHAR* ff_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
|
||||||
|
|
||||||
|
/* Some API fucntions are implemented as macro */
|
||||||
|
|
||||||
|
#define ff_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
|
||||||
|
#define ff_error(fp) ((fp)->err)
|
||||||
|
#define ff_tell(fp) ((fp)->fptr)
|
||||||
|
#define ff_size(fp) ((fp)->obj.objsize)
|
||||||
|
#define ff_rewind(fp) ff_lseek((fp), 0)
|
||||||
|
#define ff_rewinddir(dp) ff_readdir((dp), 0)
|
||||||
|
#define ff_rmdir(path) ff_unlink(path)
|
||||||
|
#define ff_unmount(path) ff_mount(0, path, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
/* Additional Functions */
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* RTC function (provided by user) */
|
||||||
|
#define FAT_TIMESTAMP(year, mon, mday, hour, min, sec) ({ \
|
||||||
|
DWORD fat_year = (DWORD)year, fat_mon = (DWORD)mon, fat_mday = (DWORD)mday, fat_hour = (DWORD)hour, fat_min = (DWORD)min, fat_sec = (DWORD)sec; \
|
||||||
|
if (fat_year < 1980 || fat_year > 2107) fat_year = FF_NORTC_YEAR; \
|
||||||
|
fat_year -= 1980; \
|
||||||
|
if (fat_mon < 1 || fat_mon > 12) fat_mon = FF_NORTC_MON; \
|
||||||
|
if (fat_mday < 1 || fat_mday > 31) fat_mday = FF_NORTC_MDAY; \
|
||||||
|
if (fat_hour > 23) fat_hour = 0; \
|
||||||
|
if (fat_min > 59) fat_min = 0; \
|
||||||
|
if (fat_sec > 58) fat_sec = 58; \
|
||||||
|
fat_sec /= 2; \
|
||||||
|
DWORD timestamp = (((fat_year << 25) & (DWORD)0xFE000000) | ((fat_mon << 21) & (DWORD)0x01E00000) | ((fat_mday << 16) & (DWORD)0x001F0000) | ((fat_hour << 11) & (DWORD)0x0000F800) | ((fat_min << 5) & (DWORD)0x000007E0) | (fat_sec & (DWORD)0x0000001F)); \
|
||||||
|
timestamp; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#if !FF_FS_NORTC
|
||||||
|
DWORD get_fattime (void); /* Get current time */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* LFN support functions (defined in ffunicode.c) */
|
||||||
|
|
||||||
|
#if FF_USE_LFN >= 1
|
||||||
|
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
|
||||||
|
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
|
||||||
|
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* O/S dependent functions (samples available in ffsystem.c) */
|
||||||
|
|
||||||
|
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
|
||||||
|
void* ff_memalloc (UINT msize); /* Allocate memory block */
|
||||||
|
void ff_memfree (void* mblock); /* Free memory block */
|
||||||
|
#endif
|
||||||
|
#if FF_FS_REENTRANT /* Sync functions */
|
||||||
|
int ff_mutex_create (int vol); /* Create a sync object */
|
||||||
|
void ff_mutex_delete (int vol); /* Delete a sync object */
|
||||||
|
int ff_mutex_take (int vol); /* Lock sync object */
|
||||||
|
void ff_mutex_give (int vol); /* Unlock sync object */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
/* Flags and Offset Address */
|
||||||
|
/*--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* File access mode and open method flags (3rd argument of ff_open) */
|
||||||
|
#define FA_READ 0x01
|
||||||
|
#define FA_WRITE 0x02
|
||||||
|
#define FA_OPEN_EXISTING 0x00
|
||||||
|
#define FA_CREATE_NEW 0x04
|
||||||
|
#define FA_CREATE_ALWAYS 0x08
|
||||||
|
#define FA_OPEN_ALWAYS 0x10
|
||||||
|
#define FA_OPEN_APPEND 0x30
|
||||||
|
|
||||||
|
/* Fast seek controls (2nd argument of ff_lseek) */
|
||||||
|
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
|
||||||
|
|
||||||
|
/* Filesystem type (FATFS.fs_type) */
|
||||||
|
#define FS_FAT12 1
|
||||||
|
#define FS_FAT16 2
|
||||||
|
#define FS_FAT32 3
|
||||||
|
#define FS_EXFAT 4
|
||||||
|
|
||||||
|
/* File attribute bits for directory entry (FILINFO.fattrib) */
|
||||||
|
#define AM_RDO 0x01 /* Read only */
|
||||||
|
#define AM_HID 0x02 /* Hidden */
|
||||||
|
#define AM_SYS 0x04 /* System */
|
||||||
|
#define AM_DIR 0x10 /* Directory */
|
||||||
|
#define AM_ARC 0x20 /* Archive */
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* FF_DEFINED */
|
921
include/libusbhsfs/source/fatfs/ff_dev.c
Normal file
921
include/libusbhsfs/source/fatfs/ff_dev.c
Normal file
@ -0,0 +1,921 @@
|
|||||||
|
/*
|
||||||
|
* ff_dev.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Loosely based on fs_dev.c from libnx, et al.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "../usbhsfs_manager.h"
|
||||||
|
#include "../usbhsfs_mount.h"
|
||||||
|
|
||||||
|
/* Helper macros. */
|
||||||
|
|
||||||
|
#define ff_end goto end
|
||||||
|
#define ff_ended_with_error (_errno != 0)
|
||||||
|
#define ff_set_error(x) r->_errno = _errno = (x)
|
||||||
|
#define ff_set_error_and_exit(x) \
|
||||||
|
do { \
|
||||||
|
ff_set_error((x)); \
|
||||||
|
ff_end; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ff_declare_error_state int _errno = 0
|
||||||
|
#define ff_declare_file_state FIL *file = (FIL*)fd
|
||||||
|
#define ff_declare_dir_state DIR *dir = (DIR*)dirState->dirStruct
|
||||||
|
#define ff_declare_fs_ctx UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = (UsbHsFsDriveLogicalUnitFileSystemContext*)r->deviceData
|
||||||
|
#define ff_declare_lun_ctx UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx
|
||||||
|
#define ff_declare_drive_ctx UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx
|
||||||
|
#define ff_declare_vol_state FATFS *fatfs = fs_ctx->fatfs
|
||||||
|
|
||||||
|
#define ff_lock_drive_ctx ff_declare_fs_ctx; \
|
||||||
|
ff_declare_lun_ctx; \
|
||||||
|
ff_declare_drive_ctx; \
|
||||||
|
bool drive_ctx_valid = usbHsFsManagerIsDriveContextPointerValid(drive_ctx); \
|
||||||
|
if (!drive_ctx_valid) ff_set_error_and_exit(ENODEV)
|
||||||
|
|
||||||
|
#define ff_unlock_drive_ctx if (drive_ctx_valid) mutexUnlock(&(drive_ctx->mutex))
|
||||||
|
|
||||||
|
#define ff_return(x) return (ff_ended_with_error ? -1 : (x))
|
||||||
|
#define ff_return_ptr(x) return (ff_ended_with_error ? NULL : (x))
|
||||||
|
#define ff_return_bool return (ff_ended_with_error ? false : true)
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int ffdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode);
|
||||||
|
static int ffdev_close(struct _reent *r, void *fd);
|
||||||
|
static ssize_t ffdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
||||||
|
static ssize_t ffdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
|
||||||
|
static off_t ffdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
|
||||||
|
static int ffdev_fstat(struct _reent *r, void *fd, struct stat *st);
|
||||||
|
static int ffdev_stat(struct _reent *r, const char *file, struct stat *st);
|
||||||
|
static int ffdev_link(struct _reent *r, const char *existing, const char *newLink);
|
||||||
|
static int ffdev_unlink(struct _reent *r, const char *name);
|
||||||
|
static int ffdev_chdir(struct _reent *r, const char *name);
|
||||||
|
static int ffdev_rename(struct _reent *r, const char *oldName, const char *newName);
|
||||||
|
static int ffdev_mkdir(struct _reent *r, const char *path, int mode);
|
||||||
|
static DIR_ITER* ffdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
|
||||||
|
static int ffdev_dirreset(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int ffdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
|
||||||
|
static int ffdev_dirclose(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int ffdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
|
||||||
|
static int ffdev_ftruncate(struct _reent *r, void *fd, off_t len);
|
||||||
|
static int ffdev_fsync(struct _reent *r, void *fd);
|
||||||
|
static int ffdev_chmod(struct _reent *r, const char *path, mode_t mode);
|
||||||
|
static int ffdev_fchmod(struct _reent *r, void *fd, mode_t mode);
|
||||||
|
static int ffdev_rmdir(struct _reent *r, const char *name);
|
||||||
|
static int ffdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]);
|
||||||
|
|
||||||
|
static bool ffdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath);
|
||||||
|
|
||||||
|
static void ffdev_fill_stat(struct stat *st, const FILINFO *info);
|
||||||
|
|
||||||
|
static int ffdev_translate_error(FRESULT res);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static const devoptab_t ffdev_devoptab = {
|
||||||
|
.name = NULL,
|
||||||
|
.structSize = sizeof(FIL),
|
||||||
|
.open_r = ffdev_open,
|
||||||
|
.close_r = ffdev_close,
|
||||||
|
.write_r = ffdev_write,
|
||||||
|
.read_r = ffdev_read,
|
||||||
|
.seek_r = ffdev_seek,
|
||||||
|
.fstat_r = ffdev_fstat, ///< Not supported by FatFs.
|
||||||
|
.stat_r = ffdev_stat,
|
||||||
|
.link_r = ffdev_link, ///< Not supported by FatFs.
|
||||||
|
.unlink_r = ffdev_unlink,
|
||||||
|
.chdir_r = ffdev_chdir,
|
||||||
|
.rename_r = ffdev_rename,
|
||||||
|
.mkdir_r = ffdev_mkdir,
|
||||||
|
.dirStateSize = sizeof(DIR),
|
||||||
|
.diropen_r = ffdev_diropen,
|
||||||
|
.dirreset_r = ffdev_dirreset,
|
||||||
|
.dirnext_r = ffdev_dirnext,
|
||||||
|
.dirclose_r = ffdev_dirclose,
|
||||||
|
.statvfs_r = ffdev_statvfs,
|
||||||
|
.ftruncate_r = ffdev_ftruncate,
|
||||||
|
.fsync_r = ffdev_fsync,
|
||||||
|
.deviceData = NULL,
|
||||||
|
.chmod_r = ffdev_chmod, ///< Not supported by FatFs.
|
||||||
|
.fchmod_r = ffdev_fchmod, ///< Not supported by FatFs.
|
||||||
|
.rmdir_r = ffdev_rmdir,
|
||||||
|
.lstat_r = ffdev_stat, ///< Symlinks aren't supported, so we'll just alias lstat() to stat().
|
||||||
|
.utimes_r = ffdev_utimes
|
||||||
|
};
|
||||||
|
|
||||||
|
const devoptab_t *ffdev_get_devoptab()
|
||||||
|
{
|
||||||
|
return &ffdev_devoptab;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
BYTE ffdev_flags = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
/* Check access mode. */
|
||||||
|
switch(flags & O_ACCMODE)
|
||||||
|
{
|
||||||
|
case O_RDONLY: /* Read-only. Don't allow append flag. */
|
||||||
|
if (flags & O_APPEND) ff_set_error_and_exit(EINVAL);
|
||||||
|
ffdev_flags |= FA_READ;
|
||||||
|
break;
|
||||||
|
case O_WRONLY: /* Write-only. */
|
||||||
|
ffdev_flags |= FA_WRITE;
|
||||||
|
break;
|
||||||
|
case O_RDWR: /* Read and write. */
|
||||||
|
ffdev_flags |= (FA_READ | FA_WRITE);
|
||||||
|
break;
|
||||||
|
default: /* Invalid option. */
|
||||||
|
ff_set_error_and_exit(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & O_ACCMODE) != O_RDONLY)
|
||||||
|
{
|
||||||
|
if (flags & O_TRUNC)
|
||||||
|
{
|
||||||
|
/* Create a new file. If the file exists, it will be truncated and overwritten. */
|
||||||
|
ffdev_flags |= FA_CREATE_ALWAYS;
|
||||||
|
} else
|
||||||
|
if (flags & O_CREAT)
|
||||||
|
{
|
||||||
|
/* O_EXCL set: create a new file. Fail if the file already exists. */
|
||||||
|
/* O_EXCL cleared: */
|
||||||
|
/* - O_APPEND set: open file. If it doesn't exist, it will be created. The file pointer will be set to EOF before each write. */
|
||||||
|
/* - O_APPEND cleared: open file. If it doesn't exist, it will be created. */
|
||||||
|
ffdev_flags |= ((flags & O_EXCL) ? FA_CREATE_NEW : ((flags & O_APPEND) ? FA_OPEN_APPEND : FA_OPEN_ALWAYS));
|
||||||
|
} else {
|
||||||
|
/* Open file. Fail if the file doesn't exist. */
|
||||||
|
ffdev_flags |= FA_OPEN_EXISTING;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Open file. Fail if the file doesn't exist. */
|
||||||
|
ffdev_flags |= FA_OPEN_EXISTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Opening file \"%s\" (\"%s\") with flags 0x%X (0x%X).", path, __usbhsfs_dev_path_buf, flags, ffdev_flags);
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(FIL));
|
||||||
|
|
||||||
|
/* Open file. */
|
||||||
|
res = ff_open(file, __usbhsfs_dev_path_buf, ffdev_flags);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_close(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Closing file from \"%u:\".", file->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Close file. */
|
||||||
|
res = ff_close(file);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(FIL));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ffdev_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
UINT bw = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with write access. */
|
||||||
|
if (!(file->flag & FA_WRITE)) ff_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
/* Check if the append flag is enabled. */
|
||||||
|
if ((file->flag & (FA_OPEN_APPEND & ~FA_OPEN_ALWAYS)) && !ff_eof(file))
|
||||||
|
{
|
||||||
|
/* Seek to EOF. */
|
||||||
|
res = ff_lseek(file, ff_size(file));
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Writing 0x%lX byte(s) to file in \"%u:\" at offset 0x%lX.", len, file->obj.fs->pdrv, ff_tell(file));
|
||||||
|
|
||||||
|
/* Write file data. */
|
||||||
|
res = ff_write(file, ptr, (UINT)len, &bw);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return((ssize_t)bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ffdev_read(struct _reent *r, void *fd, char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
UINT br = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with read access. */
|
||||||
|
if (!(file->flag & FA_READ)) ff_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Reading 0x%lX byte(s) from file in \"%u:\" at offset 0x%lX.", len, file->obj.fs->pdrv, ff_tell(file));
|
||||||
|
|
||||||
|
/* Read file data. */
|
||||||
|
res = ff_read(file, ptr, (UINT)len, &br);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return((ssize_t)br);
|
||||||
|
}
|
||||||
|
|
||||||
|
static off_t ffdev_seek(struct _reent *r, void *fd, off_t pos, int dir)
|
||||||
|
{
|
||||||
|
off_t offset = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Find the offset to seek from. */
|
||||||
|
switch(dir)
|
||||||
|
{
|
||||||
|
case SEEK_SET: /* Set absolute position relative to zero (start offset). */
|
||||||
|
break;
|
||||||
|
case SEEK_CUR: /* Set position relative to the current position. */
|
||||||
|
offset = (off_t)ff_tell(file);
|
||||||
|
break;
|
||||||
|
case SEEK_END: /* Set position relative to EOF. */
|
||||||
|
offset = (off_t)ff_size(file);
|
||||||
|
break;
|
||||||
|
default: /* Invalid option. */
|
||||||
|
ff_set_error_and_exit(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't allow negative seeks beyond the beginning of the file. */
|
||||||
|
if (pos < 0 && offset < -pos) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Calculate actual offset. */
|
||||||
|
offset += pos;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Seeking to offset 0x%lX from file in \"%u:\".", offset, file->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Perform file seek. */
|
||||||
|
res = ff_lseek(file, (FSIZE_t)offset);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_fstat(struct _reent *r, void *fd, struct stat *st)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
(void)st;
|
||||||
|
|
||||||
|
/* Not supported by FatFs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
|
{
|
||||||
|
FILINFO info = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!st) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, file, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting stats for \"%s\" (\"%s\").", file, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Get stats. */
|
||||||
|
res = ff_stat(__usbhsfs_dev_path_buf, &info);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
ffdev_fill_stat(st, &info);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_link(struct _reent *r, const char *existing, const char *newLink)
|
||||||
|
{
|
||||||
|
(void)existing;
|
||||||
|
(void)newLink;
|
||||||
|
|
||||||
|
/* Not supported by FatFs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_unlink(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, name, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Deleting \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Delete file. */
|
||||||
|
res = ff_unlink(__usbhsfs_dev_path_buf);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_chdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
DIR dir = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
size_t cwd_len = 0;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, name, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Changing current directory to \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
res = ff_opendir(&dir, __usbhsfs_dev_path_buf);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
ff_closedir(&dir);
|
||||||
|
|
||||||
|
/* Update current working directory. */
|
||||||
|
sprintf(fs_ctx->cwd, "%s", strchr(__usbhsfs_dev_path_buf, '/'));
|
||||||
|
|
||||||
|
cwd_len = strlen(fs_ctx->cwd);
|
||||||
|
if (fs_ctx->cwd[cwd_len - 1] != '/')
|
||||||
|
{
|
||||||
|
fs_ctx->cwd[cwd_len] = '/';
|
||||||
|
fs_ctx->cwd[cwd_len + 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set default devoptab device. */
|
||||||
|
usbHsFsMountSetDefaultDevoptabDevice(fs_ctx);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_rename(struct _reent *r, const char *oldName, const char *newName)
|
||||||
|
{
|
||||||
|
char old_path[MAX_PATH_LENGTH] = {0};
|
||||||
|
char *new_path = __usbhsfs_dev_path_buf;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input paths. */
|
||||||
|
if (!ffdev_fixpath(r, oldName, &fs_ctx, old_path) || !ffdev_fixpath(r, newName, &fs_ctx, new_path)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Renaming \"%s\" (\"%s\") to \"%s\" (\"%s\").", oldName, old_path, newName, new_path);
|
||||||
|
|
||||||
|
/* Rename entry. */
|
||||||
|
res = _ff_rename(old_path, new_path);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_mkdir(struct _reent *r, const char *path, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Creating directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Create directory. */
|
||||||
|
res = ff_mkdir(__usbhsfs_dev_path_buf);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DIR_ITER *ffdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
DIR_ITER *ret = NULL;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ff_declare_dir_state;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Opening directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(DIR));
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
res = ff_opendir(dir, __usbhsfs_dev_path_buf);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = dirState;
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return_ptr(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_dirreset(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ff_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Resetting directory state from \"%u:\".", dir->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
res = ff_rewinddir(dir);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat)
|
||||||
|
{
|
||||||
|
FILINFO info = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState || !filename || !filestat) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ff_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting info from next directory entry in \"%u:\".", dir->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Read directory. */
|
||||||
|
res = ff_readdir(dir, &info);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Check if we haven't reached EOD. */
|
||||||
|
/* FatFs returns an empty string if so. */
|
||||||
|
if (info.fname[0])
|
||||||
|
{
|
||||||
|
/* Copy filename. */
|
||||||
|
strcpy(filename, info.fname);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
ffdev_fill_stat(filestat, &info);
|
||||||
|
} else {
|
||||||
|
/* ENOENT signals EOD. */
|
||||||
|
ff_set_error(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_dirclose(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ff_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Closing directory from \"%u:\".", dir->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
res = ff_closedir(dir);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(DIR));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf)
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
|
||||||
|
char name[MOUNT_NAME_LENGTH] = {0};
|
||||||
|
DWORD free_clusters = 0;
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
ff_declare_vol_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!buf) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Generate volume name. */
|
||||||
|
sprintf(name, "%u:", fatfs->pdrv);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting filesystem stats for \"%s\" (\"%s\").", path, name);
|
||||||
|
|
||||||
|
/* Get volume information. */
|
||||||
|
res = ff_getfree(name, &free_clusters, &fatfs);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Fill filesystem stats. */
|
||||||
|
memset(buf, 0, sizeof(struct statvfs));
|
||||||
|
|
||||||
|
buf->f_bsize = fatfs->ssize; /* Sector size. */
|
||||||
|
buf->f_frsize = fatfs->ssize; /* Sector size. */
|
||||||
|
buf->f_blocks = ((fatfs->n_fatent - 2) * (DWORD)fatfs->csize); /* Total cluster count * cluster size in sectors. */
|
||||||
|
buf->f_bfree = (free_clusters * (DWORD)fatfs->csize); /* Free cluster count * cluster size in sectors. */
|
||||||
|
buf->f_bavail = buf->f_bfree; /* Free cluster count * cluster size in sectors. */
|
||||||
|
buf->f_files = 0;
|
||||||
|
buf->f_ffree = 0;
|
||||||
|
buf->f_favail = 0;
|
||||||
|
buf->f_fsid = fs_ctx->device_id;
|
||||||
|
buf->f_flag = ST_NOSUID;
|
||||||
|
buf->f_namemax = FF_LFN_BUF;
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_ftruncate(struct _reent *r, void *fd, off_t len)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || len < 0) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with write access. */
|
||||||
|
if (!(file->flag & FA_WRITE)) ff_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Truncating file in \"%u:\" to 0x%lX bytes.", file->obj.fs->pdrv, len);
|
||||||
|
|
||||||
|
/* Seek to the provided offset. */
|
||||||
|
res = ff_lseek(file, (FSIZE_t)len);
|
||||||
|
if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
/* Truncate file. */
|
||||||
|
res = ff_truncate(file);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_fsync(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_declare_file_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Synchronizing data for file in \"%u:\".", file->obj.fs->pdrv);
|
||||||
|
|
||||||
|
/* Synchronize file. */
|
||||||
|
res = ff_sync(file);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_chmod(struct _reent *r, const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
/* Not supported by FatFs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_fchmod(struct _reent *r, void *fd, mode_t mode)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
/* Not supported by FatFs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_rmdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
/* Exactly the same as ffdev_unlink(). */
|
||||||
|
return ffdev_unlink(r, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
time_t mtime = 0;
|
||||||
|
TimeCalendarTime caltime = {0};
|
||||||
|
DWORD timestamp = 0;
|
||||||
|
|
||||||
|
FILINFO info = {0};
|
||||||
|
FRESULT res = FR_OK;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
ff_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!ffdev_fixpath(r, filename, &fs_ctx, NULL)) ff_end;
|
||||||
|
|
||||||
|
/* Check if we should use the current time. */
|
||||||
|
/* We can only modify the last modification date and time. */
|
||||||
|
if (!times)
|
||||||
|
{
|
||||||
|
/* Get current time. */
|
||||||
|
mtime = time(NULL);
|
||||||
|
} else {
|
||||||
|
/* Only use full second precision from the provided timeval value. */
|
||||||
|
mtime = times[1].tv_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert POSIX timestamp to calendar time. */
|
||||||
|
rc = timeToCalendarTimeWithMyRule((u64)mtime, &caltime, NULL);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
/* Generate FAT timestamp. */
|
||||||
|
timestamp = FAT_TIMESTAMP(caltime.year, caltime.month, caltime.day, caltime.hour, caltime.minute, caltime.second);
|
||||||
|
|
||||||
|
/* Fill FILINFO time data. */
|
||||||
|
info.fdate = (WORD)(timestamp >> 16);
|
||||||
|
info.ftime = (WORD)(timestamp & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Setting last modification time for \"%s\" (\"%s\") to %u-%02u-%02u %02u:%02u:%02u (0x%04X%04X).", filename, __usbhsfs_dev_path_buf, caltime.year, caltime.month, caltime.day, caltime.hour, \
|
||||||
|
caltime.minute, caltime.second, info.fdate, info.ftime);
|
||||||
|
|
||||||
|
/* Change timestamp. */
|
||||||
|
res = ff_utime(__usbhsfs_dev_path_buf, &info);
|
||||||
|
if (res != FR_OK) ff_set_error(ffdev_translate_error(res));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_unlock_drive_ctx;
|
||||||
|
ff_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ffdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath)
|
||||||
|
{
|
||||||
|
FATFS *fatfs = NULL;
|
||||||
|
const u8 *p = (const u8*)path;
|
||||||
|
ssize_t units = 0;
|
||||||
|
u32 code = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
char name[MOUNT_NAME_LENGTH] = {0}, *outptr = (outpath ? outpath : __usbhsfs_dev_path_buf), *cwd = NULL;
|
||||||
|
|
||||||
|
ff_declare_error_state;
|
||||||
|
|
||||||
|
if (!r || !path || !*path || !fs_ctx || !*fs_ctx || !(fatfs = (*fs_ctx)->fatfs) || !(cwd = (*fs_ctx)->cwd)) ff_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Input path: \"%s\".", path);
|
||||||
|
|
||||||
|
/* Generate FatFs mount name ID. */
|
||||||
|
sprintf(name, "%u:", fatfs->pdrv);
|
||||||
|
|
||||||
|
/* Move the path pointer to the start of the actual path. */
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) ff_set_error_and_exit(EILSEQ);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ' && code != ':');
|
||||||
|
|
||||||
|
/* We found a colon; p points to the actual path. */
|
||||||
|
if (code == ':') path = (const char*)p;
|
||||||
|
|
||||||
|
/* Make sure there are no more colons and that the remainder of the string is valid UTF-8. */
|
||||||
|
p = (const u8*)path;
|
||||||
|
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) ff_set_error_and_exit(EILSEQ);
|
||||||
|
if (code == ':') ff_set_error_and_exit(EINVAL);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ');
|
||||||
|
|
||||||
|
/* Verify fixed path length. */
|
||||||
|
len = (strlen(name) + strlen(path));
|
||||||
|
if (path[0] != '/') len += strlen(cwd);
|
||||||
|
|
||||||
|
if (len >= MAX_PATH_LENGTH) ff_set_error_and_exit(ENAMETOOLONG);
|
||||||
|
|
||||||
|
/* Generate fixed path. */
|
||||||
|
if (path[0] == '/')
|
||||||
|
{
|
||||||
|
sprintf(outptr, "%s%s", name, path);
|
||||||
|
} else {
|
||||||
|
sprintf(outptr, "%s%s%s", name, cwd, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Fixed path: \"%s\".", outptr);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ff_return_bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ffdev_fill_stat(struct stat *st, const FILINFO *info)
|
||||||
|
{
|
||||||
|
struct tm timeinfo = {0};
|
||||||
|
|
||||||
|
/* Clear stat struct. */
|
||||||
|
memset(st, 0, sizeof(struct stat));
|
||||||
|
|
||||||
|
/* Fill stat struct. */
|
||||||
|
st->st_nlink = 1;
|
||||||
|
|
||||||
|
if (info->fattrib & AM_DIR)
|
||||||
|
{
|
||||||
|
/* We're dealing with a directory entry. */
|
||||||
|
st->st_mode = (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO);
|
||||||
|
} else {
|
||||||
|
/* We're dealing with a file entry. */
|
||||||
|
st->st_size = (off_t)info->fsize;
|
||||||
|
st->st_mode = (S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert date/time into an actual UTC POSIX timestamp using the system local time. */
|
||||||
|
timeinfo.tm_year = (((info->fdate >> 9) & 0x7F) + 80); /* DOS time: offset since 1980. POSIX time: offset since 1900. */
|
||||||
|
timeinfo.tm_mon = (((info->fdate >> 5) & 0xF) - 1); /* DOS time: 1-12 range (inclusive). POSIX time: 0-11 range (inclusive). */
|
||||||
|
timeinfo.tm_mday = (info->fdate & 0x1F);
|
||||||
|
timeinfo.tm_hour = ((info->ftime >> 11) & 0x1F);
|
||||||
|
timeinfo.tm_min = ((info->ftime >> 5) & 0x3F);
|
||||||
|
timeinfo.tm_sec = ((info->ftime & 0x1F) << 1); /* DOS time: 2-second intervals with a 0-29 range (inclusive, 58 seconds max). POSIX time: 0-59 range (inclusive). */
|
||||||
|
|
||||||
|
st->st_atime = 0; /* Not returned by FatFs + only available under exFAT. */
|
||||||
|
st->st_mtime = mktime(&timeinfo);
|
||||||
|
st->st_ctime = 0; /* Not returned by FatFs + only available under exFAT. */
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("DOS timestamp: 0x%04X%04X. Generated POSIX timestamp: %lu.", info->fdate, info->ftime, st->st_mtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ffdev_translate_error(FRESULT res)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch(res)
|
||||||
|
{
|
||||||
|
case FR_OK:
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
case FR_DISK_ERR:
|
||||||
|
case FR_NOT_READY:
|
||||||
|
ret = EIO;
|
||||||
|
break;
|
||||||
|
case FR_INT_ERR:
|
||||||
|
case FR_INVALID_NAME:
|
||||||
|
case FR_INVALID_PARAMETER:
|
||||||
|
ret = EINVAL;
|
||||||
|
break;
|
||||||
|
case FR_NO_FILE:
|
||||||
|
case FR_NO_PATH:
|
||||||
|
ret = ENOENT;
|
||||||
|
break;
|
||||||
|
case FR_DENIED:
|
||||||
|
ret = EACCES;
|
||||||
|
break;
|
||||||
|
case FR_EXIST:
|
||||||
|
ret = EEXIST;
|
||||||
|
break;
|
||||||
|
case FR_INVALID_OBJECT:
|
||||||
|
ret = EFAULT;
|
||||||
|
break;
|
||||||
|
case FR_WRITE_PROTECTED:
|
||||||
|
ret = EROFS;
|
||||||
|
break;
|
||||||
|
case FR_INVALID_DRIVE:
|
||||||
|
ret = ENODEV;
|
||||||
|
break;
|
||||||
|
case FR_NOT_ENABLED:
|
||||||
|
ret = ENOEXEC;
|
||||||
|
break;
|
||||||
|
case FR_NO_FILESYSTEM:
|
||||||
|
ret = ENFILE;
|
||||||
|
break;
|
||||||
|
case FR_TIMEOUT:
|
||||||
|
ret = EAGAIN;
|
||||||
|
break;
|
||||||
|
case FR_LOCKED:
|
||||||
|
ret = EBUSY;
|
||||||
|
break;
|
||||||
|
case FR_NOT_ENOUGH_CORE:
|
||||||
|
ret = ENOMEM;
|
||||||
|
break;
|
||||||
|
case FR_TOO_MANY_OPEN_FILES:
|
||||||
|
ret = EMFILE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = EPERM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("FRESULT: %u. Translated errno: %d.", res, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
17
include/libusbhsfs/source/fatfs/ff_dev.h
Normal file
17
include/libusbhsfs/source/fatfs/ff_dev.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* ff_dev.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __FF_DEV_H__
|
||||||
|
#define __FF_DEV_H__
|
||||||
|
|
||||||
|
const devoptab_t *ffdev_get_devoptab();
|
||||||
|
|
||||||
|
#endif /* __FF_DEV_H__ */
|
248
include/libusbhsfs/source/fatfs/ffconf.h
Normal file
248
include/libusbhsfs/source/fatfs/ffconf.h
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
#include "../usbhsfs_utils.h"
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------/
|
||||||
|
/ Configurations of FatFs Module
|
||||||
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define FFCONF_DEF 80286 /* Revision ID */
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------/
|
||||||
|
/ Function Configurations
|
||||||
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define FF_FS_MINIMIZE 0
|
||||||
|
/* This option defines minimization level to remove some basic API functions.
|
||||||
|
/
|
||||||
|
/ 0: Basic functions are fully enabled.
|
||||||
|
/ 1: ff_stat(), ff_getfree(), ff_unlink(), ff_mkdir(), ff_truncate() and ff_rename()
|
||||||
|
/ are removed.
|
||||||
|
/ 2: ff_opendir(), ff_readdir() and ff_closedir() are removed in addition to 1.
|
||||||
|
/ 3: ff_lseek() function is removed in addition to 2. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_FIND 0
|
||||||
|
/* This option switches filtered directory read functions, ff_findfirst() and
|
||||||
|
/ ff_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_FASTSEEK 0
|
||||||
|
/* This option switches fast seek function. (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_EXPAND 0
|
||||||
|
/* This option switches ff_expand function. (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_CHMOD 1
|
||||||
|
/* This option switches attribute manipulation functions, ff_chmod() and ff_utime().
|
||||||
|
/ (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_LABEL 0
|
||||||
|
/* This option switches volume label functions, ff_getlabel() and ff_setlabel().
|
||||||
|
/ (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_FORWARD 0
|
||||||
|
/* This option switches ff_forward() function. (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_STRFUNC 0
|
||||||
|
#define FF_PRINT_LLI 0
|
||||||
|
#define FF_PRINT_FLOAT 0
|
||||||
|
#define FF_STRF_ENCODE 3
|
||||||
|
/* FF_USE_STRFUNC switches string functions, ff_gets(), ff_putc(), ff_puts() and
|
||||||
|
/ ff_printf().
|
||||||
|
/
|
||||||
|
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||||
|
/ 1: Enable without LF-CRLF conversion.
|
||||||
|
/ 2: Enable with LF-CRLF conversion.
|
||||||
|
/
|
||||||
|
/ FF_PRINT_LLI = 1 makes ff_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||||
|
/ makes ff_printf() support floating point argument. These features want C99 or later.
|
||||||
|
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
|
||||||
|
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||||
|
/ to be read/written via those functions.
|
||||||
|
/
|
||||||
|
/ 0: ANSI/OEM in current CP
|
||||||
|
/ 1: Unicode in UTF-16LE
|
||||||
|
/ 2: Unicode in UTF-16BE
|
||||||
|
/ 3: Unicode in UTF-8
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------/
|
||||||
|
/ Locale and Namespace Configurations
|
||||||
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define FF_CODE_PAGE 932
|
||||||
|
/* This option specifies the OEM code page to be used on the target system.
|
||||||
|
/ Incorrect code page setting can cause a file open failure.
|
||||||
|
/
|
||||||
|
/ 437 - U.S.
|
||||||
|
/ 720 - Arabic
|
||||||
|
/ 737 - Greek
|
||||||
|
/ 771 - KBL
|
||||||
|
/ 775 - Baltic
|
||||||
|
/ 850 - Latin 1
|
||||||
|
/ 852 - Latin 2
|
||||||
|
/ 855 - Cyrillic
|
||||||
|
/ 857 - Turkish
|
||||||
|
/ 860 - Portuguese
|
||||||
|
/ 861 - Icelandic
|
||||||
|
/ 862 - Hebrew
|
||||||
|
/ 863 - Canadian French
|
||||||
|
/ 864 - Arabic
|
||||||
|
/ 865 - Nordic
|
||||||
|
/ 866 - Russian
|
||||||
|
/ 869 - Greek 2
|
||||||
|
/ 932 - Japanese (DBCS)
|
||||||
|
/ 936 - Simplified Chinese (DBCS)
|
||||||
|
/ 949 - Korean (DBCS)
|
||||||
|
/ 950 - Traditional Chinese (DBCS)
|
||||||
|
/ 0 - Include all code pages above and configured by ff_setcp()
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_LFN 3
|
||||||
|
#define FF_MAX_LFN 255
|
||||||
|
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||||
|
/
|
||||||
|
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||||
|
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||||
|
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||||
|
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||||
|
/
|
||||||
|
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
|
||||||
|
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||||
|
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||||
|
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||||
|
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
|
||||||
|
/ specification.
|
||||||
|
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||||
|
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||||
|
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_LFN_UNICODE 2
|
||||||
|
/* This option switches the character encoding on the API when LFN is enabled.
|
||||||
|
/
|
||||||
|
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||||
|
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||||
|
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||||
|
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||||
|
/
|
||||||
|
/ Also behavior of string I/O functions will be affected by this option.
|
||||||
|
/ When LFN is not enabled, this option has no effect. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_LFN_BUF 255
|
||||||
|
#define FF_SFN_BUF 12
|
||||||
|
/* This set of options defines size of file name members in the FILINFO structure
|
||||||
|
/ which is used to read out directory items. These values should be suffcient for
|
||||||
|
/ the file names to read. The maximum possible length of the read file name depends
|
||||||
|
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------/
|
||||||
|
/ Drive/Volume Configurations
|
||||||
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define FF_VOLUMES 64
|
||||||
|
/* Number of volumes (logical drives) to be used. (1-100) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_MIN_SS BLKDEV_MIN_BLOCK_SIZE
|
||||||
|
#define FF_MAX_SS BLKDEV_MAX_BLOCK_SIZE
|
||||||
|
/* This set of options configures the range of sector size to be supported. (512,
|
||||||
|
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||||
|
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||||
|
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
|
||||||
|
/ for variable sector size mode and ff_disk_ioctl() function needs to implement
|
||||||
|
/ GET_SECTOR_SIZE command. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_LBA64 1
|
||||||
|
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||||
|
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_USE_TRIM 0
|
||||||
|
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||||
|
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||||
|
/ ff_disk_ioctl() function. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------/
|
||||||
|
/ System Configurations
|
||||||
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define FF_FS_TINY 0
|
||||||
|
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||||
|
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
|
||||||
|
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||||
|
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_FS_EXFAT 1
|
||||||
|
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||||
|
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||||
|
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_FS_NORTC 0
|
||||||
|
#define FF_NORTC_MON 1
|
||||||
|
#define FF_NORTC_MDAY 1
|
||||||
|
#define FF_NORTC_YEAR 2023
|
||||||
|
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||||
|
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||||
|
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||||
|
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||||
|
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
|
||||||
|
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||||
|
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_FS_NOFSINFO 0
|
||||||
|
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||||
|
/ option, and ff_getfree() function at the first time after volume mount will force
|
||||||
|
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||||
|
/
|
||||||
|
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||||
|
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||||
|
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||||
|
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_FS_LOCK 64
|
||||||
|
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||||
|
/ and illegal operation to open objects.
|
||||||
|
/
|
||||||
|
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||||
|
/ should avoid illegal open, remove and rename to the open objects.
|
||||||
|
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||||
|
/ can be opened simultaneously under file lock control. Note that the file
|
||||||
|
/ lock control is independent of re-entrancy. */
|
||||||
|
|
||||||
|
|
||||||
|
#define FF_FS_REENTRANT 0
|
||||||
|
#define FF_FS_TIMEOUT 1000
|
||||||
|
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||||
|
/ module itself. Note that regardless of this option, file access to different
|
||||||
|
/ volume is always re-entrant and volume control functions, ff_mount(),
|
||||||
|
/ are always not re-entrant. Only file/directory access to the same volume is
|
||||||
|
/ under control of this featuer.
|
||||||
|
/
|
||||||
|
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||||
|
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||||
|
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
|
||||||
|
/ function, must be added to the project. Samples are available in ffsystem.c.
|
||||||
|
/
|
||||||
|
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*--- End of configuration options ---*/
|
207
include/libusbhsfs/source/fatfs/ffsystem.c
Normal file
207
include/libusbhsfs/source/fatfs/ffsystem.c
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* A Sample Code of User Provided OS Dependent Functions for FatFs */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include "ff.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if FF_USE_LFN == 3 /* Use dynamic memory allocation */
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Allocate/Free a Memory Block */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdlib.h> /* with POSIX API */
|
||||||
|
|
||||||
|
|
||||||
|
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
|
||||||
|
UINT msize /* Number of bytes to allocate */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return malloc((size_t)msize); /* Allocate a new memory block */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ff_memfree (
|
||||||
|
void* mblock /* Pointer to the memory block to free (no effect if null) */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
free(mblock); /* Free the memory block */
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if FF_FS_REENTRANT /* Mutal exclusion */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Definitions of Mutex */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define OS_TYPE 0 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */
|
||||||
|
|
||||||
|
|
||||||
|
#if OS_TYPE == 0 /* Win32 */
|
||||||
|
#include <windows.h>
|
||||||
|
static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||||
|
|
||||||
|
#elif OS_TYPE == 1 /* uITRON */
|
||||||
|
#include "itron.h"
|
||||||
|
#include "kernel.h"
|
||||||
|
static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||||
|
|
||||||
|
#elif OS_TYPE == 2 /* uc/OS-II */
|
||||||
|
#include "includes.h"
|
||||||
|
static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */
|
||||||
|
|
||||||
|
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||||
|
#include "FreeRTOS.h"
|
||||||
|
#include "semphr.h"
|
||||||
|
static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */
|
||||||
|
|
||||||
|
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||||
|
#include "cmsis_os.h"
|
||||||
|
static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Create a Mutex */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* This function is called in f_mount function to create a new mutex
|
||||||
|
/ or semaphore for the volume. When a 0 is returned, the f_mount function
|
||||||
|
/ fails with FR_INT_ERR.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */
|
||||||
|
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#if OS_TYPE == 0 /* Win32 */
|
||||||
|
Mutex[vol] = CreateMutex(NULL, FALSE, NULL);
|
||||||
|
return (int)(Mutex[vol] != INVALID_HANDLE_VALUE);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 1 /* uITRON */
|
||||||
|
T_CMTX cmtx = {TA_TPRI,1};
|
||||||
|
|
||||||
|
Mutex[vol] = acre_mtx(&cmtx);
|
||||||
|
return (int)(Mutex[vol] > 0);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||||
|
OS_ERR err;
|
||||||
|
|
||||||
|
Mutex[vol] = OSMutexCreate(0, &err);
|
||||||
|
return (int)(err == OS_NO_ERR);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||||
|
Mutex[vol] = xSemaphoreCreateMutex();
|
||||||
|
return (int)(Mutex[vol] != NULL);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||||
|
osMutexDef(cmsis_os_mutex);
|
||||||
|
|
||||||
|
Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex));
|
||||||
|
return (int)(Mutex[vol] != NULL);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Delete a Mutex */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* This function is called in f_mount function to delete a mutex or
|
||||||
|
/ semaphore of the volume created with ff_mutex_create function.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */
|
||||||
|
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#if OS_TYPE == 0 /* Win32 */
|
||||||
|
CloseHandle(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 1 /* uITRON */
|
||||||
|
del_mtx(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||||
|
OS_ERR err;
|
||||||
|
|
||||||
|
OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||||
|
vSemaphoreDelete(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||||
|
osMutexDelete(Mutex[vol]);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Request a Grant to Access the Volume */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* This function is called on enter file functions to lock the volume.
|
||||||
|
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */
|
||||||
|
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#if OS_TYPE == 0 /* Win32 */
|
||||||
|
return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 1 /* uITRON */
|
||||||
|
return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||||
|
OS_ERR err;
|
||||||
|
|
||||||
|
OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err));
|
||||||
|
return (int)(err == OS_NO_ERR);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||||
|
return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||||
|
return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* Release a Grant to Access the Volume */
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
/* This function is called on leave file functions to unlock the volume.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ff_mutex_give (
|
||||||
|
int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#if OS_TYPE == 0 /* Win32 */
|
||||||
|
ReleaseMutex(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 1 /* uITRON */
|
||||||
|
unl_mtx(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 2 /* uC/OS-II */
|
||||||
|
OSMutexPost(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 3 /* FreeRTOS */
|
||||||
|
xSemaphoreGive(Mutex[vol]);
|
||||||
|
|
||||||
|
#elif OS_TYPE == 4 /* CMSIS-RTOS */
|
||||||
|
osMutexRelease(Mutex[vol]);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* FF_FS_REENTRANT */
|
15593
include/libusbhsfs/source/fatfs/ffunicode.c
Normal file
15593
include/libusbhsfs/source/fatfs/ffunicode.c
Normal file
File diff suppressed because it is too large
Load Diff
151
include/libusbhsfs/source/lwext4/ext.c
Normal file
151
include/libusbhsfs/source/lwext4/ext.c
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* ext.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ext.h"
|
||||||
|
|
||||||
|
#include "../usbhsfs_drive.h"
|
||||||
|
|
||||||
|
#define EXT2_FINCOM_SUPPORTED (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG)
|
||||||
|
#define EXT2_FINCOM_UNSUPPORTED ~EXT2_FINCOM_SUPPORTED
|
||||||
|
|
||||||
|
#define EXT2_FRO_SUPPORTED (EXT4_FRO_COM_SPARSE_SUPER | EXT4_FRO_COM_LARGE_FILE | EXT4_FRO_COM_BTREE_DIR)
|
||||||
|
#define EXT2_FRO_UNSUPPORTED ~EXT2_FRO_SUPPORTED
|
||||||
|
|
||||||
|
#define EXT3_FINCOM_SUPPORTED (EXT2_FINCOM_SUPPORTED | EXT4_FINCOM_RECOVER)
|
||||||
|
#define EXT3_FINCOM_UNSUPPORTED ~EXT3_FINCOM_SUPPORTED
|
||||||
|
|
||||||
|
#define EXT3_FRO_SUPPORTED EXT2_FRO_SUPPORTED
|
||||||
|
#define EXT3_FRO_UNSUPPORTED ~EXT3_FRO_SUPPORTED
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static void ext_get_version(ext_vd *vd);
|
||||||
|
|
||||||
|
bool ext_mount(ext_vd *vd)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = NULL;
|
||||||
|
char mount_point[CONFIG_EXT4_MAX_MP_NAME + 3] = {0};
|
||||||
|
struct ext4_sblock *sblock = NULL;
|
||||||
|
bool ret = false, bdev_reg = false, vol_mounted = false, read_only = false;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
if (!vd || !vd->bdev || !vd->bdev->bdif || !vd->bdev->bdif->ph_bbuf || !(lun_ctx = (UsbHsFsDriveLogicalUnitContext*)vd->bdev->bdif->p_user) || !vd->dev_name[0])
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update read only flag. */
|
||||||
|
read_only = ((vd->flags & UsbHsFsMountFlags_ReadOnly) || lun_ctx->write_protect);
|
||||||
|
|
||||||
|
/* Register EXT block device. */
|
||||||
|
res = ext4_device_register(vd->bdev, vd->dev_name);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to register EXT block device \"%s\"! (%d).", vd->dev_name, res);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bdev_reg = true;
|
||||||
|
|
||||||
|
/* Generate mount point name. */
|
||||||
|
sprintf(mount_point, "/%s/", vd->dev_name);
|
||||||
|
|
||||||
|
/* Mount EXT volume. */
|
||||||
|
res = ext4_mount(vd->dev_name, mount_point, read_only);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to mount EXT volume \"%s\"! (%d).", mount_point, res);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
vol_mounted = true;
|
||||||
|
|
||||||
|
/* Update EXT superblock pointer. */
|
||||||
|
sblock = &(vd->bdev->fs->sb);
|
||||||
|
|
||||||
|
/* Perform EXT journal operations if needed. */
|
||||||
|
if (!read_only && ext4_sb_feature_com(sblock, EXT4_FCOM_HAS_JOURNAL))
|
||||||
|
{
|
||||||
|
/* Replay EXT journal depending on the mount flags. */
|
||||||
|
if ((vd->flags & UsbHsFsMountFlags_ReplayJournal) && (res = ext4_recover(mount_point)))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to replay EXT journal from volume \"%s\"! (%d).", mount_point, res);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start EXT journaling. */
|
||||||
|
res = ext4_journal_start(mount_point);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to start journaling on EXT volume \"%s\"! (%d).", mount_point, res);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get EXT version. */
|
||||||
|
ext_get_version(vd);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
if (vol_mounted) ext4_umount(mount_point);
|
||||||
|
|
||||||
|
if (bdev_reg) ext4_device_unregister(vd->dev_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ext_umount(ext_vd *vd)
|
||||||
|
{
|
||||||
|
if (!vd || !vd->bdev || !vd->bdev->bdif || !vd->bdev->bdif->ph_bbuf || !vd->dev_name[0]) return;
|
||||||
|
|
||||||
|
char mount_point[CONFIG_EXT4_MAX_MP_NAME + 3] = {0};
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
/* Generate mount point name. */
|
||||||
|
sprintf(mount_point, "/%s/", vd->dev_name);
|
||||||
|
|
||||||
|
/* Stop EXT journaling. */
|
||||||
|
res = ext4_journal_stop(mount_point);
|
||||||
|
if (res) USBHSFS_LOG_MSG("Failed to stop EXT journaling for volume \"%s\"! (%d).", mount_point, res);
|
||||||
|
|
||||||
|
/* Unmount EXT volume. */
|
||||||
|
res = ext4_umount(mount_point);
|
||||||
|
if (res) USBHSFS_LOG_MSG("Failed to unmount EXT volume \"%s\"! (%d).", mount_point, res);
|
||||||
|
|
||||||
|
/* Unregister EXT block device. */
|
||||||
|
/* Do not check for errors in this call - it always returns ENOENT. */
|
||||||
|
res = ext4_device_unregister(vd->dev_name);
|
||||||
|
//if (res) USBHSFS_LOG_MSG("Failed to unregister EXT block device \"%s\"! (%d).", vd->dev_name, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ext_get_version(ext_vd *vd)
|
||||||
|
{
|
||||||
|
u32 fincom = 0, fro = 0;
|
||||||
|
struct ext4_sblock *sblock = &(vd->bdev->fs->sb);
|
||||||
|
|
||||||
|
/* Get features_incompatible. */
|
||||||
|
fincom = ext4_get32(sblock, features_incompatible);
|
||||||
|
|
||||||
|
/* Get features_read_only. */
|
||||||
|
fro = ext4_get32(sblock, features_read_only);
|
||||||
|
|
||||||
|
/* Check EXT4 features. */
|
||||||
|
if ((fincom & EXT3_FINCOM_UNSUPPORTED) || (fro & EXT3_FRO_UNSUPPORTED)) vd->version = UsbHsFsDeviceFileSystemType_EXT4;
|
||||||
|
|
||||||
|
/* Check EXT3 features. */
|
||||||
|
if (!(fincom & EXT3_FINCOM_UNSUPPORTED) && !(fro & EXT3_FRO_UNSUPPORTED)) vd->version = UsbHsFsDeviceFileSystemType_EXT3;
|
||||||
|
|
||||||
|
/* Check EXT2 features. */
|
||||||
|
if (!(fincom & EXT2_FINCOM_UNSUPPORTED) && !(fro & EXT2_FRO_UNSUPPORTED)) vd->version = UsbHsFsDeviceFileSystemType_EXT2;
|
||||||
|
}
|
44
include/libusbhsfs/source/lwext4/ext.h
Normal file
44
include/libusbhsfs/source/lwext4/ext.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* ext.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __EXT_H__
|
||||||
|
#define __EXT_H__
|
||||||
|
|
||||||
|
#include <lwext4/ext4.h>
|
||||||
|
#include <lwext4/ext4_super.h>
|
||||||
|
#include <lwext4/ext4_debug.h>
|
||||||
|
#include <lwext4/ext4_fs.h>
|
||||||
|
#include <lwext4/ext4_inode.h>
|
||||||
|
#include <lwext4/ext4_journal.h>
|
||||||
|
|
||||||
|
#include "../usbhsfs_utils.h"
|
||||||
|
|
||||||
|
#include "ext_disk_io.h"
|
||||||
|
|
||||||
|
/// EXT volume descriptor.
|
||||||
|
typedef struct _ext_vd {
|
||||||
|
struct ext4_blockdev *bdev; ///< EXT block device handle.
|
||||||
|
char dev_name[CONFIG_EXT4_MAX_MP_NAME]; ///< Block device mount name.
|
||||||
|
u32 flags; ///< EXT mount flags.
|
||||||
|
s64 id; ///< Filesystem ID.
|
||||||
|
u16 uid; ///< User ID for entry creation.
|
||||||
|
u16 gid; ///< Group ID for entry creation.
|
||||||
|
u16 fmask; ///< Unix style permission mask for file creation.
|
||||||
|
u16 dmask; ///< Unix style permission mask for directory creation.
|
||||||
|
u8 version; ///< UsbHsFsDeviceFileSystemType_EXT* value to identify the EXT version.
|
||||||
|
} ext_vd;
|
||||||
|
|
||||||
|
/// Mounts an EXT volume using the provided volume descriptor.
|
||||||
|
bool ext_mount(ext_vd *vd);
|
||||||
|
|
||||||
|
/// Unmounts the EXT volume represented by the provided volume descriptor.
|
||||||
|
void ext_umount(ext_vd *vd);
|
||||||
|
|
||||||
|
#endif /* __EXT_H__ */
|
910
include/libusbhsfs/source/lwext4/ext_dev.c
Normal file
910
include/libusbhsfs/source/lwext4/ext_dev.c
Normal file
@ -0,0 +1,910 @@
|
|||||||
|
/*
|
||||||
|
* ext_dev.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Loosely based on fs_dev.c from libnx, et al.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "../usbhsfs_manager.h"
|
||||||
|
#include "../usbhsfs_mount.h"
|
||||||
|
|
||||||
|
/* Helper macros. */
|
||||||
|
|
||||||
|
#define ext_end goto end
|
||||||
|
#define ext_ended_with_error (_errno != 0)
|
||||||
|
#define ext_set_error(x) r->_errno = _errno = (x)
|
||||||
|
#define ext_set_error_and_exit(x) \
|
||||||
|
do { \
|
||||||
|
ext_set_error((x)); \
|
||||||
|
ext_end; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ext_declare_error_state int _errno = 0
|
||||||
|
#define ext_declare_file_state ext4_file *file = (ext4_file*)fd
|
||||||
|
#define ext_declare_dir_state ext4_dir *dir = (ext4_dir*)dirState->dirStruct
|
||||||
|
#define ext_declare_fs_ctx UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = (UsbHsFsDriveLogicalUnitFileSystemContext*)r->deviceData
|
||||||
|
#define ext_declare_lun_ctx UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx
|
||||||
|
#define ext_declare_drive_ctx UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx
|
||||||
|
#define ext_declare_vol_state ext_vd *vd = fs_ctx->ext
|
||||||
|
|
||||||
|
#define ext_lock_drive_ctx ext_declare_fs_ctx; \
|
||||||
|
ext_declare_lun_ctx; \
|
||||||
|
ext_declare_drive_ctx; \
|
||||||
|
bool drive_ctx_valid = usbHsFsManagerIsDriveContextPointerValid(drive_ctx); \
|
||||||
|
if (!drive_ctx_valid) ext_set_error_and_exit(ENODEV)
|
||||||
|
|
||||||
|
#define ext_unlock_drive_ctx if (drive_ctx_valid) mutexUnlock(&(drive_ctx->mutex))
|
||||||
|
|
||||||
|
#define ext_return(x) return (ext_ended_with_error ? -1 : (x))
|
||||||
|
#define ext_return_ptr(x) return (ext_ended_with_error ? NULL : (x))
|
||||||
|
#define ext_return_bool return (ext_ended_with_error ? false : true)
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int extdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode);
|
||||||
|
static int extdev_close(struct _reent *r, void *fd);
|
||||||
|
static ssize_t extdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
||||||
|
static ssize_t extdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
|
||||||
|
static off_t extdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
|
||||||
|
static int extdev_fstat(struct _reent *r, void *fd, struct stat *st);
|
||||||
|
static int extdev_stat(struct _reent *r, const char *file, struct stat *st);
|
||||||
|
static int extdev_link(struct _reent *r, const char *existing, const char *newLink);
|
||||||
|
static int extdev_unlink(struct _reent *r, const char *name);
|
||||||
|
static int extdev_chdir(struct _reent *r, const char *name);
|
||||||
|
static int extdev_rename(struct _reent *r, const char *oldName, const char *newName);
|
||||||
|
static int extdev_mkdir(struct _reent *r, const char *path, int mode);
|
||||||
|
static DIR_ITER* extdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
|
||||||
|
static int extdev_dirreset(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int extdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
|
||||||
|
static int extdev_dirclose(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int extdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
|
||||||
|
static int extdev_ftruncate(struct _reent *r, void *fd, off_t len);
|
||||||
|
static int extdev_fsync(struct _reent *r, void *fd);
|
||||||
|
static int extdev_chmod(struct _reent *r, const char *path, mode_t mode);
|
||||||
|
static int extdev_fchmod(struct _reent *r, void *fd, mode_t mode);
|
||||||
|
static int extdev_rmdir(struct _reent *r, const char *name);
|
||||||
|
static int extdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]);
|
||||||
|
|
||||||
|
static bool extdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath);
|
||||||
|
|
||||||
|
static void extdev_fill_stat(const struct ext4_inode *inode, u32 st_dev, u32 st_ino, u32 st_blksize, struct stat *st);
|
||||||
|
|
||||||
|
static int ext_trans_start(struct ext4_fs *ext_fs);
|
||||||
|
static int ext_trans_stop(struct ext4_fs *ext_fs);
|
||||||
|
static void ext_trans_abort(struct ext4_fs *ext_fs);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static const devoptab_t extdev_devoptab = {
|
||||||
|
.name = NULL,
|
||||||
|
.structSize = sizeof(ext4_file),
|
||||||
|
.open_r = extdev_open,
|
||||||
|
.close_r = extdev_close,
|
||||||
|
.write_r = extdev_write,
|
||||||
|
.read_r = extdev_read,
|
||||||
|
.seek_r = extdev_seek,
|
||||||
|
.fstat_r = extdev_fstat,
|
||||||
|
.stat_r = extdev_stat,
|
||||||
|
.link_r = extdev_link,
|
||||||
|
.unlink_r = extdev_unlink,
|
||||||
|
.chdir_r = extdev_chdir,
|
||||||
|
.rename_r = extdev_rename,
|
||||||
|
.mkdir_r = extdev_mkdir,
|
||||||
|
.dirStateSize = sizeof(ext4_dir),
|
||||||
|
.diropen_r = extdev_diropen,
|
||||||
|
.dirreset_r = extdev_dirreset,
|
||||||
|
.dirnext_r = extdev_dirnext,
|
||||||
|
.dirclose_r = extdev_dirclose,
|
||||||
|
.statvfs_r = extdev_statvfs,
|
||||||
|
.ftruncate_r = extdev_ftruncate,
|
||||||
|
.fsync_r = extdev_fsync, ///< Not supported by lwext4.
|
||||||
|
.deviceData = NULL,
|
||||||
|
.chmod_r = extdev_chmod,
|
||||||
|
.fchmod_r = extdev_fchmod,
|
||||||
|
.rmdir_r = extdev_rmdir,
|
||||||
|
.lstat_r = extdev_stat, ///< We'll just alias lstat() to stat().
|
||||||
|
.utimes_r = extdev_utimes
|
||||||
|
};
|
||||||
|
|
||||||
|
const devoptab_t *extdev_get_devoptab()
|
||||||
|
{
|
||||||
|
return &extdev_devoptab;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, path, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Opening file \"%s\" (\"%s\") with flags 0x%X.", path, __usbhsfs_dev_path_buf, flags);
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(ext4_file));
|
||||||
|
|
||||||
|
/* Open file. */
|
||||||
|
ret = ext4_fopen2(file, __usbhsfs_dev_path_buf, flags);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_close(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Closing file %u.", file->inode);
|
||||||
|
|
||||||
|
/* Close file. */
|
||||||
|
ret = ext4_fclose(file);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Reset file descriptor. */
|
||||||
|
memset(file, 0, sizeof(ext4_file));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t extdev_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
size_t bw = 0;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the append flag is enabled. */
|
||||||
|
if ((file->flags & O_APPEND) && ext4_ftell(file) != ext4_fsize(file))
|
||||||
|
{
|
||||||
|
/* Seek to EOF. */
|
||||||
|
ret = ext4_fseek(file, 0, SEEK_END);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Writing 0x%lX byte(s) to file %u at offset 0x%lX.", len, file->inode, ext4_ftell(file));
|
||||||
|
|
||||||
|
/* Write file data. */
|
||||||
|
ret = ext4_fwrite(file, ptr, len, &bw);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return((ssize_t)bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t extdev_read(struct _reent *r, void *fd, char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
size_t br = 0;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Reading 0x%lX byte(s) from file %u at offset 0x%lX.", len, file->inode, ext4_ftell(file));
|
||||||
|
|
||||||
|
/* Read file data. */
|
||||||
|
ret = ext4_fread(file, ptr, len, &br);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return((ssize_t)br);
|
||||||
|
}
|
||||||
|
|
||||||
|
static off_t extdev_seek(struct _reent *r, void *fd, off_t pos, int dir)
|
||||||
|
{
|
||||||
|
off_t offset = 0;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Seeking 0x%lX byte(s) from current position in file %u.", pos, file->inode);
|
||||||
|
|
||||||
|
/* Perform file seek. */
|
||||||
|
ret = ext4_fseek(file, (s64)pos, (u32)dir);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Update current offset. */
|
||||||
|
offset = (off_t)ext4_ftell(file);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_fstat(struct _reent *r, void *fd, struct stat *st)
|
||||||
|
{
|
||||||
|
struct ext4_inode_ref inode_ref = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
ext_declare_vol_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !st) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Get inode reference. */
|
||||||
|
ret = ext4_fs_get_inode_ref(vd->bdev->fs, file->inode, &inode_ref);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
extdev_fill_stat(inode_ref.inode, fs_ctx->device_id, file->inode, vd->bdev->lg_bsize, st);
|
||||||
|
|
||||||
|
/* Put back inode reference. */
|
||||||
|
ret = ext4_fs_put_inode_ref(&inode_ref);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
|
{
|
||||||
|
u32 inode_num = 0;
|
||||||
|
struct ext4_inode inode = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
ext_declare_vol_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!st) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, file, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting stats for \"%s\" (\"%s\").", file, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Get inode. */
|
||||||
|
ret = ext4_raw_inode_fill(__usbhsfs_dev_path_buf, &inode_num, &inode);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
extdev_fill_stat(&inode, fs_ctx->device_id, inode_num, vd->bdev->lg_bsize, st);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_link(struct _reent *r, const char *existing, const char *newLink)
|
||||||
|
{
|
||||||
|
char existing_path[MAX_PATH_LENGTH] = {0};
|
||||||
|
char *new_path = __usbhsfs_dev_path_buf;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input paths. */
|
||||||
|
if (!extdev_fixpath(r, existing, &fs_ctx, existing_path) || !extdev_fixpath(r, newLink, &fs_ctx, new_path)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Linking \"%s\" (\"%s\") to \"%s\" (\"%s\").", existing, existing_path, newLink, new_path);
|
||||||
|
|
||||||
|
/* Create hard link. */
|
||||||
|
ret = ext4_flink(existing_path, new_path);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_unlink(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, name, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Deleting \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Delete file. */
|
||||||
|
ret = ext4_fremove(__usbhsfs_dev_path_buf);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_chdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
ext4_dir dir = {0};
|
||||||
|
size_t cwd_len = 0;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, name, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Changing current directory to \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
ret = ext4_dir_open(&dir, __usbhsfs_dev_path_buf);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
ext4_dir_close(&dir);
|
||||||
|
|
||||||
|
/* Update current working directory. */
|
||||||
|
sprintf(fs_ctx->cwd, "%s", strchr(__usbhsfs_dev_path_buf + 1, '/'));
|
||||||
|
|
||||||
|
cwd_len = strlen(fs_ctx->cwd);
|
||||||
|
if (fs_ctx->cwd[cwd_len - 1] != '/')
|
||||||
|
{
|
||||||
|
fs_ctx->cwd[cwd_len] = '/';
|
||||||
|
fs_ctx->cwd[cwd_len + 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set default devoptab device. */
|
||||||
|
usbHsFsMountSetDefaultDevoptabDevice(fs_ctx);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_rename(struct _reent *r, const char *oldName, const char *newName)
|
||||||
|
{
|
||||||
|
char old_path[MAX_PATH_LENGTH] = {0};
|
||||||
|
char *new_path = __usbhsfs_dev_path_buf;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input paths. */
|
||||||
|
if (!extdev_fixpath(r, oldName, &fs_ctx, old_path) || !extdev_fixpath(r, newName, &fs_ctx, new_path)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Renaming \"%s\" (\"%s\") to \"%s\" (\"%s\").", oldName, old_path, newName, new_path);
|
||||||
|
|
||||||
|
/* Rename entry. */
|
||||||
|
ret = ext4_frename(old_path, new_path);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_mkdir(struct _reent *r, const char *path, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, path, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Creating directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Create directory. */
|
||||||
|
ret = ext4_dir_mk(__usbhsfs_dev_path_buf);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DIR_ITER *extdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path)
|
||||||
|
{
|
||||||
|
int res = -1;
|
||||||
|
DIR_ITER *ret = NULL;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ext_declare_dir_state;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, path, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Opening directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(ext4_dir));
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
res = ext4_dir_open(dir, __usbhsfs_dev_path_buf);
|
||||||
|
if (res) ext_set_error_and_exit(res);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = dirState;
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return_ptr(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_dirreset(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ext_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Resetting state from directory %u.", dir->f.inode);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
ext4_dir_entry_rewind(dir);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat)
|
||||||
|
{
|
||||||
|
const ext4_direntry *entry = NULL;
|
||||||
|
struct ext4_inode_ref inode_ref = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
ext_declare_vol_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState || !filename || !filestat) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ext_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting info from next entry in directory %u.", dir->f.inode);
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
/* Read directory. */
|
||||||
|
entry = ext4_dir_entry_next(dir);
|
||||||
|
if (!entry) break;
|
||||||
|
|
||||||
|
/* Filter entry types. */
|
||||||
|
if (entry->inode_type != EXT4_DE_REG_FILE && entry->inode_type != EXT4_DE_DIR && entry->inode_type != EXT4_DE_SYMLINK) continue;
|
||||||
|
|
||||||
|
/* Filter dot directory entries. */
|
||||||
|
if (entry->inode_type == EXT4_DE_DIR && (!strcmp((char*)entry->name, ".") || !strcmp((char*)entry->name, ".."))) continue;
|
||||||
|
|
||||||
|
/* Jackpot. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry) ext_set_error_and_exit(ENOENT); /* ENOENT signals EOD. */
|
||||||
|
|
||||||
|
/* Get inode reference. */
|
||||||
|
ret = ext4_fs_get_inode_ref(vd->bdev->fs, entry->inode, &inode_ref);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
extdev_fill_stat(inode_ref.inode, fs_ctx->device_id, entry->inode, vd->bdev->lg_bsize, filestat);
|
||||||
|
|
||||||
|
/* Put back inode reference. */
|
||||||
|
ret = ext4_fs_put_inode_ref(&inode_ref);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Copy filename. */
|
||||||
|
strcpy(filename, (char*)entry->name);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_dirclose(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
ext_declare_dir_state;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Closing directory %u.", dir->f.inode);
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
ret = ext4_dir_close(dir);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(ext4_dir));
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf)
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
|
||||||
|
char mount_point[CONFIG_EXT4_MAX_MP_NAME + 3] = {0};
|
||||||
|
struct ext4_mount_stats mount_stats = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
ext_declare_vol_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!buf) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Generate lwext4 mount point. */
|
||||||
|
sprintf(mount_point, "/%s/", vd->dev_name);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Getting filesystem stats for \"%s\" (\"%s\").", path, mount_point);
|
||||||
|
|
||||||
|
/* Get volume information. */
|
||||||
|
ret = ext4_mount_point_stats(mount_point, &mount_stats);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Fill filesystem stats. */
|
||||||
|
memset(buf, 0, sizeof(struct statvfs));
|
||||||
|
|
||||||
|
buf->f_bsize = mount_stats.block_size;
|
||||||
|
buf->f_frsize = mount_stats.block_size;
|
||||||
|
buf->f_blocks = mount_stats.blocks_count;
|
||||||
|
buf->f_bfree = mount_stats.free_blocks_count;
|
||||||
|
buf->f_bavail = mount_stats.free_blocks_count;
|
||||||
|
buf->f_files = mount_stats.inodes_count;
|
||||||
|
buf->f_ffree = mount_stats.free_inodes_count;
|
||||||
|
buf->f_favail = mount_stats.free_inodes_count;
|
||||||
|
buf->f_fsid = fs_ctx->device_id;
|
||||||
|
buf->f_flag = ST_NOSUID;
|
||||||
|
buf->f_namemax = EXT4_DIRECTORY_FILENAME_LEN;
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_ftruncate(struct _reent *r, void *fd, off_t len)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || len < 0) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Truncating file %u to 0x%lX bytes.", file->inode, len);
|
||||||
|
|
||||||
|
/* Truncate file. */
|
||||||
|
ret = ext4_ftruncate(file, (u64)len);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_fsync(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
|
||||||
|
/* Not supported by lwext4. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_chmod(struct _reent *r, const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, path, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Changing permissions for \"%s\" (\"%s\") to %o.", path, __usbhsfs_dev_path_buf, mode);
|
||||||
|
|
||||||
|
/* Change permissions. */
|
||||||
|
ret = ext4_mode_set(__usbhsfs_dev_path_buf, (u32)mode);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_fchmod(struct _reent *r, void *fd, mode_t mode)
|
||||||
|
{
|
||||||
|
struct ext4_fs *ext_fs = NULL;
|
||||||
|
struct ext4_sblock *sblock = NULL;
|
||||||
|
struct ext4_inode_ref inode_ref = {0};
|
||||||
|
u32 orig_mode = 0;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_declare_file_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
ext_declare_vol_state;
|
||||||
|
|
||||||
|
ext_fs = vd->bdev->fs;
|
||||||
|
sblock = &(vd->bdev->fs->sb);
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Start journal transfer. */
|
||||||
|
ret = ext_trans_start(ext_fs);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Get inode reference. */
|
||||||
|
ret = ext4_fs_get_inode_ref(ext_fs, file->inode, &inode_ref);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ext_trans_abort(ext_fs);
|
||||||
|
ext_set_error_and_exit(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Changing permissions for file %u to %o.", file->inode, mode);
|
||||||
|
|
||||||
|
/* Change permissions. */
|
||||||
|
orig_mode = ext4_inode_get_mode(sblock, inode_ref.inode);
|
||||||
|
orig_mode &= ~0xFFF;
|
||||||
|
orig_mode |= ((u32)mode & 0xFFF);
|
||||||
|
ext4_inode_set_mode(sblock, inode_ref.inode, orig_mode);
|
||||||
|
inode_ref.dirty = true;
|
||||||
|
|
||||||
|
/* Put back inode reference. */
|
||||||
|
ret = ext4_fs_put_inode_ref(&inode_ref);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
ext_trans_abort(ext_fs);
|
||||||
|
ext_set_error_and_exit(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop journal transfer. */
|
||||||
|
ret = ext_trans_stop(ext_fs);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_rmdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
/* Exactly the same as extdev_unlink(). */
|
||||||
|
return extdev_unlink(r, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
struct timespec ts_times[2] = {0};
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
ext_lock_drive_ctx;
|
||||||
|
|
||||||
|
/* Fix input path. */
|
||||||
|
if (!extdev_fixpath(r, filename, &fs_ctx, NULL)) ext_end;
|
||||||
|
|
||||||
|
/* Check if we should use the current time. */
|
||||||
|
if (!times)
|
||||||
|
{
|
||||||
|
/* Get current time. */
|
||||||
|
clock_gettime(CLOCK_REALTIME, &(ts_times[0]));
|
||||||
|
memcpy(&(ts_times[1]), &(ts_times[0]), sizeof(struct timespec));
|
||||||
|
} else {
|
||||||
|
/* Convert provided timeval values to timespec values. */
|
||||||
|
TIMEVAL_TO_TIMESPEC(&(times[0]), &(ts_times[0]));
|
||||||
|
TIMEVAL_TO_TIMESPEC(&(times[1]), &(ts_times[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Setting last access and modification times for \"%s\" (\"%s\") to 0x%lX and 0x%lX, respectively.", filename, __usbhsfs_dev_path_buf, ts_times[0].tv_sec, ts_times[1].tv_sec);
|
||||||
|
|
||||||
|
/* Set access time. */
|
||||||
|
ret = ext4_atime_set(__usbhsfs_dev_path_buf, (u32)ts_times[0].tv_sec);
|
||||||
|
if (ret) ext_set_error_and_exit(ret);
|
||||||
|
|
||||||
|
/* Set modification time. */
|
||||||
|
ret = ext4_mtime_set(__usbhsfs_dev_path_buf, (u32)ts_times[1].tv_sec);
|
||||||
|
if (ret) ext_set_error(ret);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_unlock_drive_ctx;
|
||||||
|
ext_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath)
|
||||||
|
{
|
||||||
|
ext_vd *vd = NULL;
|
||||||
|
const u8 *p = (const u8*)path;
|
||||||
|
ssize_t units = 0;
|
||||||
|
u32 code = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
char mount_point[CONFIG_EXT4_MAX_MP_NAME + 3] = {0}, *outptr = (outpath ? outpath : __usbhsfs_dev_path_buf), *cwd = NULL;
|
||||||
|
|
||||||
|
ext_declare_error_state;
|
||||||
|
|
||||||
|
if (!r || !path || !*path || !fs_ctx || !*fs_ctx || !(vd = (*fs_ctx)->ext) || !(cwd = (*fs_ctx)->cwd)) ext_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Input path: \"%s\".", path);
|
||||||
|
|
||||||
|
/* Generate lwext4 mount point. */
|
||||||
|
sprintf(mount_point, "/%s", vd->dev_name);
|
||||||
|
|
||||||
|
/* Move the path pointer to the start of the actual path. */
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) ext_set_error_and_exit(EILSEQ);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ' && code != ':');
|
||||||
|
|
||||||
|
/* We found a colon; p points to the actual path. */
|
||||||
|
if (code == ':') path = (const char*)p;
|
||||||
|
|
||||||
|
/* Make sure there are no more colons and that the remainder of the string is valid UTF-8. */
|
||||||
|
p = (const u8*)path;
|
||||||
|
|
||||||
|
do {
|
||||||
|
units = decode_utf8(&code, p);
|
||||||
|
if (units < 0) ext_set_error_and_exit(EILSEQ);
|
||||||
|
if (code == ':') ext_set_error_and_exit(EINVAL);
|
||||||
|
p += units;
|
||||||
|
} while(code >= ' ');
|
||||||
|
|
||||||
|
/* Verify fixed path length. */
|
||||||
|
len = (strlen(mount_point) + strlen(path));
|
||||||
|
if (path[0] != '/') len += strlen(cwd);
|
||||||
|
|
||||||
|
if (len >= MAX_PATH_LENGTH) ext_set_error_and_exit(ENAMETOOLONG);
|
||||||
|
|
||||||
|
/* Generate fixed path. */
|
||||||
|
if (path[0] == '/')
|
||||||
|
{
|
||||||
|
sprintf(outptr, "%s%s", mount_point, path);
|
||||||
|
} else {
|
||||||
|
sprintf(outptr, "%s%s%s", mount_point, cwd, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Fixed path: \"%s\".", outptr);
|
||||||
|
|
||||||
|
end:
|
||||||
|
ext_return_bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extdev_fill_stat(const struct ext4_inode *inode, u32 st_dev, u32 st_ino, u32 st_blksize, struct stat *st)
|
||||||
|
{
|
||||||
|
/* Clear stat struct. */
|
||||||
|
memset(st, 0, sizeof(struct stat));
|
||||||
|
|
||||||
|
/* Fill stat struct. */
|
||||||
|
st->st_dev = st_dev;
|
||||||
|
st->st_ino = st_ino;
|
||||||
|
st->st_mode = inode->mode;
|
||||||
|
st->st_nlink = inode->links_count;
|
||||||
|
st->st_uid = inode->uid;
|
||||||
|
st->st_gid = inode->gid;
|
||||||
|
if (inode->mode & (EXT4_INODE_MODE_FILE | EXT4_INODE_MODE_SOFTLINK)) st->st_size = ((((u64)inode->size_hi << 0x20) & 0xFFFFFFFF00000000ULL) | (u64)inode->size_lo);
|
||||||
|
st->st_blksize = st_blksize;
|
||||||
|
st->st_blocks = inode->blocks_count_lo;
|
||||||
|
|
||||||
|
st->st_atim.tv_sec = inode->access_time;
|
||||||
|
st->st_atim.tv_nsec = inode->atime_extra;
|
||||||
|
|
||||||
|
st->st_mtim.tv_sec = inode->modification_time;
|
||||||
|
st->st_mtim.tv_nsec = inode->mtime_extra;
|
||||||
|
|
||||||
|
st->st_ctim.tv_sec = inode->crtime;
|
||||||
|
st->st_ctim.tv_nsec = inode->crtime_extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_trans_start(struct ext4_fs *ext_fs)
|
||||||
|
{
|
||||||
|
struct jbd_journal *journal = NULL;
|
||||||
|
struct jbd_trans *trans = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (ext_fs->jbd_journal && !ext_fs->curr_trans)
|
||||||
|
{
|
||||||
|
journal = ext_fs->jbd_journal;
|
||||||
|
|
||||||
|
trans = jbd_journal_new_trans(journal);
|
||||||
|
if (!trans)
|
||||||
|
{
|
||||||
|
ret = ENOMEM;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext_fs->curr_trans = trans;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_trans_stop(struct ext4_fs *ext_fs)
|
||||||
|
{
|
||||||
|
struct jbd_journal *journal = NULL;
|
||||||
|
struct jbd_trans *trans = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (ext_fs->jbd_journal && ext_fs->curr_trans)
|
||||||
|
{
|
||||||
|
journal = ext_fs->jbd_journal;
|
||||||
|
trans = ext_fs->curr_trans;
|
||||||
|
ret = jbd_journal_commit_trans(journal, trans);
|
||||||
|
ext_fs->curr_trans = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ext_trans_abort(struct ext4_fs *ext_fs)
|
||||||
|
{
|
||||||
|
struct jbd_journal *journal = NULL;
|
||||||
|
struct jbd_trans *trans = NULL;
|
||||||
|
|
||||||
|
if (ext_fs->jbd_journal && ext_fs->curr_trans)
|
||||||
|
{
|
||||||
|
journal = ext_fs->jbd_journal;
|
||||||
|
trans = ext_fs->curr_trans;
|
||||||
|
jbd_journal_free_trans(journal, trans, true);
|
||||||
|
ext_fs->curr_trans = NULL;
|
||||||
|
}
|
||||||
|
}
|
16
include/libusbhsfs/source/lwext4/ext_dev.h
Normal file
16
include/libusbhsfs/source/lwext4/ext_dev.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* ext_dev.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __EXT_DEV_H__
|
||||||
|
#define __EXT_DEV_H__
|
||||||
|
|
||||||
|
const devoptab_t *extdev_get_devoptab();
|
||||||
|
|
||||||
|
#endif /* __EXT_DEV_H__ */
|
152
include/libusbhsfs/source/lwext4/ext_disk_io.c
Normal file
152
include/libusbhsfs/source/lwext4/ext_disk_io.c
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* ext_disk_io.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ext.h"
|
||||||
|
|
||||||
|
#include "../usbhsfs_scsi.h"
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int ext_blockdev_open(struct ext4_blockdev *bdev);
|
||||||
|
static int ext_blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, uint32_t blk_cnt);
|
||||||
|
static int ext_blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, uint64_t blk_id, uint32_t blk_cnt);
|
||||||
|
static int ext_blockdev_close(struct ext4_blockdev *bdev);
|
||||||
|
static int ext_blockdev_lock(struct ext4_blockdev *bdev);
|
||||||
|
static int ext_blockdev_unlock(struct ext4_blockdev *bdev);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static const struct ext4_blockdev_iface ext_blockdev_usbhsfs_iface = {
|
||||||
|
.open = ext_blockdev_open,
|
||||||
|
.bread = ext_blockdev_bread,
|
||||||
|
.bwrite = ext_blockdev_bwrite,
|
||||||
|
.close = ext_blockdev_close,
|
||||||
|
.lock = ext_blockdev_lock,
|
||||||
|
.unlock = ext_blockdev_unlock,
|
||||||
|
.ph_bsize = 0,
|
||||||
|
.ph_bcnt = 0,
|
||||||
|
.ph_bbuf = NULL,
|
||||||
|
.ph_refctr = 0,
|
||||||
|
.bread_ctr = 0,
|
||||||
|
.bwrite_ctr = 0,
|
||||||
|
.p_user = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ext4_blockdev *ext_disk_io_alloc_blockdev(void *p_user, u64 part_lba, u64 part_size)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)p_user;
|
||||||
|
struct ext4_blockdev *bdev = NULL;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
/* Allocate memory for ext4_blockdev object. */
|
||||||
|
bdev = calloc(1, sizeof(struct ext4_blockdev));
|
||||||
|
if (!bdev)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory for ext4_blockdev object!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for ext4_blockdev_iface object. */
|
||||||
|
bdev->bdif = calloc(1, sizeof(struct ext4_blockdev_iface));
|
||||||
|
if (!bdev->bdif)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory for ext4_blockdev_iface object!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy ext4_blockdev_iface object data. */
|
||||||
|
memcpy(bdev->bdif, &ext_blockdev_usbhsfs_iface, sizeof(struct ext4_blockdev_iface));
|
||||||
|
|
||||||
|
/* Allocate memory for block size buffer. */
|
||||||
|
bdev->bdif->ph_bbuf = calloc(1, lun_ctx->block_length);
|
||||||
|
if (!bdev->bdif->ph_bbuf)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%X bytes for block size buffer!", lun_ctx->block_length);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill ext4_blockdev object. */
|
||||||
|
bdev->part_offset = (part_lba * (u64)lun_ctx->block_length);
|
||||||
|
bdev->part_size = (part_size * (u64)lun_ctx->block_length);
|
||||||
|
|
||||||
|
/* Fill ext4_blockdev_iface object. */
|
||||||
|
bdev->bdif->ph_bsize = lun_ctx->block_length;
|
||||||
|
bdev->bdif->ph_bcnt = part_size;
|
||||||
|
bdev->bdif->p_user = lun_ctx;
|
||||||
|
|
||||||
|
/* Update flag. */
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!success && bdev)
|
||||||
|
{
|
||||||
|
ext_disk_io_free_blockdev(bdev);
|
||||||
|
bdev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ext_disk_io_free_blockdev(struct ext4_blockdev *bdev)
|
||||||
|
{
|
||||||
|
if (!bdev) return;
|
||||||
|
|
||||||
|
if (bdev->bdif)
|
||||||
|
{
|
||||||
|
if (bdev->bdif->ph_bbuf) free(bdev->bdif->ph_bbuf);
|
||||||
|
free(bdev->bdif);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(bdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_open(struct ext4_blockdev *bdev)
|
||||||
|
{
|
||||||
|
(void)bdev;
|
||||||
|
|
||||||
|
/* Low level block device initialization is handled by us. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, uint32_t blk_cnt)
|
||||||
|
{
|
||||||
|
/* Get LUN context and read sectors. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)bdev->bdif->p_user;
|
||||||
|
return (usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, buf, blk_id, blk_cnt) ? 0 : EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, uint64_t blk_id, uint32_t blk_cnt)
|
||||||
|
{
|
||||||
|
/* Get LUN context and write sectors. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)bdev->bdif->p_user;
|
||||||
|
return (usbHsFsScsiWriteLogicalUnitBlocks(lun_ctx, buf, blk_id, blk_cnt) ? 0 : EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_close(struct ext4_blockdev *bdev)
|
||||||
|
{
|
||||||
|
(void)bdev;
|
||||||
|
|
||||||
|
/* Low level block device deinitialization is handled by us. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_lock(struct ext4_blockdev *bdev)
|
||||||
|
{
|
||||||
|
(void)bdev;
|
||||||
|
|
||||||
|
/* Mutex locking is handled by us. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ext_blockdev_unlock(struct ext4_blockdev *bdev)
|
||||||
|
{
|
||||||
|
(void)bdev;
|
||||||
|
|
||||||
|
/* Mutex unlocking is handled by us. */
|
||||||
|
return 0;
|
||||||
|
}
|
20
include/libusbhsfs/source/lwext4/ext_disk_io.h
Normal file
20
include/libusbhsfs/source/lwext4/ext_disk_io.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* ext_disk_io.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __EXT_DISK_IO_H__
|
||||||
|
#define __EXT_DISK_IO_H__
|
||||||
|
|
||||||
|
/// Returns a pointer to a dynamically allocated ext4_blockdev object using the provided data.
|
||||||
|
struct ext4_blockdev *ext_disk_io_alloc_blockdev(void *p_user, u64 part_lba, u64 part_size);
|
||||||
|
|
||||||
|
/// Frees a previously allocated ext4_blockdev object.
|
||||||
|
void ext_disk_io_free_blockdev(struct ext4_blockdev *bdev);
|
||||||
|
|
||||||
|
#endif /* __EXT_DISK_IO_H__ */
|
345
include/libusbhsfs/source/ntfs-3g/ntfs.c
Normal file
345
include/libusbhsfs/source/ntfs-3g/ntfs.c
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
/*
|
||||||
|
* ntfs.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
/* Type definitions. */
|
||||||
|
|
||||||
|
/// NTFS path.
|
||||||
|
typedef struct _ntfs_path {
|
||||||
|
const char *path; ///< Volume path (e.g. '/foo/bar/file.txt').
|
||||||
|
const char *dir; ///< Directory path (e.g. '/foo/bar').
|
||||||
|
const char *name; ///< Filename (e.g. 'file.txt').
|
||||||
|
char buf[MAX_PATH_LENGTH]; ///< Internal buffer containing the path string.
|
||||||
|
} ntfs_path;
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static ntfs_inode *ntfs_inode_open_from_path_reparse(ntfs_vd *vd, const char *path, int reparse_depth);
|
||||||
|
|
||||||
|
static void ntfs_split_path(const char *path, ntfs_path *p);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
int ntfs_log_handler_usbhsfs(const char *function, const char *file, int line, u32 level, void *data, const char *format, va_list args)
|
||||||
|
{
|
||||||
|
(void)data;
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
size_t formatted_str_len = 0;
|
||||||
|
char *formatted_str = NULL;
|
||||||
|
|
||||||
|
/* Get formatted string length. */
|
||||||
|
formatted_str_len = vsnprintf(NULL, 0, format, args);
|
||||||
|
if (!formatted_str_len) return ret;
|
||||||
|
|
||||||
|
/* Allocate buffer for the formatted string. */
|
||||||
|
formatted_str = calloc(++formatted_str_len, sizeof(char));
|
||||||
|
if (!formatted_str) return ret;
|
||||||
|
|
||||||
|
/* Generate formatted string and save it to the logfile. */
|
||||||
|
ret = (int)vsnprintf(formatted_str, formatted_str_len, format, args);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
/* Remove CRLFs and dots - we take care of them. */
|
||||||
|
if (formatted_str[formatted_str_len - 1] == '\n') formatted_str[--formatted_str_len] = '\0';
|
||||||
|
if (formatted_str[formatted_str_len - 1] == '\r') formatted_str[--formatted_str_len] = '\0';
|
||||||
|
if (formatted_str[formatted_str_len - 1] == '.') formatted_str[--formatted_str_len] = '\0';
|
||||||
|
|
||||||
|
/* Log message. */
|
||||||
|
usbHsFsLogWriteFormattedStringToLogFile(function, "%s (file \"%s\", line %d, level 0x%X).", formatted_str, file, line, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free allocated buffer. */
|
||||||
|
free(formatted_str);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif /* DEBUG */
|
||||||
|
|
||||||
|
ntfs_inode *ntfs_inode_open_from_path(ntfs_vd *vd, const char *path)
|
||||||
|
{
|
||||||
|
return ntfs_inode_open_from_path_reparse(vd, path, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ntfs_inode *ntfs_inode_create(ntfs_vd *vd, const char *path, mode_t type, const char *target)
|
||||||
|
{
|
||||||
|
ntfs_path full_path = {0};
|
||||||
|
ntfs_inode *dir_ni = NULL, *ni = NULL;
|
||||||
|
ntfschar *uname = NULL, *utarget = NULL;
|
||||||
|
int uname_len = 0, utarget_len = 0;
|
||||||
|
|
||||||
|
/* Safety check. */
|
||||||
|
if (!vd || !vd->vol || !path || !*path || (type == S_IFLNK && (!target || !*target)))
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split entry path. */
|
||||||
|
ntfs_split_path(path, &full_path);
|
||||||
|
|
||||||
|
/* Make sure we have a valid entry name to work with. */
|
||||||
|
if (full_path.name[0] == '\0')
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open the parent directory the desired entry will be created in. */
|
||||||
|
dir_ni = ntfs_inode_open_from_path(vd, full_path.dir);
|
||||||
|
if (!dir_ni) goto end;
|
||||||
|
|
||||||
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
||||||
|
uname_len = ntfs_mbstoucs(full_path.name, &uname);
|
||||||
|
if (uname_len <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the new entry. */
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case S_IFDIR: /* Directory. */
|
||||||
|
case S_IFREG: /* File. */
|
||||||
|
USBHSFS_LOG_MSG("Creating inode in directory \"%s\" named \"%s\".", full_path.dir, full_path.name);
|
||||||
|
ni = ntfs_create(dir_ni, 0, uname, uname_len, type);
|
||||||
|
break;
|
||||||
|
case S_IFLNK: /* Symbolic link. */
|
||||||
|
/* Convert the target link path string from our current locale (UTF-8) into UTF-16LE. */
|
||||||
|
utarget_len = ntfs_mbstoucs(target, &utarget);
|
||||||
|
if (utarget_len <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Creating symlink in directory \"%s\" named \"%s\" targetting \"%s\".", full_path.dir, full_path.name, target);
|
||||||
|
ni = ntfs_create_symlink(dir_ni, 0, uname, uname_len, utarget, utarget_len);
|
||||||
|
break;
|
||||||
|
default: /* Invalid entry. */
|
||||||
|
errno = EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (utarget) free(utarget);
|
||||||
|
|
||||||
|
if (uname) free(uname);
|
||||||
|
|
||||||
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
||||||
|
|
||||||
|
return ni;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_inode_link(ntfs_vd *vd, const char *old_path, const char *new_path)
|
||||||
|
{
|
||||||
|
ntfs_path full_old_path = {0}, full_new_path = {0};
|
||||||
|
ntfs_inode *ni = NULL, *dir_ni = NULL;
|
||||||
|
ntfschar *uname = NULL;
|
||||||
|
int ret = -1, uname_len = 0;
|
||||||
|
|
||||||
|
/* Safety check. */
|
||||||
|
if (!vd || !vd->vol || !old_path || !*old_path || !new_path || !*new_path)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split entry paths. */
|
||||||
|
ntfs_split_path(old_path, &full_old_path);
|
||||||
|
ntfs_split_path(new_path, &full_new_path);
|
||||||
|
|
||||||
|
/* Make sure we have valid old and new entry names to work with. */
|
||||||
|
if (full_old_path.name[0] == '\0' || full_new_path.name[0] == '\0')
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open the entry we will create a symlink for. */
|
||||||
|
ni = ntfs_inode_open_from_path(vd, full_old_path.path);
|
||||||
|
if (!ni) goto end;
|
||||||
|
|
||||||
|
/* Open new parent directory. */
|
||||||
|
dir_ni = ntfs_inode_open_from_path(vd, full_new_path.dir);
|
||||||
|
if (!dir_ni) goto end;
|
||||||
|
|
||||||
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
||||||
|
uname_len = ntfs_mbstoucs(full_new_path.name, &uname);
|
||||||
|
if (uname_len <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link the entry to its new parent directory. */
|
||||||
|
USBHSFS_LOG_MSG("Linking inode \"%s\" to \"%s\".", full_old_path.path, full_new_path.path);
|
||||||
|
ret = ntfs_link(ni, dir_ni, uname, uname_len);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (uname) free(uname);
|
||||||
|
|
||||||
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
||||||
|
|
||||||
|
if (ni) ntfs_inode_close(ni);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ntfs_inode_unlink(ntfs_vd *vd, const char *path)
|
||||||
|
{
|
||||||
|
ntfs_path full_path = {0};
|
||||||
|
ntfs_inode *ni = NULL, *dir_ni = NULL;
|
||||||
|
ntfschar *uname = NULL;
|
||||||
|
int ret = -1, uname_len = 0;
|
||||||
|
|
||||||
|
/* Safety check. */
|
||||||
|
if (!vd || !vd->vol || !path || !*path)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split entry path. */
|
||||||
|
ntfs_split_path(path, &full_path);
|
||||||
|
|
||||||
|
/* Make sure we have a valid entry name to work with. */
|
||||||
|
if (full_path.name[0] == '\0')
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open entry. */
|
||||||
|
ni = ntfs_inode_open_from_path(vd, full_path.path);
|
||||||
|
if (!ni) goto end;
|
||||||
|
|
||||||
|
/* Open parent directory. */
|
||||||
|
dir_ni = ntfs_inode_open_from_path(vd, full_path.dir);
|
||||||
|
if (!dir_ni) goto end;
|
||||||
|
|
||||||
|
/* Convert the entry name string from our current locale (UTF-8) into UTF-16LE. */
|
||||||
|
uname_len = ntfs_mbstoucs(full_path.name, &uname);
|
||||||
|
if (uname_len <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Unlinking inode \"%s\" from \"%s\".", full_path.name, full_path.dir);
|
||||||
|
|
||||||
|
/* Unlink entry from its parent. */
|
||||||
|
/* 'ni' and 'dir_ni' are always closed by ntfs_delete(), even if it fails. */
|
||||||
|
ret = ntfs_delete(vd->vol, full_path.path, ni, dir_ni, uname, uname_len);
|
||||||
|
|
||||||
|
ni = NULL;
|
||||||
|
dir_ni = NULL;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (uname) free(uname);
|
||||||
|
|
||||||
|
if (dir_ni) ntfs_inode_close(dir_ni);
|
||||||
|
|
||||||
|
if (ni) ntfs_inode_close(ni);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ntfs_inode_update_times_filtered(ntfs_vd *vd, ntfs_inode *ni, ntfs_time_update_flags mask)
|
||||||
|
{
|
||||||
|
if (!vd || !ni) return;
|
||||||
|
|
||||||
|
/* Run the access time update strategy against the volume settings first. */
|
||||||
|
if (!vd->update_access_times) mask &= ~NTFS_UPDATE_ATIME;
|
||||||
|
|
||||||
|
/* Update entry times. */
|
||||||
|
if (mask)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Updating access times for inode %lu (mask 0x%X).", ni->mft_no, mask);
|
||||||
|
ntfs_inode_update_times(ni, mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ntfs_inode *ntfs_inode_open_from_path_reparse(ntfs_vd *vd, const char *path, int reparse_depth)
|
||||||
|
{
|
||||||
|
ntfs_inode *ni = NULL;
|
||||||
|
char *target = NULL;
|
||||||
|
|
||||||
|
/* Safety check. */
|
||||||
|
if (!vd || !vd->vol || !path || !*path || reparse_depth <= 0 || reparse_depth > NTFS_MAX_SYMLINK_DEPTH)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Opening requested inode \"%s\" (reparse depth %d).", path, reparse_depth);
|
||||||
|
|
||||||
|
/* Open requested inode. */
|
||||||
|
ni = ntfs_pathname_to_inode(vd->vol, NULL, path);
|
||||||
|
if (!ni)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to open requested inode \"%s\" (errno %d).", path, errno);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Successfully opened inode from path \"%s\" (mft_no %lu).", path, ni->mft_no);
|
||||||
|
|
||||||
|
/* If the entry was found and it has reparse data, then resolve the true entry. */
|
||||||
|
/* This effectively follows directory junctions and symbolic links until the target entry is found. */
|
||||||
|
if ((ni->flags & FILE_ATTR_REPARSE_POINT) && ntfs_possible_symlink(ni))
|
||||||
|
{
|
||||||
|
/* Get the target path of this entry. */
|
||||||
|
target = ntfs_make_symlink(ni, path);
|
||||||
|
if (!target) goto end;
|
||||||
|
|
||||||
|
/* Close this entry (we are no longer interested in it). */
|
||||||
|
ntfs_inode_close(ni);
|
||||||
|
|
||||||
|
/* Open the target entry. */
|
||||||
|
USBHSFS_LOG_MSG("Following inode symlink \"%s\" -> \"%s\".", path, target);
|
||||||
|
ni = ntfs_inode_open_from_path_reparse(vd, target, ++reparse_depth);
|
||||||
|
|
||||||
|
/* Clean up. */
|
||||||
|
free(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ni;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function doesn't perform checks on the provided path because it is guaranteed to be valid. */
|
||||||
|
/* Check ntfsdev_fixpath(). */
|
||||||
|
static void ntfs_split_path(const char *path, ntfs_path *p)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Input path: \"%s\".", path);
|
||||||
|
|
||||||
|
/* Setup NTFS path. */
|
||||||
|
memset(p, 0, sizeof(ntfs_path));
|
||||||
|
p->path = path;
|
||||||
|
|
||||||
|
/* Copy the path to internal buffer so we can modify it. */
|
||||||
|
strcpy(p->buf, p->path);
|
||||||
|
|
||||||
|
/* Split the path into separate directory and filename parts. */
|
||||||
|
/* e.g. "/dir/file.txt" => dir: "/dir", name: "file.txt". */
|
||||||
|
char *buf_sep = strrchr(p->buf, PATH_SEP);
|
||||||
|
|
||||||
|
/* If there's just a single path separator at the start of the string, set the directory string to a path separator. */
|
||||||
|
/* Otherwise, just use the directory string as-is. */
|
||||||
|
p->dir = (buf_sep == p->buf ? "/" : p->buf);
|
||||||
|
|
||||||
|
/* Remove the path separator we found and update the entry name pointer. */
|
||||||
|
*buf_sep = '\0';
|
||||||
|
p->name = (buf_sep + 1);
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Output strings -> Path: \"%s\" | Directory: \"%s\" | Name: \"%s\".", p->path, p->dir, p->name);
|
||||||
|
}
|
69
include/libusbhsfs/source/ntfs-3g/ntfs.h
Normal file
69
include/libusbhsfs/source/ntfs-3g/ntfs.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* ntfs.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __NTFS_H__
|
||||||
|
#define __NTFS_H__
|
||||||
|
|
||||||
|
#include <ntfs-3g/config.h>
|
||||||
|
#include <ntfs-3g/types.h>
|
||||||
|
#include <ntfs-3g/bootsect.h>
|
||||||
|
#include <ntfs-3g/layout.h>
|
||||||
|
#include <ntfs-3g/device.h>
|
||||||
|
#include <ntfs-3g/volume.h>
|
||||||
|
#include <ntfs-3g/cache.h>
|
||||||
|
#include <ntfs-3g/inode.h>
|
||||||
|
#include <ntfs-3g/logging.h>
|
||||||
|
#include <ntfs-3g/dir.h>
|
||||||
|
#include <ntfs-3g/reparse.h>
|
||||||
|
|
||||||
|
#include "../usbhsfs_utils.h"
|
||||||
|
|
||||||
|
#include "ntfs_disk_io.h"
|
||||||
|
|
||||||
|
/// NTFS errno values.
|
||||||
|
#define ENOPART 3000 /* No partition was found. */
|
||||||
|
#define EINVALPART 3001 /* Specified partition is invalid or not supported. */
|
||||||
|
#define EDIRTY 3002 /* Volume is dirty and NTFS_RECOVER was not specified during mount. */
|
||||||
|
#define EHIBERNATED 3003 /* Volume is hibernated and NTFS_IGNORE_HIBERFILE was not specified during mount. */
|
||||||
|
|
||||||
|
#define NTFS_MAX_SYMLINK_DEPTH 10 /* Maximum search depth when resolving symbolic links. */
|
||||||
|
|
||||||
|
/// NTFS volume descriptor.
|
||||||
|
typedef struct _ntfs_vd {
|
||||||
|
struct _ntfs_dd *dd; ///< NTFS device descriptor.
|
||||||
|
struct ntfs_device *dev; ///< NTFS device handle.
|
||||||
|
ntfs_volume *vol; ///< NTFS volume handle.
|
||||||
|
u32 flags; ///< NTFS mount flags.
|
||||||
|
s64 id; ///< Filesystem ID.
|
||||||
|
u16 uid; ///< User ID for entry creation.
|
||||||
|
u16 gid; ///< Group ID for entry creation.
|
||||||
|
u16 fmask; ///< Unix style permission mask for file creation.
|
||||||
|
u16 dmask; ///< Unix style permission mask for directory creation.
|
||||||
|
bool update_access_times; ///< True if file/directory access times should be updated during I/O operations.
|
||||||
|
bool ignore_read_only_attr; ///< True if read-only file attributes should be ignored (allows writing to read-only files).
|
||||||
|
} ntfs_vd;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
int ntfs_log_handler_usbhsfs(const char *function, const char *file, int line, u32 level, void *data, const char *format, va_list args);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ntfs_inode *ntfs_inode_open_from_path(ntfs_vd *vd, const char *path);
|
||||||
|
|
||||||
|
ntfs_inode *ntfs_inode_create(ntfs_vd *vd, const char *path, mode_t type, const char *target);
|
||||||
|
|
||||||
|
int ntfs_inode_link(ntfs_vd *vd, const char *old_path, const char *new_path);
|
||||||
|
int ntfs_inode_unlink(ntfs_vd *vd, const char *path);
|
||||||
|
|
||||||
|
void ntfs_inode_update_times_filtered(ntfs_vd *vd, ntfs_inode *ni, ntfs_time_update_flags mask);
|
||||||
|
|
||||||
|
#endif /* __NTFS_H__ */
|
1246
include/libusbhsfs/source/ntfs-3g/ntfs_dev.c
Normal file
1246
include/libusbhsfs/source/ntfs-3g/ntfs_dev.c
Normal file
File diff suppressed because it is too large
Load Diff
19
include/libusbhsfs/source/ntfs-3g/ntfs_dev.h
Normal file
19
include/libusbhsfs/source/ntfs-3g/ntfs_dev.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* ntfs_dev.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __NTFS_DEV_H__
|
||||||
|
#define __NTFS_DEV_H__
|
||||||
|
|
||||||
|
const devoptab_t *ntfsdev_get_devoptab();
|
||||||
|
|
||||||
|
#endif /* __NTFS_DEV_H__ */
|
554
include/libusbhsfs/source/ntfs-3g/ntfs_disk_io.c
Normal file
554
include/libusbhsfs/source/ntfs-3g/ntfs_disk_io.c
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
/*
|
||||||
|
* ntfs_disk_io.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "ntfs.h"
|
||||||
|
|
||||||
|
#include "../usbhsfs_scsi.h"
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int ntfs_io_device_open(struct ntfs_device *dev, int flags);
|
||||||
|
static int ntfs_io_device_close(struct ntfs_device *dev);
|
||||||
|
static s64 ntfs_io_device_seek(struct ntfs_device *dev, s64 offset, int whence);
|
||||||
|
static s64 ntfs_io_device_read(struct ntfs_device *dev, void *buf, s64 count);
|
||||||
|
static s64 ntfs_io_device_write(struct ntfs_device *dev, const void *buf, s64 count);
|
||||||
|
static s64 ntfs_io_device_pread(struct ntfs_device *dev, void *buf, s64 count, s64 offset);
|
||||||
|
static s64 ntfs_io_device_pwrite(struct ntfs_device *dev, const void *buf, s64 count, s64 offset);
|
||||||
|
static int ntfs_io_device_sync(struct ntfs_device *dev);
|
||||||
|
static int ntfs_io_device_stat(struct ntfs_device *dev, struct stat *buf);
|
||||||
|
static int ntfs_io_device_ioctl(struct ntfs_device *dev, unsigned long request, void *argp);
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_readbytes(struct ntfs_device *dev, s64 offset, s64 count, void *buf);
|
||||||
|
static s64 ntfs_io_device_writebytes(struct ntfs_device *dev, s64 offset, s64 count, const void *buf);
|
||||||
|
static bool ntfs_io_device_readsectors(struct ntfs_device *dev, u64 start, u32 count, void *buf);
|
||||||
|
static bool ntfs_io_device_writesectors(struct ntfs_device *dev, u64 start, u32 count, const void *buf);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static struct ntfs_device_operations ntfs_device_usbhs_io_ops = {
|
||||||
|
.open = ntfs_io_device_open,
|
||||||
|
.close = ntfs_io_device_close,
|
||||||
|
.seek = ntfs_io_device_seek,
|
||||||
|
.read = ntfs_io_device_read,
|
||||||
|
.write = ntfs_io_device_write,
|
||||||
|
.pread = ntfs_io_device_pread,
|
||||||
|
.pwrite = ntfs_io_device_pwrite,
|
||||||
|
.sync = ntfs_io_device_sync,
|
||||||
|
.stat = ntfs_io_device_stat,
|
||||||
|
.ioctl = ntfs_io_device_ioctl
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ntfs_device_operations *ntfs_disk_io_get_dops(void)
|
||||||
|
{
|
||||||
|
return &ntfs_device_usbhs_io_ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_io_device_open(struct ntfs_device *dev, int flags)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, flags 0x%X.", dev, flags);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the device isn't already open (e.g. used by another mount). */
|
||||||
|
if (NDevOpen(dev))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Device %p is busy (already open).", dev);
|
||||||
|
errno = EBUSY;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the boot sector is valid. */
|
||||||
|
if (!ntfs_boot_sector_is_ntfs(&(dd->vbr)))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid NTFS volume in device %p.", dev);
|
||||||
|
errno = EINVALPART;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse partition info from the boot sector. */
|
||||||
|
dd->sector_offset = le32_to_cpu(dd->vbr.bpb.hidden_sectors);
|
||||||
|
dd->sector_size = le16_to_cpu(dd->vbr.bpb.bytes_per_sector);
|
||||||
|
dd->sector_count = sle64_to_cpu(dd->vbr.number_of_sectors);
|
||||||
|
dd->pos = 0;
|
||||||
|
dd->len = ((u64)dd->sector_size * dd->sector_count);
|
||||||
|
dd->ino = le64_to_cpu(dd->vbr.volume_serial_number);
|
||||||
|
|
||||||
|
/* Mark the device as read-only (if requested). */
|
||||||
|
if (flags & O_RDONLY) NDevSetReadOnly(dev);
|
||||||
|
|
||||||
|
/* Mark the device as open. */
|
||||||
|
NDevSetBlock(dev);
|
||||||
|
NDevSetOpen(dev);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_io_device_close(struct ntfs_device *dev)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p.", dev);
|
||||||
|
|
||||||
|
/* Check if the device is actually open. */
|
||||||
|
if (!NDevOpen(dev))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Device %p is not open.", dev);
|
||||||
|
errno = EIO;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark the device as closed. */
|
||||||
|
NDevClearOpen(dev);
|
||||||
|
NDevClearBlock(dev);
|
||||||
|
|
||||||
|
/* Flush the device (if dirty and not read-only). */
|
||||||
|
if (NDevDirty(dev) && !NDevReadOnly(dev))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Device %p is dirty. Synchronizing data.", dev);
|
||||||
|
ntfs_io_device_sync(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_seek(struct ntfs_device *dev, s64 offset, int whence)
|
||||||
|
{
|
||||||
|
s64 ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, offset 0x%lX, whence %d.", dev, offset, whence);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the current position on the device (in bytes). */
|
||||||
|
switch(whence)
|
||||||
|
{
|
||||||
|
case SEEK_SET:
|
||||||
|
dd->pos = MIN(MAX(offset, 0), dd->len);
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
dd->pos = MIN(MAX(dd->pos + offset, 0), dd->len);
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
dd->pos = MIN(MAX(dd->len + offset, 0), dd->len);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_read(struct ntfs_device *dev, void *buf, s64 count)
|
||||||
|
{
|
||||||
|
return ntfs_io_device_readbytes(dev, ((ntfs_dd*)dev->d_private)->pos, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_write(struct ntfs_device *dev, const void *buf, s64 count)
|
||||||
|
{
|
||||||
|
return ntfs_io_device_writebytes(dev, ((ntfs_dd*)dev->d_private)->pos, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_pread(struct ntfs_device *dev, void *buf, s64 count, s64 offset)
|
||||||
|
{
|
||||||
|
return ntfs_io_device_readbytes(dev, offset, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_pwrite(struct ntfs_device *dev, const void *buf, s64 count, s64 offset)
|
||||||
|
{
|
||||||
|
return ntfs_io_device_writebytes(dev, offset, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_readbytes(struct ntfs_device *dev, s64 offset, s64 count, void *buf)
|
||||||
|
{
|
||||||
|
s64 ret = -1;
|
||||||
|
u64 sec_start = 0, sec_count = 1;
|
||||||
|
u32 buffer_offset = 0;
|
||||||
|
u8 *buffer = NULL;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, offset 0x%lX, count 0x%lX.", dev, offset, count);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure the provided offset isn't negative and the amount of bytes to read is valid. */
|
||||||
|
if (offset < 0 || count <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill values. */
|
||||||
|
sec_start = dd->sector_start;
|
||||||
|
buffer_offset = (u32)(offset % dd->sector_size);
|
||||||
|
|
||||||
|
/* Determine the range of sectors required for this read. */
|
||||||
|
if (offset > 0) sec_start += (u64)floor((double)offset / (double)dd->sector_size);
|
||||||
|
|
||||||
|
if ((buffer_offset + count) > dd->sector_size) sec_count = (u64)ceil((double)(buffer_offset + count) / (double)dd->sector_size);
|
||||||
|
|
||||||
|
/* Read data from device. */
|
||||||
|
if (!buffer_offset && !(count % dd->sector_size))
|
||||||
|
{
|
||||||
|
/* If this read happens to be on sector boundaries, then read straight into the destination buffer. */
|
||||||
|
USBHSFS_LOG_MSG("Reading 0x%lX sector(s) at sector 0x%lX from device %p (direct read).", sec_count, sec_start, dev);
|
||||||
|
if (ntfs_io_device_readsectors(dev, sec_start, sec_count, buf))
|
||||||
|
{
|
||||||
|
/* Update return value. */
|
||||||
|
ret = count;
|
||||||
|
} else {
|
||||||
|
USBHSFS_LOG_MSG("Failed to read 0x%lX sector(s) at sector 0x%lX from device %p (direct read).", sec_count, sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Read data into a buffer and copy over only what was requested. */
|
||||||
|
/* This shouldn't normally happen as NTFS-3G aligns addresses and sizes to sectors, but it's better to be safe than sorry. */
|
||||||
|
|
||||||
|
/* Allocate a buffer to hold the read data. */
|
||||||
|
buffer = malloc(sec_count * (u64)dd->sector_size);
|
||||||
|
if (!buffer)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read data. */
|
||||||
|
USBHSFS_LOG_MSG("Reading 0x%lX sector(s) from sector 0x%lX in device %p (buffered read).", sec_count, sec_start, dev);
|
||||||
|
if (ntfs_io_device_readsectors(dev, sec_start, sec_count, buffer))
|
||||||
|
{
|
||||||
|
/* Copy what was requested to the destination buffer. */
|
||||||
|
memcpy(buf, buffer + buffer_offset, count);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = count;
|
||||||
|
} else {
|
||||||
|
USBHSFS_LOG_MSG("Failed to read 0x%lX sector(s) at sector 0x%lX from device %p (buffered read).", sec_count, sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (buffer) free(buffer);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static s64 ntfs_io_device_writebytes(struct ntfs_device *dev, s64 offset, s64 count, const void *buf)
|
||||||
|
{
|
||||||
|
s64 ret = -1;
|
||||||
|
u64 sec_start = 0, sec_count = 1;
|
||||||
|
u32 buffer_offset = 0;
|
||||||
|
u8 *buffer = NULL;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, offset 0x%lX, count 0x%lX.", dev, offset, count);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the device can be written to. */
|
||||||
|
if (NDevReadOnly(dev))
|
||||||
|
{
|
||||||
|
errno = EROFS;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure the provided offset isn't negative and the amount of bytes to write is valid. */
|
||||||
|
if (offset < 0 || count <= 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill values. */
|
||||||
|
sec_start = dd->sector_start;
|
||||||
|
buffer_offset = (u32)(offset % dd->sector_size);
|
||||||
|
|
||||||
|
/* Determine the range of sectors required for this read. */
|
||||||
|
if (offset > 0) sec_start += (u64)floor((double)offset / (double)dd->sector_size);
|
||||||
|
|
||||||
|
if ((buffer_offset + count) > dd->sector_size) sec_count = (u64)ceil((double)(buffer_offset + count) / (double)dd->sector_size);
|
||||||
|
|
||||||
|
/* Write data to device. */
|
||||||
|
if (!buffer_offset && !(count % dd->sector_size))
|
||||||
|
{
|
||||||
|
/* If this write happens to be on sector boundaries, then write straight to the device. */
|
||||||
|
USBHSFS_LOG_MSG("Writing 0x%lX sector(s) at sector 0x%lX from device %p (direct write).", sec_count, sec_start, dev);
|
||||||
|
if (ntfs_io_device_writesectors(dev, sec_start, sec_count, buf))
|
||||||
|
{
|
||||||
|
/* Update return value. */
|
||||||
|
ret = count;
|
||||||
|
} else {
|
||||||
|
USBHSFS_LOG_MSG("Failed to write 0x%lX sector(s) at sector 0x%lX from device %p (direct write).", sec_count, sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Write data from a buffer aligned to the sector boundaries. */
|
||||||
|
/* This shouldn't normally happen as NTFS-3G aligns addresses and sizes to sectors, but it's better to be safe than sorry. */
|
||||||
|
|
||||||
|
/* Allocate a buffer to hold the read data. */
|
||||||
|
buffer = malloc(sec_count * (u64)dd->sector_size);
|
||||||
|
if (!buffer)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the first and last sectors of the buffer from the device (if required). */
|
||||||
|
/* This is done thwn the data doesn't line up with sector boundaries, so we just in the buffer edges where the data overlaps. */
|
||||||
|
if (buffer_offset != 0 && !ntfs_io_device_readsectors(dev, sec_start, 1, buffer))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to read sector 0x%lX from device %p (first).", sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((buffer_offset + count) % dd->sector_size) != 0 && !ntfs_io_device_readsectors(dev, sec_start + sec_count - 1, 1, buffer + ((sec_count - 1) * dd->sector_size)))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to read sector 0x%lX from device %p (last).", sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy data into the write buffer. */
|
||||||
|
memcpy(buffer + buffer_offset, buf, count);
|
||||||
|
|
||||||
|
/* Write data. */
|
||||||
|
USBHSFS_LOG_MSG("Writing 0x%lX sector(s) at sector 0x%lX from device %p (buffered write).", sec_count, sec_start, dev);
|
||||||
|
if (ntfs_io_device_writesectors(dev, sec_start, sec_count, buffer))
|
||||||
|
{
|
||||||
|
/* Write successful. Mark the device as dirty. */
|
||||||
|
NDevSetDirty(dev);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = count;
|
||||||
|
} else {
|
||||||
|
USBHSFS_LOG_MSG("Failed to write 0x%lX sector(s) at sector 0x%lX from device %p (buffered write).", sec_count, sec_start, dev);
|
||||||
|
errno = EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (buffer) free(buffer);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ntfs_io_device_readsectors(struct ntfs_device *dev, u64 start, u32 count, void *buf)
|
||||||
|
{
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd) return false;
|
||||||
|
|
||||||
|
/* Get LUN context and read sectors. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)dd->lun_ctx;
|
||||||
|
return usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, buf, start, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ntfs_io_device_writesectors(struct ntfs_device *dev, u64 start, u32 count, const void *buf)
|
||||||
|
{
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd) return false;
|
||||||
|
|
||||||
|
/* Get LUN context and write sectors. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)dd->lun_ctx;
|
||||||
|
return usbHsFsScsiWriteLogicalUnitBlocks(lun_ctx, buf, start, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_io_device_sync(struct ntfs_device *dev)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p.", dev);
|
||||||
|
|
||||||
|
/* Check if the device can be written to. */
|
||||||
|
if (NDevReadOnly(dev))
|
||||||
|
{
|
||||||
|
errno = EROFS;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: implement write cache? */
|
||||||
|
|
||||||
|
/* Mark the device as clean. */
|
||||||
|
NDevClearDirty(dev);
|
||||||
|
NDevClearSync(dev);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_io_device_stat(struct ntfs_device *dev, struct stat *buf)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, buf %p.", dev, buf);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get LUN context. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)dd->lun_ctx;
|
||||||
|
if (!lun_ctx)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build device mode. */
|
||||||
|
mode_t mode = (S_IFBLK | S_IRUSR | S_IRGRP | S_IROTH | (!NDevReadOnly(dev) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0));
|
||||||
|
|
||||||
|
/* Clear the stat buffer. */
|
||||||
|
memset(buf, 0, sizeof(struct stat));
|
||||||
|
|
||||||
|
/* Fill device stats. */
|
||||||
|
buf->st_dev = lun_ctx->usb_if_id;
|
||||||
|
buf->st_ino = dd->ino;
|
||||||
|
buf->st_mode = mode;
|
||||||
|
buf->st_rdev = lun_ctx->usb_if_id;
|
||||||
|
buf->st_size = ((u64)dd->sector_size * dd->sector_count);
|
||||||
|
buf->st_blksize = dd->sector_size;
|
||||||
|
buf->st_blocks = dd->sector_count;
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ntfs_io_device_ioctl(struct ntfs_device *dev, unsigned long request, void *argp)
|
||||||
|
{
|
||||||
|
(void)argp;
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Device %p, ioctl 0x%lX, argp %p.", dev, request, argp);
|
||||||
|
|
||||||
|
/* Get device descriptor. */
|
||||||
|
ntfs_dd *dd = (ntfs_dd*)dev->d_private;
|
||||||
|
if (!dd)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get LUN context. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)dd->lun_ctx;
|
||||||
|
if (!lun_ctx)
|
||||||
|
{
|
||||||
|
errno = EBADF;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Figure out which control was requested. */
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
#ifdef BLKGETSIZE64
|
||||||
|
case BLKGETSIZE64: /* Get block device size (bytes). */
|
||||||
|
*(u64*)argp = lun_ctx->capacity;
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef BLKGETSIZE
|
||||||
|
case BLKGETSIZE: /* Get block device size (sectors). */
|
||||||
|
*(u32*)argp = (u32)lun_ctx->block_count;
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef HDIO_GETGEO
|
||||||
|
case HDIO_GETGEO: /* Get hard drive geometry. */
|
||||||
|
{
|
||||||
|
/* TODO: properly define this? */
|
||||||
|
struct hd_geometry *geo = (struct hd_geometry*)argp;
|
||||||
|
geo->cylinders = 0;
|
||||||
|
geo->heads = 0;
|
||||||
|
geo->sectors = 0;
|
||||||
|
geo->start = dd->sector_offset;
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef BLKSSZGET
|
||||||
|
case BLKSSZGET: /* Get block device sector size. */
|
||||||
|
*(int*)argp = (int)lun_ctx->block_length;
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef BLKBSZSET
|
||||||
|
case BLKBSZSET: /* Set block device sector size. */
|
||||||
|
dd->sector_size = *(int*)argp;
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if defined(BLKDISCARD)
|
||||||
|
case BLKDISCARD: /* Discard device sectors. */
|
||||||
|
/* TODO: zero out sectors. */
|
||||||
|
USBHSFS_LOG_MSG("Bulk discard is not supported.");
|
||||||
|
errno = EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default: /* Unimplemented control. */
|
||||||
|
USBHSFS_LOG_MSG("Unsupported ioctl 0x%lX was requested.", request);
|
||||||
|
errno = EOPNOTSUPP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
33
include/libusbhsfs/source/ntfs-3g/ntfs_disk_io.h
Normal file
33
include/libusbhsfs/source/ntfs-3g/ntfs_disk_io.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* ntfs_disk_io.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Based on work from libntfs-wii (https://github.com/rhyskoedijk/libntfs-wii).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __NTFS_DISK_IO_H__
|
||||||
|
#define __NTFS_DISK_IO_H__
|
||||||
|
|
||||||
|
/// NTFS device descriptor.
|
||||||
|
typedef struct _ntfs_dd {
|
||||||
|
void *lun_ctx; ///< Logical unit context.
|
||||||
|
NTFS_BOOT_SECTOR vbr; ///< Volume Boot Record (VBR) data. This is the first sector of the filesystem.
|
||||||
|
u64 sector_start; ///< LBA of partition start.
|
||||||
|
u64 sector_offset; ///< LBA offset to true partition start (as described by boot sector).
|
||||||
|
u16 sector_size; ///< Device sector size (in bytes).
|
||||||
|
u64 sector_count; ///< Total number of sectors in partition.
|
||||||
|
u64 pos; ///< Current position within the partition (in bytes).
|
||||||
|
u64 len; ///< Total length of partition (in bytes).
|
||||||
|
ino_t ino; ///< Device identifier (serial number).
|
||||||
|
} ntfs_dd;
|
||||||
|
|
||||||
|
/// Returns a pointer to the generic ntfs_device_operations object.
|
||||||
|
struct ntfs_device_operations *ntfs_disk_io_get_dops(void);
|
||||||
|
|
||||||
|
#endif /* __NTFS_DISK_IO_H__ */
|
65
include/libusbhsfs/source/sxos/service_guard.h
Normal file
65
include/libusbhsfs/source/sxos/service_guard.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* service_guard.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018-2021, Switchbrew and libnx contributors.
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __SERVICE_GUARD_H__
|
||||||
|
#define __SERVICE_GUARD_H__
|
||||||
|
|
||||||
|
typedef struct ServiceGuard {
|
||||||
|
Mutex mutex;
|
||||||
|
u32 refCount;
|
||||||
|
} ServiceGuard;
|
||||||
|
|
||||||
|
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||||
|
{
|
||||||
|
mutexLock(&g->mutex);
|
||||||
|
return (g->refCount++) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||||
|
{
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
cleanupFunc();
|
||||||
|
--g->refCount;
|
||||||
|
}
|
||||||
|
mutexUnlock(&g->mutex);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||||
|
{
|
||||||
|
mutexLock(&g->mutex);
|
||||||
|
if (g->refCount && (--g->refCount) == 0)
|
||||||
|
cleanupFunc();
|
||||||
|
mutexUnlock(&g->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||||
|
\
|
||||||
|
static ServiceGuard g_##name##Guard = {0}; \
|
||||||
|
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||||
|
static void _##name##Cleanup(void); \
|
||||||
|
\
|
||||||
|
Result name##Initialize _paramdecl \
|
||||||
|
{ \
|
||||||
|
Result rc = 0; \
|
||||||
|
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||||
|
rc = _##name##Initialize _parampass; \
|
||||||
|
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
void name##Exit(void) \
|
||||||
|
{ \
|
||||||
|
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
||||||
|
|
||||||
|
#endif /* __SERVICE_GUARD_H__ */
|
257
include/libusbhsfs/source/sxos/usbfs.c
Normal file
257
include/libusbhsfs/source/sxos/usbfs.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* usbfs.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Blake Warner.
|
||||||
|
* Copyright (c) 2018, Team Xecuter.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "usbfs.h"
|
||||||
|
#include "service_guard.h"
|
||||||
|
|
||||||
|
enum UsbFsServiceCmd {
|
||||||
|
UsbFs_Cmd_GetMountStatus = 0,
|
||||||
|
UsbFs_Cmd_OpenFile = 1,
|
||||||
|
UsbFs_Cmd_CloseFile = 2,
|
||||||
|
UsbFs_Cmd_ReadFile = 3,
|
||||||
|
UsbFs_Cmd_IsReady = 4,
|
||||||
|
UsbFs_Cmd_OpenDir = 5,
|
||||||
|
UsbFs_Cmd_CloseDir = 6,
|
||||||
|
UsbFs_Cmd_ReadDir = 7,
|
||||||
|
UsbFs_Cmd_CreateDir = 8,
|
||||||
|
UsbFs_Cmd_SeekFile = 9,
|
||||||
|
UsbFs_Cmd_ReadRaw = 10,
|
||||||
|
UsbFs_Cmd_WriteFile = 11,
|
||||||
|
UsbFs_Cmd_SyncFile = 12,
|
||||||
|
UsbFs_Cmd_DeleteDir = 13,
|
||||||
|
UsbFs_Cmd_DeleteFile = 14,
|
||||||
|
UsbFs_Cmd_TruncateFile = 15,
|
||||||
|
UsbFs_Cmd_StatFile = 16,
|
||||||
|
UsbFs_Cmd_StatPath = 17,
|
||||||
|
UsbFs_Cmd_StatFilesystem = 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Service g_usbFsSrv = {0};
|
||||||
|
|
||||||
|
NX_GENERATE_SERVICE_GUARD(usbFs);
|
||||||
|
|
||||||
|
Result usbFsGetMountStatus(u64 *status)
|
||||||
|
{
|
||||||
|
return serviceDispatchOut(&g_usbFsSrv, UsbFs_Cmd_GetMountStatus, *status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsOpenFile(u64 *fileid, const char *filepath, u64 mode)
|
||||||
|
{
|
||||||
|
return serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_OpenFile, mode, *fileid, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { filepath, strlen(filepath) + 1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsCloseFile(u64 fileid)
|
||||||
|
{
|
||||||
|
return serviceDispatchIn(&g_usbFsSrv, UsbFs_Cmd_CloseFile, fileid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsReadFile(u64 fileid, void *buffer, size_t size, size_t *retsize)
|
||||||
|
{
|
||||||
|
return serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_ReadFile, fileid, *retsize, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, \
|
||||||
|
.buffers = { { buffer, size } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsIsReady()
|
||||||
|
{
|
||||||
|
return serviceDispatch(&g_usbFsSrv, UsbFs_Cmd_IsReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsOpenDir(u64 *dirid, const char *dirpath)
|
||||||
|
{
|
||||||
|
return serviceDispatchOut(&g_usbFsSrv, UsbFs_Cmd_OpenDir, *dirid, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { dirpath, strlen(dirpath) + 1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsCloseDir(u64 dirid)
|
||||||
|
{
|
||||||
|
return serviceDispatchIn(&g_usbFsSrv, UsbFs_Cmd_CloseDir, dirid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsReadDir(u64 dirid, u64 *type, u64 *size, char *name, size_t namemax)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 type;
|
||||||
|
u64 size;
|
||||||
|
} out;
|
||||||
|
|
||||||
|
rc = serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_ReadDir, dirid, out, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, \
|
||||||
|
.buffers = { { name, namemax } });
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
*type = out.type;
|
||||||
|
*size = out.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsCreateDir(const char *dirpath)
|
||||||
|
{
|
||||||
|
return serviceDispatch(&g_usbFsSrv, UsbFs_Cmd_CreateDir, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { dirpath, strlen(dirpath) + 1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsSeekFile(u64 fileid, u64 pos, u64 whence, u64 *retpos)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
u64 fileid;
|
||||||
|
u64 pos;
|
||||||
|
u64 whence;
|
||||||
|
} in = { fileid, pos, whence };
|
||||||
|
|
||||||
|
return serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_SeekFile, in, *retpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsReadRaw(u64 sector, u64 sectorcount, void *buffer)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
u64 sector;
|
||||||
|
u64 sectorcount;
|
||||||
|
} in = { sector, sectorcount };
|
||||||
|
|
||||||
|
return serviceDispatchIn(&g_usbFsSrv, UsbFs_Cmd_ReadRaw, in, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, \
|
||||||
|
.buffers = { { buffer, 0x200ULL * sectorcount } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsWriteFile(u64 fileid, const void *buffer, size_t size, size_t *retsize)
|
||||||
|
{
|
||||||
|
return serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_WriteFile, fileid, *retsize, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { buffer, size } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsSyncFile(u64 fileid)
|
||||||
|
{
|
||||||
|
return serviceDispatchIn(&g_usbFsSrv, UsbFs_Cmd_SyncFile, fileid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsDeleteDir(const char *dirpath)
|
||||||
|
{
|
||||||
|
return serviceDispatch(&g_usbFsSrv, UsbFs_Cmd_DeleteDir, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { dirpath, strlen(dirpath) + 1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsDeleteFile(const char *filepath)
|
||||||
|
{
|
||||||
|
return serviceDispatch(&g_usbFsSrv, UsbFs_Cmd_DeleteFile, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { filepath, strlen(filepath) + 1 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsTruncateFile(u64 fileid, u64 size)
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
u64 fileid;
|
||||||
|
u64 size;
|
||||||
|
} in = { fileid, size };
|
||||||
|
|
||||||
|
return serviceDispatchIn(&g_usbFsSrv, UsbFs_Cmd_TruncateFile, in);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsStatFile(u64 fileid, u64 *size, u64 *mode)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 size;
|
||||||
|
u64 mode;
|
||||||
|
} out;
|
||||||
|
|
||||||
|
rc = serviceDispatchInOut(&g_usbFsSrv, UsbFs_Cmd_StatFile, fileid, out);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
*size = out.size;
|
||||||
|
*mode = out.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsStatPath(const char *path, u64 *size, u64 *mode)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 size;
|
||||||
|
u64 mode;
|
||||||
|
} out;
|
||||||
|
|
||||||
|
rc = serviceDispatchOut(&g_usbFsSrv, UsbFs_Cmd_StatPath, out, \
|
||||||
|
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, \
|
||||||
|
.buffers = { { path, strlen(path) + 1 } });
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
*size = out.size;
|
||||||
|
*mode = out.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result usbFsStatFilesystem(u64 *totalsize, u64 *freesize)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u64 totalsize;
|
||||||
|
u64 freesize;
|
||||||
|
} out;
|
||||||
|
|
||||||
|
rc = serviceDispatchOut(&g_usbFsSrv, UsbFs_Cmd_StatFilesystem, out);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
*totalsize = out.totalsize;
|
||||||
|
*freesize = out.freesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE Result _usbFsInitialize(void)
|
||||||
|
{
|
||||||
|
return smGetService(&g_usbFsSrv, "usbfs");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _usbFsCleanup(void)
|
||||||
|
{
|
||||||
|
serviceClose(&g_usbFsSrv);
|
||||||
|
}
|
65
include/libusbhsfs/source/sxos/usbfs.h
Normal file
65
include/libusbhsfs/source/sxos/usbfs.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* usbfs.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, Blake Warner.
|
||||||
|
* Copyright (c) 2018, Team Xecuter.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBFS_H__
|
||||||
|
#define __USBFS_H__
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#define USBFS_MOUNT_NAME "usbhdd"
|
||||||
|
|
||||||
|
#define USBFS_UNMOUNTED 0
|
||||||
|
#define USBFS_MOUNTED 1
|
||||||
|
#define USBFS_UNSUPPORTED_FS 2
|
||||||
|
|
||||||
|
Result usbFsInitialize(void);
|
||||||
|
void usbFsExit(void);
|
||||||
|
|
||||||
|
Result usbFsGetMountStatus(u64 *status);
|
||||||
|
Result usbFsOpenFile(u64 *fileid, const char *filepath, u64 mode);
|
||||||
|
Result usbFsCloseFile(u64 fileid);
|
||||||
|
Result usbFsReadFile(u64 fileid, void *buffer, size_t size, size_t *retsize);
|
||||||
|
Result usbFsIsReady();
|
||||||
|
Result usbFsOpenDir(u64 *dirid, const char *dirpath);
|
||||||
|
Result usbFsCloseDir(u64 dirid);
|
||||||
|
Result usbFsReadDir(u64 dirid, u64 *type, u64 *size, char *name, size_t namemax);
|
||||||
|
Result usbFsCreateDir(const char *dirpath);
|
||||||
|
Result usbFsSeekFile(u64 fileid, u64 pos, u64 whence, u64 *retpos);
|
||||||
|
Result usbFsReadRaw(u64 sector, u64 sectorcount, void *buffer);
|
||||||
|
Result usbFsWriteFile(u64 fileid, const void *buffer, size_t size, size_t *retsize);
|
||||||
|
Result usbFsSyncFile(u64 fileid);
|
||||||
|
Result usbFsDeleteDir(const char *dirpath);
|
||||||
|
Result usbFsDeleteFile(const char *filepath);
|
||||||
|
Result usbFsTruncateFile(u64 fileid, u64 size);
|
||||||
|
Result usbFsStatFile(u64 fileid, u64 *size, u64 *mode);
|
||||||
|
Result usbFsStatPath(const char *path, u64 *size, u64 *mode);
|
||||||
|
Result usbFsStatFilesystem(u64 *totalsize, u64 *freesize);
|
||||||
|
|
||||||
|
#endif /* __USBFS_H__ */
|
593
include/libusbhsfs/source/sxos/usbfs_dev.c
Normal file
593
include/libusbhsfs/source/sxos/usbfs_dev.c
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
/*
|
||||||
|
* usbfs_dev.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2018, Team Xecuter.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "../usbhsfs_utils.h"
|
||||||
|
#include "usbfs_dev.h"
|
||||||
|
|
||||||
|
/* Helper macros. */
|
||||||
|
|
||||||
|
#define usbfs_end goto end
|
||||||
|
#define usbfs_ended_with_error (_errno != 0)
|
||||||
|
#define usbfs_set_error(x) r->_errno = _errno = (x)
|
||||||
|
#define usbfs_set_error_and_exit(x) \
|
||||||
|
do { \
|
||||||
|
usbfs_set_error((x)); \
|
||||||
|
usbfs_end; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define usbfs_declare_error_state int _errno = 0
|
||||||
|
#define usbfs_declare_file_state usbfsdev_file *file = (usbfsdev_file*)fd
|
||||||
|
#define usbfs_declare_dir_state usbfsdev_dir *dir = (usbfsdev_dir*)dirState->dirStruct
|
||||||
|
|
||||||
|
#define usbfs_return(x) return (usbfs_ended_with_error ? -1 : (x))
|
||||||
|
#define usbfs_return_ptr(x) return (usbfs_ended_with_error ? NULL : (x))
|
||||||
|
|
||||||
|
/* Type definitions. */
|
||||||
|
|
||||||
|
/// usbfs file state.
|
||||||
|
typedef struct {
|
||||||
|
uint64_t fileid;
|
||||||
|
int flags;
|
||||||
|
} usbfsdev_file;
|
||||||
|
|
||||||
|
/// usbfs directory state.
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint64_t dirid;
|
||||||
|
} usbfsdev_dir;
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static int usbfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode);
|
||||||
|
static int usbfsdev_close(struct _reent *r, void *fd);
|
||||||
|
static ssize_t usbfsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
|
||||||
|
static ssize_t usbfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
|
||||||
|
static off_t usbfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
|
||||||
|
static int usbfsdev_fstat(struct _reent *r, void *fd, struct stat *st);
|
||||||
|
static int usbfsdev_stat(struct _reent *r, const char *file, struct stat *st);
|
||||||
|
static int usbfsdev_link(struct _reent *r, const char *existing, const char *newLink);
|
||||||
|
static int usbfsdev_unlink(struct _reent *r, const char *name);
|
||||||
|
static int usbfsdev_chdir(struct _reent *r, const char *name);
|
||||||
|
static int usbfsdev_rename(struct _reent *r, const char *oldName, const char *newName);
|
||||||
|
static int usbfsdev_mkdir(struct _reent *r, const char *path, int mode);
|
||||||
|
static DIR_ITER* usbfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
|
||||||
|
static int usbfsdev_dirreset(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int usbfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
|
||||||
|
static int usbfsdev_dirclose(struct _reent *r, DIR_ITER *dirState);
|
||||||
|
static int usbfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
|
||||||
|
static int usbfsdev_ftruncate(struct _reent *r, void *fd, off_t len);
|
||||||
|
static int usbfsdev_fsync(struct _reent *r, void *fd);
|
||||||
|
static int usbfsdev_chmod(struct _reent *r, const char *path, mode_t mode);
|
||||||
|
static int usbfsdev_fchmod(struct _reent *r, void *fd, mode_t mode);
|
||||||
|
static int usbfsdev_rmdir(struct _reent *r, const char *name);
|
||||||
|
static int usbfsdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]);
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static const devoptab_t usbfsdev_devoptab = {
|
||||||
|
.name = USBFS_MOUNT_NAME,
|
||||||
|
.structSize = sizeof(usbfsdev_file),
|
||||||
|
.open_r = usbfsdev_open,
|
||||||
|
.close_r = usbfsdev_close,
|
||||||
|
.write_r = usbfsdev_write,
|
||||||
|
.read_r = usbfsdev_read,
|
||||||
|
.seek_r = usbfsdev_seek,
|
||||||
|
.fstat_r = usbfsdev_fstat,
|
||||||
|
.stat_r = usbfsdev_stat,
|
||||||
|
.link_r = usbfsdev_link, ///< Not supported by usbfs.
|
||||||
|
.unlink_r = usbfsdev_unlink,
|
||||||
|
.chdir_r = usbfsdev_chdir, ///< Not supported by usbfs.
|
||||||
|
.rename_r = usbfsdev_rename, ///< Not supported by usbfs.
|
||||||
|
.mkdir_r = usbfsdev_mkdir,
|
||||||
|
.dirStateSize = sizeof(usbfsdev_dir),
|
||||||
|
.diropen_r = usbfsdev_diropen,
|
||||||
|
.dirreset_r = usbfsdev_dirreset, ///< Not supported by usbfs.
|
||||||
|
.dirnext_r = usbfsdev_dirnext,
|
||||||
|
.dirclose_r = usbfsdev_dirclose,
|
||||||
|
.statvfs_r = usbfsdev_statvfs,
|
||||||
|
.ftruncate_r = usbfsdev_ftruncate,
|
||||||
|
.fsync_r = usbfsdev_fsync,
|
||||||
|
.deviceData = NULL,
|
||||||
|
.chmod_r = usbfsdev_chmod, ///< Not supported by usbfs.
|
||||||
|
.fchmod_r = usbfsdev_fchmod, ///< Not supported by usbfs.
|
||||||
|
.rmdir_r = usbfsdev_rmdir,
|
||||||
|
.lstat_r = usbfsdev_stat, ///< Symlinks aren't supported, so we'll just alias lstat() to stat().
|
||||||
|
.utimes_r = usbfsdev_utimes ///< Not supported by usbfs.
|
||||||
|
};
|
||||||
|
|
||||||
|
bool usbfsdev_register(void)
|
||||||
|
{
|
||||||
|
int device_idx = -1;
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
/* Get devoptab device index for our filesystem. */
|
||||||
|
device_idx = FindDevice(USBFS_MOUNT_NAME ":");
|
||||||
|
if (device_idx < 0)
|
||||||
|
{
|
||||||
|
/* Device not available. Let's add it. */
|
||||||
|
device_idx = AddDevice(&usbfsdev_devoptab);
|
||||||
|
if (device_idx < 0)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to add devoptab device for \"" USBFS_MOUNT_NAME "\"!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbfsdev_unregister(void)
|
||||||
|
{
|
||||||
|
RemoveDevice(USBFS_MOUNT_NAME ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !path || !*path || !(path_start = strchr(path, ':')) || !*(++path_start)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Reset file state. */
|
||||||
|
memset(file, 0, sizeof(usbfsdev_file));
|
||||||
|
file->flags = flags;
|
||||||
|
|
||||||
|
/* Open file. */
|
||||||
|
rc = usbFsOpenFile(&(file->fileid), path_start, (u64)flags);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(ENOENT);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_close(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Close file. */
|
||||||
|
rc = usbFsCloseFile(file->fileid);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Invalidate file ID. */
|
||||||
|
file->fileid = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t usbfsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 pos = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with write access. */
|
||||||
|
if ((file->flags & O_ACCMODE) == O_RDONLY) usbfs_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
/* Check if the append flag is enabled. */
|
||||||
|
if (file->flags & O_APPEND)
|
||||||
|
{
|
||||||
|
/* Seek to EOF. */
|
||||||
|
rc = usbFsSeekFile(file->fileid, 0, SEEK_END, &pos);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write file data. */
|
||||||
|
rc = usbFsWriteFile(file->fileid, ptr, len, (size_t*)&pos);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return((ssize_t)pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t usbfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
size_t rd_sz = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !ptr || !len) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with read access. */
|
||||||
|
if ((file->flags & O_ACCMODE) == O_WRONLY) usbfs_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
/* Read file data. */
|
||||||
|
rc = usbFsReadFile(file->fileid, ptr, len, &rd_sz);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return((ssize_t)rd_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static off_t usbfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 outpos = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Perform file seek. */
|
||||||
|
rc = usbFsSeekFile(file->fileid, (u64)pos, (u64)dir, &outpos);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return((off_t)outpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_fstat(struct _reent *r, void *fd, struct stat *st)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 size = 0, mode = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !st) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Get file stats. */
|
||||||
|
rc = usbFsStatFile(file->fileid, &size, &mode);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
memset(st, 0, sizeof(struct stat));
|
||||||
|
st->st_nlink = 1;
|
||||||
|
st->st_size = (off_t)size;
|
||||||
|
st->st_mode = (mode_t)mode;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_stat(struct _reent *r, const char *file, struct stat *st)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 size = 0, mode = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || !*file || !(path_start = strchr(file, ':')) || !*(++path_start) || !st) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Get file stats. */
|
||||||
|
rc = usbFsStatPath(path_start, &size, &mode);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
memset(st, 0, sizeof(struct stat));
|
||||||
|
st->st_nlink = 1;
|
||||||
|
st->st_size = (off_t)size;
|
||||||
|
st->st_mode = (mode_t)mode;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_link(struct _reent *r, const char *existing, const char *newLink)
|
||||||
|
{
|
||||||
|
(void)existing;
|
||||||
|
(void)newLink;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_unlink(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!name || !*name || !(path_start = strchr(name, ':')) || !*(++path_start)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Delete file. */
|
||||||
|
rc = usbFsDeleteFile(path_start);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_chdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
(void)name;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_rename(struct _reent *r, const char *oldName, const char *newName)
|
||||||
|
{
|
||||||
|
(void)oldName;
|
||||||
|
(void)newName;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_mkdir(struct _reent *r, const char *path, int mode)
|
||||||
|
{
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!path || !*path || !(path_start = strchr(path, ':')) || !*(++path_start)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Create directory. */
|
||||||
|
rc = usbFsCreateDir(path_start);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DIR_ITER *usbfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
DIR_ITER *ret = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState || !path || !*path || !(path_start = strchr(path, ':')) || !*(++path_start)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
usbfs_declare_dir_state;
|
||||||
|
|
||||||
|
/* Reset directory state. */
|
||||||
|
memset(dir, 0, sizeof(usbfsdev_dir));
|
||||||
|
|
||||||
|
/* Open directory. */
|
||||||
|
rc = usbFsOpenDir(&(dir->dirid), path_start);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = dirState;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return_ptr(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_dirreset(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
(void)dirState;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u64 type = 0, size = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState || !filename || !filestat) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
usbfs_declare_dir_state;
|
||||||
|
|
||||||
|
/* Clear filename buffer. */
|
||||||
|
memset(filename, 0, NAME_MAX);
|
||||||
|
|
||||||
|
/* Read directory. */
|
||||||
|
rc = usbFsReadDir(dir->dirid, &type, &size, filename, NAME_MAX);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(rc == 0x68A ? ENOENT : EINVAL); /* ENOENT signals EOD. */
|
||||||
|
|
||||||
|
/* Fill stat info. */
|
||||||
|
filestat->st_ino = 0;
|
||||||
|
filestat->st_mode = (mode_t)type;
|
||||||
|
filestat->st_size = (off_t)size;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_dirclose(struct _reent *r, DIR_ITER *dirState)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!dirState) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
usbfs_declare_dir_state;
|
||||||
|
|
||||||
|
/* Close directory. */
|
||||||
|
rc = usbFsCloseDir(dir->dirid);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf)
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
u64 freespace = 0, totalspace = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!buf) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Get volume information. */
|
||||||
|
rc = usbFsStatFilesystem(&totalspace, &freespace);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Fill filesystem stats. */
|
||||||
|
memset(buf, 0, sizeof(struct statvfs));
|
||||||
|
|
||||||
|
buf->f_bsize = 1;
|
||||||
|
buf->f_frsize = 1;
|
||||||
|
buf->f_blocks = totalspace;
|
||||||
|
buf->f_bfree = freespace;
|
||||||
|
buf->f_bavail = freespace;
|
||||||
|
buf->f_files = 0;
|
||||||
|
buf->f_ffree = 0;
|
||||||
|
buf->f_favail = 0;
|
||||||
|
buf->f_fsid = 0;
|
||||||
|
buf->f_flag = ST_NOSUID;
|
||||||
|
buf->f_namemax = 0;
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_ftruncate(struct _reent *r, void *fd, off_t len)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file || len < 0) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Check if the file was opened with write access. */
|
||||||
|
if ((file->flags & O_ACCMODE) == O_RDONLY) usbfs_set_error_and_exit(EBADF);
|
||||||
|
|
||||||
|
/* Truncate file. */
|
||||||
|
rc = usbFsTruncateFile(file->fileid, (u64)len);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_fsync(struct _reent *r, void *fd)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
usbfs_declare_file_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!file) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Synchronize file. */
|
||||||
|
rc = usbFsSyncFile(file->fileid);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_chmod(struct _reent *r, const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
(void)path;
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_fchmod(struct _reent *r, void *fd, mode_t mode)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
(void)mode;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_rmdir(struct _reent *r, const char *name)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
char *path_start = NULL;
|
||||||
|
|
||||||
|
usbfs_declare_error_state;
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
if (!name || !*name || !(path_start = strchr(name, ':')) || !*(++path_start)) usbfs_set_error_and_exit(EINVAL);
|
||||||
|
|
||||||
|
/* Delete directory. */
|
||||||
|
rc = usbFsDeleteDir(path_start);
|
||||||
|
if (R_FAILED(rc)) usbfs_set_error(EINVAL);
|
||||||
|
|
||||||
|
end:
|
||||||
|
usbfs_return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usbfsdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
(void)filename;
|
||||||
|
(void)times;
|
||||||
|
|
||||||
|
/* Not supported by usbfs. */
|
||||||
|
r->_errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
}
|
39
include/libusbhsfs/source/sxos/usbfs_dev.h
Normal file
39
include/libusbhsfs/source/sxos/usbfs_dev.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* usbfs_dev.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2018, Team Xecuter.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __USBFS_DEV_H__
|
||||||
|
#define __USBFS_DEV_H__
|
||||||
|
|
||||||
|
#include "usbfs.h"
|
||||||
|
|
||||||
|
/// Register "usbhdd" devoptab device.
|
||||||
|
bool usbfsdev_register(void);
|
||||||
|
|
||||||
|
/// Unregister "usbhdd" devoptab device.
|
||||||
|
void usbfsdev_unregister(void);
|
||||||
|
|
||||||
|
#endif /* __USBFS_DEV_H__ */
|
79
include/libusbhsfs/source/usb_common.h
Normal file
79
include/libusbhsfs/source/usb_common.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* usb_common.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USB_COMMON_H__
|
||||||
|
#define __USB_COMMON_H__
|
||||||
|
|
||||||
|
#define USB_SUBCLASS_SCSI_TRANSPARENT_CMD_SET 0x06
|
||||||
|
|
||||||
|
#define USB_PROTOCOL_BULK_ONLY_TRANSPORT 0x50
|
||||||
|
#define USB_PROTOCOL_USB_ATTACHED_SCSI 0x62
|
||||||
|
|
||||||
|
#define USB_XFER_BUF_ALIGNMENT 0x1000 /* 4 KiB. */
|
||||||
|
#define USB_XFER_BUF_SIZE 0x800000 /* 8 MiB. */
|
||||||
|
|
||||||
|
#define USB_FEATURE_ENDPOINT_HALT 0x00
|
||||||
|
|
||||||
|
#define USB_DT_PIPE_USAGE 0x24
|
||||||
|
|
||||||
|
#define USB_DT_STRING_MAXLEN 0x7E
|
||||||
|
|
||||||
|
#define USB_LANGID_ENUS 0x0409
|
||||||
|
|
||||||
|
#define UMS_MAX_LUN 16 /* Max returned value is actually a zero-based index to the highest LUN. */
|
||||||
|
|
||||||
|
#define MOUNT_NAME_LENGTH 32
|
||||||
|
#define MAX_PATH_LENGTH (FS_MAX_PATH + 1)
|
||||||
|
|
||||||
|
#define BLKDEV_MIN_BLOCK_SIZE 512
|
||||||
|
#define BLKDEV_MAX_BLOCK_SIZE 4096
|
||||||
|
|
||||||
|
/// Structs imported from libusb, with some adjustments.
|
||||||
|
|
||||||
|
struct _usb_string_descriptor {
|
||||||
|
u8 bLength;
|
||||||
|
u8 bDescriptorType; ///< Must match USB_DT_STRING.
|
||||||
|
u16 wData[USB_DT_STRING_MAXLEN];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum usb_pipe_usage_id {
|
||||||
|
USB_PIPE_USAGE_ID_CMD = 0x01, ///< Command pipe.
|
||||||
|
USB_PIPE_USAGE_ID_STS = 0x02, ///< Status pipe.
|
||||||
|
USB_PIPE_USAGE_ID_DATA_IN = 0x03, ///< Data In pipe.
|
||||||
|
USB_PIPE_USAGE_ID_DATA_OUT = 0x04 ///< Data Out pipe.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_pipe_usage_descriptor {
|
||||||
|
u8 bLength;
|
||||||
|
u8 bDescriptorType; ///< Must match USB_DT_PIPE_USAGE.
|
||||||
|
u8 bPipeID; ///< usb_pipe_usage_id.
|
||||||
|
u8 Reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum usb_request_type {
|
||||||
|
USB_REQUEST_TYPE_STANDARD = (0x00 << 5),
|
||||||
|
USB_REQUEST_TYPE_CLASS = (0x01 << 5),
|
||||||
|
USB_REQUEST_TYPE_VENDOR = (0x02 << 5),
|
||||||
|
USB_REQUEST_TYPE_RESERVED = (0x03 << 5),
|
||||||
|
};
|
||||||
|
|
||||||
|
enum usb_request_recipient {
|
||||||
|
USB_RECIPIENT_DEVICE = 0x00,
|
||||||
|
USB_RECIPIENT_INTERFACE = 0x01,
|
||||||
|
USB_RECIPIENT_ENDPOINT = 0x02,
|
||||||
|
USB_RECIPIENT_OTHER = 0x03,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum usb_request_bot {
|
||||||
|
USB_REQUEST_BOT_GET_MAX_LUN = 0xFE,
|
||||||
|
USB_REQUEST_BOT_RESET = 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __USB_COMMON_H__ */
|
692
include/libusbhsfs/source/usbhsfs_drive.c
Normal file
692
include/libusbhsfs/source/usbhsfs_drive.c
Normal file
@ -0,0 +1,692 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_drive.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "usbhsfs_utils.h"
|
||||||
|
#include "usbhsfs_request.h"
|
||||||
|
#include "usbhsfs_scsi.h"
|
||||||
|
#include "usbhsfs_mount.h"
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static bool usbHsFsDriveSetupInterfaceAndEndpointDescriptors(UsbHsFsDriveContext *drive_ctx);
|
||||||
|
static bool usbHsFsDriveChangeInterfaceDescriptor(UsbHsClientIfSession *usb_if_session, const struct usb_interface_descriptor *interface_desc);
|
||||||
|
static bool usbHsFsDriveSetupEndpointDescriptors(UsbHsFsDriveContext *drive_ctx, u8 *config_desc_start, u8 *config_desc_end, u8 **config_desc_ptr);
|
||||||
|
static bool usbHsFsDriveGetEndpointSession(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool input, u8 ep_addr);
|
||||||
|
|
||||||
|
static void usbHsFsDriveGetDeviceStrings(UsbHsFsDriveContext *drive_ctx);
|
||||||
|
static void usbHsFsDriveGetUtf8StringFromStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, char **out_buf);
|
||||||
|
|
||||||
|
static void usbHsFsDriveDestroyLogicalUnitContext(UsbHsFsDriveLogicalUnitContext *lun_ctx, bool stop_lun);
|
||||||
|
|
||||||
|
bool usbHsFsDriveInitializeContext(UsbHsFsDriveContext *drive_ctx, UsbHsInterface *usb_if)
|
||||||
|
{
|
||||||
|
if (!drive_ctx || !usb_if)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
||||||
|
UsbHsFsDriveLogicalUnitContext **tmp_lun_ctx = NULL;
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
/* Allocate memory for the USB transfer buffer. */
|
||||||
|
drive_ctx->xfer_buf = usbHsFsRequestAllocateXferBuffer();
|
||||||
|
if (!drive_ctx->xfer_buf)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate USB transfer buffer! (interface %d).", usb_if->inf.ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open current interface. */
|
||||||
|
rc = usbHsAcquireUsbIf(usb_if_session, usb_if);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsAcquireUsbIf failed! (0x%08X) (interface %d).", rc, usb_if->inf.ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup interface and endpoint descriptors. */
|
||||||
|
if (!usbHsFsDriveSetupInterfaceAndEndpointDescriptors(drive_ctx))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to setup interface and endpoint descriptors! (interface %d).", usb_if->inf.ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill extra device data. */
|
||||||
|
drive_ctx->usb_if_id = usb_if_session->ID;
|
||||||
|
drive_ctx->uasp = (usb_if_session->inf.inf.interface_desc.bInterfaceProtocol == USB_PROTOCOL_USB_ATTACHED_SCSI);
|
||||||
|
drive_ctx->vid = usb_if_session->inf.device_desc.idVendor;
|
||||||
|
drive_ctx->pid = usb_if_session->inf.device_desc.idProduct;
|
||||||
|
usbHsFsDriveGetDeviceStrings(drive_ctx);
|
||||||
|
|
||||||
|
/* Retrieve max supported logical units from this storage device. */
|
||||||
|
rc = usbHsFsRequestGetMaxLogicalUnits(usb_if_session, &(drive_ctx->max_lun));
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
/* Fallback to a single logical unit. */
|
||||||
|
drive_ctx->max_lun = 1;
|
||||||
|
|
||||||
|
/* If the request fails (e.g. unsupported by the device), we'll attempt to clear a possible STALL status from the endpoints. */
|
||||||
|
if (R_MODULE(rc) != Module_Libnx) usbHsFsDriveClearStallStatus(drive_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Max LUN count (interface %d): %u.", drive_ctx->usb_if_id, drive_ctx->max_lun);
|
||||||
|
|
||||||
|
/* Bail out if we're dealing with a UASP interface (for now). */
|
||||||
|
if (drive_ctx->uasp) goto end;
|
||||||
|
|
||||||
|
/* Allocate memory for LUN context pointer array. */
|
||||||
|
drive_ctx->lun_ctx = calloc(drive_ctx->max_lun, sizeof(UsbHsFsDriveLogicalUnitContext*));
|
||||||
|
if (!drive_ctx->lun_ctx)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory for LUN context pointer array! (interface %d).", drive_ctx->usb_if_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare LUNs using SCSI commands. */
|
||||||
|
for(u8 i = 0; i < drive_ctx->max_lun; i++)
|
||||||
|
{
|
||||||
|
/* Retrieve pointer to the current LUN context. */
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[drive_ctx->lun_count];
|
||||||
|
if (!lun_ctx)
|
||||||
|
{
|
||||||
|
/* Allocate memory for a new LUN context. */
|
||||||
|
lun_ctx = calloc(1, sizeof(UsbHsFsDriveLogicalUnitContext));
|
||||||
|
if (!lun_ctx)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory for LUN context entry #%u! (%u / %u).", drive_ctx->lun_count, i + 1, drive_ctx->max_lun);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set LUN context entry pointer. */
|
||||||
|
drive_ctx->lun_ctx[drive_ctx->lun_count] = lun_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increase LUN context count. */
|
||||||
|
(drive_ctx->lun_count)++;
|
||||||
|
|
||||||
|
/* Set USB interface ID and LUN index. */
|
||||||
|
lun_ctx->drive_ctx = drive_ctx;
|
||||||
|
lun_ctx->usb_if_id = drive_ctx->usb_if_id;
|
||||||
|
lun_ctx->uasp = drive_ctx->uasp;
|
||||||
|
lun_ctx->lun = i;
|
||||||
|
|
||||||
|
/* Start LUN. */
|
||||||
|
if (!usbHsFsScsiStartDriveLogicalUnit(lun_ctx))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to initialize context for LUN #%u! (interface %d).", i, drive_ctx->usb_if_id);
|
||||||
|
(drive_ctx->lun_count)--; /* Decrease LUN context count. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize filesystem contexts for this LUN. */
|
||||||
|
if (!usbHsFsMountInitializeLogicalUnitFileSystemContexts(lun_ctx))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to initialize filesystem contexts for LUN #%u! (interface %d).", i, drive_ctx->usb_if_id);
|
||||||
|
usbHsFsDriveDestroyLogicalUnitContext(lun_ctx, true);
|
||||||
|
(drive_ctx->lun_count)--; /* Decrease LUN context count. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drive_ctx->lun_count)
|
||||||
|
{
|
||||||
|
if (drive_ctx->lun_ctx[0]) free(drive_ctx->lun_ctx[0]);
|
||||||
|
USBHSFS_LOG_MSG("Failed to initialize any LUN/filesystem contexts! (interface %d).", drive_ctx->usb_if_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reallocate LUN context pointer array if we're not using all allocated entries. */
|
||||||
|
if (drive_ctx->lun_count < drive_ctx->max_lun)
|
||||||
|
{
|
||||||
|
tmp_lun_ctx = realloc(drive_ctx->lun_ctx, drive_ctx->lun_count * sizeof(UsbHsFsDriveLogicalUnitContext*));
|
||||||
|
if (tmp_lun_ctx)
|
||||||
|
{
|
||||||
|
drive_ctx->lun_ctx = tmp_lun_ctx;
|
||||||
|
tmp_lun_ctx = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update return value. */
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
/* Destroy drive context if something went wrong. */
|
||||||
|
if (!ret) usbHsFsDriveDestroyContext(drive_ctx, true);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbHsFsDriveDestroyContext(UsbHsFsDriveContext *drive_ctx, bool stop_lun)
|
||||||
|
{
|
||||||
|
if (!drive_ctx) return;
|
||||||
|
|
||||||
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
||||||
|
|
||||||
|
UsbHsClientEpSession *usb_in_ep_session_1 = &(drive_ctx->usb_in_ep_session[0]);
|
||||||
|
UsbHsClientEpSession *usb_out_ep_session_1 = &(drive_ctx->usb_out_ep_session[0]);
|
||||||
|
|
||||||
|
UsbHsClientEpSession *usb_in_ep_session_2 = &(drive_ctx->usb_in_ep_session[1]);
|
||||||
|
UsbHsClientEpSession *usb_out_ep_session_2 = &(drive_ctx->usb_out_ep_session[1]);
|
||||||
|
|
||||||
|
if (drive_ctx->lun_ctx)
|
||||||
|
{
|
||||||
|
/* Destroy LUN contexts. */
|
||||||
|
for(u8 i = 0; i < drive_ctx->lun_count; i++)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[i];
|
||||||
|
if (!lun_ctx) continue;
|
||||||
|
|
||||||
|
usbHsFsDriveDestroyLogicalUnitContext(lun_ctx, stop_lun);
|
||||||
|
free(lun_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free LUN context pointer array. */
|
||||||
|
free(drive_ctx->lun_ctx);
|
||||||
|
drive_ctx->lun_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free device strings. */
|
||||||
|
if (drive_ctx->manufacturer)
|
||||||
|
{
|
||||||
|
free(drive_ctx->manufacturer);
|
||||||
|
drive_ctx->manufacturer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drive_ctx->product_name)
|
||||||
|
{
|
||||||
|
free(drive_ctx->product_name);
|
||||||
|
drive_ctx->product_name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drive_ctx->serial_number)
|
||||||
|
{
|
||||||
|
free(drive_ctx->serial_number);
|
||||||
|
drive_ctx->serial_number = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close USB interface and endpoint sessions. */
|
||||||
|
if (serviceIsActive(&(usb_out_ep_session_2->s))) usbHsEpClose(usb_out_ep_session_2);
|
||||||
|
if (serviceIsActive(&(usb_in_ep_session_2->s))) usbHsEpClose(usb_in_ep_session_2);
|
||||||
|
|
||||||
|
if (serviceIsActive(&(usb_out_ep_session_1->s))) usbHsEpClose(usb_out_ep_session_1);
|
||||||
|
if (serviceIsActive(&(usb_in_ep_session_1->s))) usbHsEpClose(usb_in_ep_session_1);
|
||||||
|
|
||||||
|
if (usbHsIfIsActive(usb_if_session)) usbHsIfClose(usb_if_session);
|
||||||
|
|
||||||
|
/* Free dedicated USB transfer buffer. */
|
||||||
|
if (drive_ctx->xfer_buf)
|
||||||
|
{
|
||||||
|
free(drive_ctx->xfer_buf);
|
||||||
|
drive_ctx->xfer_buf = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbHsFsDriveClearStallStatus(UsbHsFsDriveContext *drive_ctx)
|
||||||
|
{
|
||||||
|
if (!usbHsFsDriveIsValidContext(drive_ctx)) return;
|
||||||
|
|
||||||
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_in_ep_session[0]));
|
||||||
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_out_ep_session[0]));
|
||||||
|
|
||||||
|
if (drive_ctx->uasp)
|
||||||
|
{
|
||||||
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_in_ep_session[1]));
|
||||||
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_out_ep_session[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsDriveSetupInterfaceAndEndpointDescriptors(UsbHsFsDriveContext *drive_ctx)
|
||||||
|
{
|
||||||
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session))) return false;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
||||||
|
|
||||||
|
struct usb_interface_descriptor orig_interface_desc = {0}, *new_interface_desc = NULL;
|
||||||
|
|
||||||
|
u32 config_desc_size = 0;
|
||||||
|
u8 *config_desc_start = NULL, *config_desc_end = NULL, *config_desc_ptr = NULL;
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
/* Copy interface descriptor provided by UsbHsInterface. */
|
||||||
|
/* We'll use this to skip this descriptor when we find it within the full configuration descriptor. */
|
||||||
|
/* Furthermore, we'll use it as a fallback method if something goes wrong while setting up USB Attached SCSI interface and endpoint descriptors. */
|
||||||
|
memcpy(&orig_interface_desc, &(usb_if_session->inf.inf.interface_desc), sizeof(struct usb_interface_descriptor));
|
||||||
|
|
||||||
|
/* Get full configuration descriptor. The one provided by UsbHsInterface is truncated. */
|
||||||
|
/* To simplify things, we won't go beyond index #0. */
|
||||||
|
rc = usbHsFsRequestGetConfigurationDescriptor(usb_if_session, 0, &config_desc_start, &config_desc_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsFsRequestGetConfigurationDescriptor failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do not proceed if the configuration descriptor is too small to hold a USB Attached SCSI interface. */
|
||||||
|
/* SuperSpeed endpoint companion descriptors aren't part of the math because we could be dealing with UASP over USB2. */
|
||||||
|
if (config_desc_size < (sizeof(struct usb_config_descriptor) + sizeof(struct usb_interface_descriptor) + ((sizeof(struct usb_endpoint_descriptor) + sizeof(struct usb_pipe_usage_descriptor)) * 4)))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Configuration descriptor is too small to hold a UASP interface (interface %d).", usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update configuration descriptor pointers. */
|
||||||
|
config_desc_end = (config_desc_start + config_desc_size);
|
||||||
|
config_desc_ptr = config_desc_start;
|
||||||
|
|
||||||
|
/* Parse configuration descriptor. */
|
||||||
|
while(config_desc_ptr < config_desc_end)
|
||||||
|
{
|
||||||
|
u32 config_desc_offset = (u32)(config_desc_ptr - config_desc_start);
|
||||||
|
|
||||||
|
/* If somehow we're only a byte away from the end of the configuration descriptor, bail out. */
|
||||||
|
if ((config_desc_ptr + 1) >= config_desc_end) break;
|
||||||
|
|
||||||
|
/* Get descriptor size and type. */
|
||||||
|
u8 cur_desc_size = *config_desc_ptr;
|
||||||
|
u8 cur_desc_type = *(config_desc_ptr + 1);
|
||||||
|
|
||||||
|
/* Check descriptor size. */
|
||||||
|
if (!cur_desc_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Size for descriptor 0x%02X at offset 0x%X is zero! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config_desc_offset + cur_desc_size) > config_desc_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Descriptor 0x%02X at offset 0x%X exceeds configuration descriptor size! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we're dealing with a valid USB Attached SCSI interface descriptor. */
|
||||||
|
if (cur_desc_type != USB_DT_INTERFACE || cur_desc_size != sizeof(struct usb_interface_descriptor) || \
|
||||||
|
(orig_interface_desc.bInterfaceProtocol != USB_PROTOCOL_USB_ATTACHED_SCSI && !memcmp(config_desc_ptr, &orig_interface_desc, sizeof(struct usb_interface_descriptor))) || \
|
||||||
|
*(config_desc_ptr + 7) != USB_PROTOCOL_USB_ATTACHED_SCSI || *(config_desc_ptr + 4) != 4)
|
||||||
|
{
|
||||||
|
config_desc_ptr += cur_desc_size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Found a USB Attached SCSI descriptor. */
|
||||||
|
new_interface_desc = (struct usb_interface_descriptor*)config_desc_ptr;
|
||||||
|
USBHSFS_LOG_DATA(new_interface_desc, sizeof(struct usb_interface_descriptor), "Found UASP interface descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
||||||
|
|
||||||
|
/* Update configuration descriptor pointer. */
|
||||||
|
config_desc_ptr += cur_desc_size;
|
||||||
|
|
||||||
|
/* Switch to the new interface descriptor. */
|
||||||
|
if (!usbHsFsDriveChangeInterfaceDescriptor(usb_if_session, new_interface_desc)) continue;
|
||||||
|
|
||||||
|
/* Setup endpoint descriptors. */
|
||||||
|
success = usbHsFsDriveSetupEndpointDescriptors(drive_ctx, config_desc_start, config_desc_end, &config_desc_ptr);
|
||||||
|
if (success) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) USBHSFS_LOG_MSG("Unable to find and/or set a UASP interface descriptor (interface %d).", usb_if_session->ID);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (config_desc_start) free(config_desc_start);
|
||||||
|
|
||||||
|
/* Fallback to the Bulk-Only Transport interface if we must. */
|
||||||
|
if (!success && orig_interface_desc.bInterfaceProtocol == USB_PROTOCOL_BULK_ONLY_TRANSPORT && usbHsFsDriveChangeInterfaceDescriptor(usb_if_session, &orig_interface_desc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Proceeding with BOT interface descriptor (interface %d).", usb_if_session->ID);
|
||||||
|
success = usbHsFsDriveSetupEndpointDescriptors(drive_ctx, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsDriveChangeInterfaceDescriptor(UsbHsClientIfSession *usb_if_session, const struct usb_interface_descriptor *interface_desc)
|
||||||
|
{
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !interface_desc) return false;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
u8 cur_if_num = usb_if_session->inf.inf.interface_desc.bInterfaceNumber;
|
||||||
|
u8 cur_if_alt_setting = usb_if_session->inf.inf.interface_desc.bAlternateSetting;
|
||||||
|
|
||||||
|
u8 new_if_num = interface_desc->bInterfaceNumber;
|
||||||
|
u8 new_if_alt_setting = interface_desc->bAlternateSetting;
|
||||||
|
|
||||||
|
/* Check if we're trying to set the same interface. */
|
||||||
|
if (new_if_num == cur_if_num && new_if_alt_setting == cur_if_alt_setting)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Trying to set an interface descriptor that matches the current one (interface %d).", usb_if_session->ID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we must set a new interface. */
|
||||||
|
if (new_if_num != cur_if_num)
|
||||||
|
{
|
||||||
|
rc = usbHsIfSetInterface(usb_if_session, NULL, new_if_num);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfSetInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we must set an alternate interface setting. */
|
||||||
|
if (new_if_alt_setting != cur_if_alt_setting)
|
||||||
|
{
|
||||||
|
rc = usbHsIfGetAlternateInterface(usb_if_session, NULL, new_if_alt_setting);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfGetAlternateInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_DATA(&(usb_if_session->inf), sizeof(UsbHsInterface), "New interface data (interface %d):", usb_if_session->ID);
|
||||||
|
|
||||||
|
/*rc = usbHsFsRequestSetInterface(usb_if_session);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsFsRequestSetInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
return false;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsDriveSetupEndpointDescriptors(UsbHsFsDriveContext *drive_ctx, u8 *config_desc_start, u8 *config_desc_end, u8 **config_desc_ptr)
|
||||||
|
{
|
||||||
|
UsbHsClientIfSession *usb_if_session = NULL;
|
||||||
|
UsbHsClientEpSession *usb_in_ep_session_1 = NULL, *usb_out_ep_session_1 = NULL, *usb_in_ep_session_2 = NULL, *usb_out_ep_session_2 = NULL;
|
||||||
|
|
||||||
|
u8 *config_desc_ptr_tmp = NULL;
|
||||||
|
u32 config_desc_size = 0;
|
||||||
|
|
||||||
|
struct usb_endpoint_descriptor *ep_desc = NULL;
|
||||||
|
struct usb_pipe_usage_descriptor *pipe_usage_desc = NULL;
|
||||||
|
|
||||||
|
bool success = false, uasp = false;
|
||||||
|
|
||||||
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session)) || ((uasp = (drive_ctx->usb_if_session.inf.inf.interface_desc.bInterfaceProtocol == USB_PROTOCOL_USB_ATTACHED_SCSI)) && \
|
||||||
|
(!config_desc_start || !config_desc_end || !config_desc_ptr || !*config_desc_ptr || config_desc_end <= config_desc_start || *config_desc_ptr >= config_desc_end))) return false;
|
||||||
|
|
||||||
|
usb_if_session = &(drive_ctx->usb_if_session);
|
||||||
|
|
||||||
|
usb_in_ep_session_1 = &(drive_ctx->usb_in_ep_session[0]);
|
||||||
|
usb_out_ep_session_1 = &(drive_ctx->usb_out_ep_session[0]);
|
||||||
|
|
||||||
|
usb_in_ep_session_2 = &(drive_ctx->usb_in_ep_session[1]);
|
||||||
|
usb_out_ep_session_2 = &(drive_ctx->usb_out_ep_session[1]);
|
||||||
|
|
||||||
|
if (!uasp)
|
||||||
|
{
|
||||||
|
/* If we're dealing with a Bulk-Only Transport interface, just setup the endpoints from the interface session right away. */
|
||||||
|
success = (usbHsFsDriveGetEndpointSession(usb_if_session, usb_in_ep_session_1, true, 0) && usbHsFsDriveGetEndpointSession(usb_if_session, usb_out_ep_session_1, false, 0));
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse endpoint and pipe usage descriptors from the configuration descriptor. */
|
||||||
|
config_desc_ptr_tmp = *config_desc_ptr;
|
||||||
|
config_desc_size = (u32)(config_desc_end - config_desc_start);
|
||||||
|
|
||||||
|
while(config_desc_ptr_tmp < config_desc_end)
|
||||||
|
{
|
||||||
|
u32 config_desc_offset = (u32)(config_desc_ptr_tmp - config_desc_start);
|
||||||
|
|
||||||
|
/* If somehow we're only a byte away from the end of the configuration descriptor, bail out. */
|
||||||
|
if ((config_desc_ptr_tmp + 1) >= config_desc_end) break;
|
||||||
|
|
||||||
|
/* Get descriptor size and type. */
|
||||||
|
u8 cur_desc_size = *config_desc_ptr_tmp;
|
||||||
|
u8 cur_desc_type = *(config_desc_ptr_tmp + 1);
|
||||||
|
|
||||||
|
/* Check descriptor size. */
|
||||||
|
if (!cur_desc_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Size for descriptor 0x%02X at offset 0x%X is zero! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((config_desc_offset + cur_desc_size) > config_desc_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Descriptor 0x%02X at offset 0x%X exceeds configuration descriptor size! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we're dealing with an endpoint descriptor or a pipe usage descriptor. */
|
||||||
|
if ((!ep_desc && (cur_desc_type != USB_DT_ENDPOINT || cur_desc_size != sizeof(struct usb_endpoint_descriptor))) || (ep_desc && (cur_desc_type != USB_DT_PIPE_USAGE || \
|
||||||
|
cur_desc_size != sizeof(struct usb_pipe_usage_descriptor))))
|
||||||
|
{
|
||||||
|
config_desc_ptr_tmp += cur_desc_size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur_desc_type == USB_DT_ENDPOINT)
|
||||||
|
{
|
||||||
|
/* Found an endpoint descriptor. */
|
||||||
|
/* Update our current endpoint pointer, then look for its pipe usage descriptor. */
|
||||||
|
ep_desc = (struct usb_endpoint_descriptor*)config_desc_ptr_tmp;
|
||||||
|
USBHSFS_LOG_DATA(ep_desc, sizeof(struct usb_endpoint_descriptor), "Found endpoint descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
||||||
|
|
||||||
|
config_desc_ptr_tmp += cur_desc_size;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Found a pipe usage descriptor. */
|
||||||
|
pipe_usage_desc = (struct usb_pipe_usage_descriptor*)config_desc_ptr_tmp;
|
||||||
|
USBHSFS_LOG_DATA(pipe_usage_desc, sizeof(struct usb_pipe_usage_descriptor), "Found pipe usage descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
||||||
|
|
||||||
|
/* Update configuration descriptor pointer. */
|
||||||
|
config_desc_ptr_tmp += cur_desc_size;
|
||||||
|
|
||||||
|
/* Check if the pipe ID matches the endpoint direction. */
|
||||||
|
u8 ep_addr = ep_desc->bEndpointAddress, pipe_id = pipe_usage_desc->bPipeID;
|
||||||
|
bool input = ((ep_addr & USB_ENDPOINT_IN) != 0);
|
||||||
|
|
||||||
|
if ((input && pipe_id != USB_PIPE_USAGE_ID_STS && pipe_id != USB_PIPE_USAGE_ID_DATA_IN) || (!input && pipe_id != USB_PIPE_USAGE_ID_CMD && pipe_id != USB_PIPE_USAGE_ID_DATA_OUT))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Pipe ID 0x%02X doesn't match direction from endpoint 0x%02X! (interface %d).", pipe_id, ep_addr, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, setup this endpoint. */
|
||||||
|
UsbHsClientEpSession *usb_ep_session = (pipe_id == USB_PIPE_USAGE_ID_CMD ? usb_out_ep_session_1 : (pipe_id == USB_PIPE_USAGE_ID_STS ? usb_in_ep_session_1 : \
|
||||||
|
(pipe_id == USB_PIPE_USAGE_ID_DATA_IN ? usb_in_ep_session_2 : usb_out_ep_session_2)));
|
||||||
|
|
||||||
|
if (!usbHsFsDriveGetEndpointSession(usb_if_session, usb_ep_session, input, ep_addr))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to retrieve endpoint session! (interface %d, endpoint 0x%02X, pipe ID 0x%02X).", usb_if_session->ID, ep_addr, pipe_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear endpoint and pipe usage descriptor pointers. */
|
||||||
|
ep_desc = NULL;
|
||||||
|
pipe_usage_desc = NULL;
|
||||||
|
|
||||||
|
/* Check if we're done here. */
|
||||||
|
if (serviceIsActive(&(usb_in_ep_session_1->s)) && serviceIsActive(&(usb_out_ep_session_1->s)) && serviceIsActive(&(usb_in_ep_session_2->s)) && serviceIsActive(&(usb_out_ep_session_2->s)))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
/* Close opened endpoints if something went wrong. */
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
if (serviceIsActive(&(usb_out_ep_session_2->s))) usbHsEpClose(usb_out_ep_session_2);
|
||||||
|
if (serviceIsActive(&(usb_in_ep_session_2->s))) usbHsEpClose(usb_in_ep_session_2);
|
||||||
|
|
||||||
|
if (serviceIsActive(&(usb_out_ep_session_1->s))) usbHsEpClose(usb_out_ep_session_1);
|
||||||
|
if (serviceIsActive(&(usb_in_ep_session_1->s))) usbHsEpClose(usb_in_ep_session_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update configuration descriptor pointer (if needed). */
|
||||||
|
if (config_desc_ptr_tmp) *config_desc_ptr = config_desc_ptr_tmp;
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsDriveGetEndpointSession(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool input, u8 ep_addr)
|
||||||
|
{
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !usb_ep_session) return false;
|
||||||
|
|
||||||
|
for(u8 i = 0; i < 15; i++)
|
||||||
|
{
|
||||||
|
struct usb_endpoint_descriptor *ep_desc = (input ? &(usb_if_session->inf.inf.input_endpoint_descs[i]) : &(usb_if_session->inf.inf.output_endpoint_descs[i]));
|
||||||
|
|
||||||
|
u8 max_burst = (input ? usb_if_session->inf.inf.input_ss_endpoint_companion_descs[i].bMaxBurst : usb_if_session->inf.inf.output_ss_endpoint_companion_descs[i].bMaxBurst);
|
||||||
|
max_burst++;
|
||||||
|
|
||||||
|
if (ep_desc->bLength && ((!ep_addr && ((input && (ep_desc->bEndpointAddress & USB_ENDPOINT_IN)) || (!input && !(ep_desc->bEndpointAddress & USB_ENDPOINT_IN)))) || \
|
||||||
|
(ep_addr && ep_desc->bEndpointAddress == ep_addr)) && (ep_desc->bmAttributes & USB_TRANSFER_TYPE_MASK) == USB_TRANSFER_TYPE_BULK)
|
||||||
|
{
|
||||||
|
Result rc = usbHsIfOpenUsbEp(usb_if_session, usb_ep_session, 1, ep_desc->wMaxPacketSize, ep_desc);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfOpenUsbEp failed! (0x%08X) (interface %d, endpoint 0x%02X, %u URB(s)).", rc, usb_if_session->ID, ep_desc->bEndpointAddress, max_burst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbHsFsDriveGetDeviceStrings(UsbHsFsDriveContext *drive_ctx)
|
||||||
|
{
|
||||||
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session))) return;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
||||||
|
|
||||||
|
u16 *lang_ids = NULL, cur_lang_id = 0, sel_lang_id = 0;
|
||||||
|
u32 lang_ids_count = 0;
|
||||||
|
|
||||||
|
/* Set default string language ID to English (US). */
|
||||||
|
sel_lang_id = USB_LANGID_ENUS;
|
||||||
|
|
||||||
|
/* Retrieve string descriptor indexes. Bail out if none of them are valid. */
|
||||||
|
u8 manufacturer = usb_if_session->inf.device_desc.iManufacturer;
|
||||||
|
u8 product_name = usb_if_session->inf.device_desc.iProduct;
|
||||||
|
u8 serial_number = usb_if_session->inf.device_desc.iSerialNumber;
|
||||||
|
|
||||||
|
if (!manufacturer && !product_name && !serial_number) return;
|
||||||
|
|
||||||
|
/* Get supported language IDs. */
|
||||||
|
rc = usbHsFsRequestGetStringDescriptor(usb_if_session, 0, 0, &lang_ids, &lang_ids_count);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Unable to retrieve supported language IDs! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if English (US) is supported. Otherwise, just default to the last valid language ID. */
|
||||||
|
lang_ids_count /= sizeof(u16);
|
||||||
|
for(u32 i = 0; i < lang_ids_count; i++)
|
||||||
|
{
|
||||||
|
if (!(cur_lang_id = lang_ids[i])) continue;
|
||||||
|
|
||||||
|
if (cur_lang_id == sel_lang_id) break;
|
||||||
|
|
||||||
|
if ((i + 1) == lang_ids_count)
|
||||||
|
{
|
||||||
|
sel_lang_id = cur_lang_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(lang_ids);
|
||||||
|
|
||||||
|
/* Retrieve string descriptors. */
|
||||||
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, manufacturer, sel_lang_id, &(drive_ctx->manufacturer));
|
||||||
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, product_name, sel_lang_id, &(drive_ctx->product_name));
|
||||||
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, serial_number, sel_lang_id, &(drive_ctx->serial_number));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbHsFsDriveGetUtf8StringFromStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, char **out_buf)
|
||||||
|
{
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !idx || !lang_id || !out_buf) return;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
u16 *string_data = NULL;
|
||||||
|
u32 string_data_size = 0;
|
||||||
|
|
||||||
|
ssize_t units = 0;
|
||||||
|
char *utf8_str = NULL;
|
||||||
|
|
||||||
|
/* Get string descriptor. */
|
||||||
|
rc = usbHsFsRequestGetStringDescriptor(usb_if_session, idx, lang_id, &string_data, &string_data_size);
|
||||||
|
if (R_FAILED(rc)) goto end;
|
||||||
|
|
||||||
|
/* Get UTF-8 string size. */
|
||||||
|
units = utf16_to_utf8(NULL, string_data, 0);
|
||||||
|
if (units <= 0)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to get UTF-8 string size for string descriptor! (interface %d, language ID 0x%04X, index %u).", usb_if_session->ID, lang_id, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the UTF-8 string. */
|
||||||
|
utf8_str = calloc(units + 1, sizeof(char));
|
||||||
|
if (!utf8_str)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%lX byte-long UTF-8 buffer for string descriptor! (interface %d, language ID 0x%04X, index %u).", units + 1, usb_if_session->ID, lang_id, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform UTF-16 to UTF-8 conversion. */
|
||||||
|
units = utf16_to_utf8((u8*)utf8_str, string_data, (size_t)units);
|
||||||
|
if (units <= 0)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("UTF-16 to UTF-8 conversion failed for string descriptor! (interface %d, language ID 0x%04X, index %u).", usb_if_session->ID, lang_id, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_MSG("Converted string (interface %d, language ID 0x%04X, index %u): \"%s\".", usb_if_session->ID, lang_id, idx, utf8_str);
|
||||||
|
|
||||||
|
/* Update output. */
|
||||||
|
*out_buf = utf8_str;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if ((R_FAILED(rc) || units <= 0) && utf8_str) free(utf8_str);
|
||||||
|
|
||||||
|
if (string_data) free(string_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbHsFsDriveDestroyLogicalUnitContext(UsbHsFsDriveLogicalUnitContext *lun_ctx, bool stop_lun)
|
||||||
|
{
|
||||||
|
if (!lun_ctx || !usbHsFsDriveIsValidContext((UsbHsFsDriveContext*)lun_ctx->drive_ctx) || lun_ctx->lun >= UMS_MAX_LUN) return;
|
||||||
|
|
||||||
|
if (lun_ctx->fs_ctx)
|
||||||
|
{
|
||||||
|
/* Destroy filesystem contexts. */
|
||||||
|
for(u32 i = 0; i < lun_ctx->fs_count; i++)
|
||||||
|
{
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = lun_ctx->fs_ctx[i];
|
||||||
|
if (!fs_ctx) continue;
|
||||||
|
|
||||||
|
usbHsFsMountDestroyLogicalUnitFileSystemContext(fs_ctx);
|
||||||
|
free(fs_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free filesystem context pointer array. */
|
||||||
|
free(lun_ctx->fs_ctx);
|
||||||
|
lun_ctx->fs_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop current LUN. */
|
||||||
|
if (stop_lun) usbHsFsScsiStopDriveLogicalUnit(lun_ctx);
|
||||||
|
}
|
148
include/libusbhsfs/source/usbhsfs_drive.h
Normal file
148
include/libusbhsfs/source/usbhsfs_drive.h
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_drive.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
* Copyright (c) 2020-2021, Rhys Koedijk.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_DRIVE_H__
|
||||||
|
#define __USBHSFS_DRIVE_H__
|
||||||
|
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
#include "fatfs/ff.h"
|
||||||
|
|
||||||
|
#ifdef GPL_BUILD
|
||||||
|
#include "ntfs-3g/ntfs.h"
|
||||||
|
#include "lwext4/ext.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Used by filesystem contexts to determine which FS object to use.
|
||||||
|
typedef enum {
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemType_Invalid = 0, ///< Invalid boot signature.
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemType_Unsupported = 1, ///< Valid boot signature, unsupported FS.
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemType_FAT = 2, ///< FAT filesystem (FAT12, FAT16, FAT32, exFAT).
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemType_NTFS = 3, ///< NTFS filesystem.
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemType_EXT = 4 ///< EXT* filesystem (EXT2, EXT3, EXT4).
|
||||||
|
} UsbHsFsDriveLogicalUnitFileSystemType;
|
||||||
|
|
||||||
|
/// Used to handle filesystems from LUNs.
|
||||||
|
typedef struct {
|
||||||
|
void *lun_ctx; ///< Pointer to the LUN context this filesystem belongs to.
|
||||||
|
u32 fs_idx; ///< Filesystem index within the fs_ctx array from the LUN context.
|
||||||
|
u8 fs_type; ///< UsbHsFsDriveLogicalUnitFileSystemType.
|
||||||
|
u32 flags; ///< UsbHsFsMountFlags bitmask used at mount time.
|
||||||
|
FATFS *fatfs; ///< Pointer to a dynamically allocated FatFs object. Only used if fs_type == UsbHsFsFileSystemType_FAT.
|
||||||
|
#ifdef GPL_BUILD
|
||||||
|
ntfs_vd *ntfs; ///< Pointer to a dynamically allocated ntfs_vd object. Only used if fs_type == UsbHsFsFileSystemType_NTFS.
|
||||||
|
ext_vd *ext; ///< Pointer to a dynamically allocated ext_vd object. Only used if fs_type == UsbHsFsFileSystemType_EXT.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// TODO: add more FS objects here after implementing support for other filesystems.
|
||||||
|
|
||||||
|
u32 device_id; ///< ID used as part of the mount name.
|
||||||
|
char *name; ///< Pointer to the dynamically allocated mount name string. Must end with a colon (:).
|
||||||
|
char *cwd; ///< Pointer to the dynamically allocated current working directory string.
|
||||||
|
devoptab_t *device; ///< Pointer to the dynamically allocated devoptab virtual device interface. Used to provide a way to use libcstd I/O calls on the mounted filesystem.
|
||||||
|
} UsbHsFsDriveLogicalUnitFileSystemContext;
|
||||||
|
|
||||||
|
/// Used to handle LUNs from drives.
|
||||||
|
typedef struct {
|
||||||
|
void *drive_ctx; ///< Pointer to the drive context this LUN belongs to.
|
||||||
|
s32 usb_if_id; ///< USB interface ID. Placed here for convenience.
|
||||||
|
bool uasp; ///< Set to true if USB Attached SCSI Protocol is being used with this drive. Placed here for convenience.
|
||||||
|
u8 lun; ///< Drive LUN index (zero-based, up to 15). Used to send SCSI commands.
|
||||||
|
bool removable; ///< Set to true if this LUN is removable. Retrieved via Inquiry SCSI command.
|
||||||
|
bool eject_supported; ///< Set to true if ejection via Prevent/Allow Medium Removal + Start Stop Unit is supported.
|
||||||
|
bool write_protect; ///< Set to true if the Write Protect bit is set.
|
||||||
|
bool fua_supported; ///< Set to true if the Force Unit Access feature is supported.
|
||||||
|
char vendor_id[0x9]; ///< Vendor identification string. Retrieved via Inquiry SCSI command. May be empty.
|
||||||
|
char product_id[0x11]; ///< Product identification string. Retrieved via Inquiry SCSI command. May be empty.
|
||||||
|
bool long_lba; ///< Set to true if Read Capacity (16) was used to retrieve the LUN capacity.
|
||||||
|
u64 block_count; ///< Logical block count. Retrieved via Read Capacity SCSI command. Must be non-zero.
|
||||||
|
u32 block_length; ///< Logical block length (bytes). Retrieved via Read Capacity SCSI command. Must be non-zero.
|
||||||
|
u64 capacity; ///< LUN capacity (block count times block length).
|
||||||
|
u32 fs_count; ///< Number of mounted filesystems stored in this LUN.
|
||||||
|
UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx; ///< Dynamically allocated pointer array of fs_count filesystem contexts.
|
||||||
|
} UsbHsFsDriveLogicalUnitContext;
|
||||||
|
|
||||||
|
/// Used to handle drives.
|
||||||
|
typedef struct {
|
||||||
|
Mutex mutex; ///< Drive mutex.
|
||||||
|
u8 *xfer_buf; ///< Dedicated transfer buffer for this drive.
|
||||||
|
s32 usb_if_id; ///< USB interface ID. Exactly the same as usb_if_session.ID / usb_if_session.inf.inf.ID. Placed here for convenience.
|
||||||
|
bool uasp; ///< Set to true if USB Attached SCSI Protocol is being used with this drive.
|
||||||
|
UsbHsClientIfSession usb_if_session; ///< Interface session.
|
||||||
|
UsbHsClientEpSession usb_in_ep_session[2]; ///< Input endpoint sessions (device to host). BOT: 0 = Data In & Status, 1 = Unused. UASP: 0 = Status, 1 = Data In.
|
||||||
|
UsbHsClientEpSession usb_out_ep_session[2]; ///< Output endpoint sessions (host to device). BOT: 0 = Command & Data Out, 1 = Unused. UASP: 0 = Command, 1 = Data Out.
|
||||||
|
u16 vid; ///< Vendor ID. Retrieved from the device descriptor. Placed here for convenience.
|
||||||
|
u16 pid; ///< Product ID. Retrieved from the device descriptor. Placed here for convenience.
|
||||||
|
char *manufacturer; ///< Dynamically allocated, UTF-8 encoded manufacturer string. May be NULL if not provided by the device descriptor.
|
||||||
|
char *product_name; ///< Dynamically allocated, UTF-8 encoded manufacturer string. May be NULL if not provided by the device descriptor.
|
||||||
|
char *serial_number; ///< Dynamically allocated, UTF-8 encoded manufacturer string. May be NULL if not provided by the device descriptor.
|
||||||
|
u8 max_lun; ///< Max LUNs supported by this drive. Must be at least 1.
|
||||||
|
u8 lun_count; ///< Initialized LUN count. May differ from the max LUN count.
|
||||||
|
UsbHsFsDriveLogicalUnitContext **lun_ctx; ///< Dynamically allocated pointer array of lun_count LUN contexts.
|
||||||
|
} UsbHsFsDriveContext;
|
||||||
|
|
||||||
|
/// None of these functions are thread safe - make sure to (un)lock mutexes elsewhere.
|
||||||
|
|
||||||
|
/// Initializes a drive context using the provided UsbHsInterface object.
|
||||||
|
bool usbHsFsDriveInitializeContext(UsbHsFsDriveContext *drive_ctx, UsbHsInterface *usb_if);
|
||||||
|
|
||||||
|
/// Destroys the provided drive context.
|
||||||
|
void usbHsFsDriveDestroyContext(UsbHsFsDriveContext *drive_ctx, bool stop_lun);
|
||||||
|
|
||||||
|
/// Wrapper for usbHsFsRequestClearEndpointHaltFeature() that clears a possible STALL status from all endpoints.
|
||||||
|
void usbHsFsDriveClearStallStatus(UsbHsFsDriveContext *drive_ctx);
|
||||||
|
|
||||||
|
/// Checks if the provided drive context is valid.
|
||||||
|
NX_INLINE bool usbHsFsDriveIsValidContext(UsbHsFsDriveContext *drive_ctx)
|
||||||
|
{
|
||||||
|
return (drive_ctx && drive_ctx->xfer_buf && usbHsIfIsActive(&(drive_ctx->usb_if_session)) && \
|
||||||
|
serviceIsActive(&(drive_ctx->usb_in_ep_session[0].s)) && serviceIsActive(&(drive_ctx->usb_out_ep_session[0].s)) && \
|
||||||
|
(!drive_ctx->uasp || (serviceIsActive(&(drive_ctx->usb_in_ep_session[1].s)) && serviceIsActive(&(drive_ctx->usb_out_ep_session[1].s)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the provided LUN context is valid.
|
||||||
|
NX_INLINE bool usbHsFsDriveIsValidLogicalUnitContext(UsbHsFsDriveLogicalUnitContext *lun_ctx)
|
||||||
|
{
|
||||||
|
return (lun_ctx && usbHsFsDriveIsValidContext((UsbHsFsDriveContext*)lun_ctx->drive_ctx) && lun_ctx->lun < UMS_MAX_LUN && lun_ctx->block_count && lun_ctx->block_length && lun_ctx->capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the provided filesystem context is valid.
|
||||||
|
/// TODO: update this after adding support for more filesystems.
|
||||||
|
NX_INLINE bool usbHsFsDriveIsValidLogicalUnitFileSystemContext(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx)
|
||||||
|
{
|
||||||
|
bool ctx_valid = (fs_ctx && usbHsFsDriveIsValidLogicalUnitContext((UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx) && fs_ctx->fs_type > UsbHsFsDriveLogicalUnitFileSystemType_Unsupported && \
|
||||||
|
fs_ctx->name && fs_ctx->cwd && fs_ctx->device);
|
||||||
|
bool fs_valid = false;
|
||||||
|
|
||||||
|
if (ctx_valid)
|
||||||
|
{
|
||||||
|
switch(fs_ctx->fs_type)
|
||||||
|
{
|
||||||
|
case UsbHsFsDriveLogicalUnitFileSystemType_FAT:
|
||||||
|
fs_valid = (fs_ctx->fatfs != NULL);
|
||||||
|
break;
|
||||||
|
#ifdef GPL_BUILD
|
||||||
|
case UsbHsFsDriveLogicalUnitFileSystemType_NTFS:
|
||||||
|
fs_valid = (fs_ctx->ntfs != NULL);
|
||||||
|
break;
|
||||||
|
case UsbHsFsDriveLogicalUnitFileSystemType_EXT:
|
||||||
|
fs_valid = (fs_ctx->ext != NULL);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ctx_valid && fs_valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_DRIVE_H__ */
|
341
include/libusbhsfs/source/usbhsfs_log.c
Normal file
341
include/libusbhsfs/source/usbhsfs_log.c
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_log.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#include "usbhsfs_utils.h"
|
||||||
|
|
||||||
|
#define LOG_FILE_NAME "/" LIB_TITLE ".log"
|
||||||
|
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
|
||||||
|
#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static Mutex g_logMutex = 0;
|
||||||
|
|
||||||
|
static FsFileSystem *g_sdCardFileSystem = NULL;
|
||||||
|
|
||||||
|
static FsFile g_logFile = {0};
|
||||||
|
static s64 g_logFileOffset = 0;
|
||||||
|
|
||||||
|
static char *g_logBuffer = NULL;
|
||||||
|
static size_t g_logBufferLength = 0;
|
||||||
|
|
||||||
|
static const char *g_utf8Bom = "\xEF\xBB\xBF";
|
||||||
|
static const char *g_logStrFormat = "[%d-%02d-%02d %02d:%02d:%02d.%09lu] %s -> ";
|
||||||
|
static const char *g_logLineBreak = "\r\n";
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static void _usbHsFsLogWriteStringToLogFile(const char *src);
|
||||||
|
static void _usbHsFsLogWriteFormattedStringToLogFile(const char *func_name, const char *fmt, va_list args);
|
||||||
|
|
||||||
|
static void _usbHsFsLogFlushLogFile(void);
|
||||||
|
|
||||||
|
static bool usbHsFsLogAllocateLogBuffer(void);
|
||||||
|
static bool usbHsFsLogOpenLogFile(void);
|
||||||
|
|
||||||
|
static void usbHsFsLogGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size);
|
||||||
|
|
||||||
|
void usbHsFsLogWriteStringToLogFile(const char *src)
|
||||||
|
{
|
||||||
|
SCOPED_LOCK(&g_logMutex) _usbHsFsLogWriteStringToLogFile(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((format(printf, 2, 3))) void usbHsFsLogWriteFormattedStringToLogFile(const char *func_name, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
SCOPED_LOCK(&g_logMutex) _usbHsFsLogWriteFormattedStringToLogFile(func_name, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((format(printf, 4, 5))) void usbHsFsLogWriteBinaryDataToLogFile(const void *data, size_t data_size, const char *func_name, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (!data || !data_size || !func_name || !*func_name || !fmt || !*fmt) return;
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
size_t data_str_size = ((data_size * 2) + 3);
|
||||||
|
char *data_str = NULL;
|
||||||
|
|
||||||
|
/* Allocate memory for the hex string representation of the provided binary data. */
|
||||||
|
data_str = calloc(data_str_size, sizeof(char));
|
||||||
|
if (!data_str) goto end;
|
||||||
|
|
||||||
|
/* Generate hex string representation. */
|
||||||
|
usbHsFsLogGenerateHexStringFromData(data_str, data_str_size, data, data_size);
|
||||||
|
strcat(data_str, g_logLineBreak);
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_logMutex)
|
||||||
|
{
|
||||||
|
/* Write formatted string. */
|
||||||
|
va_start(args, fmt);
|
||||||
|
_usbHsFsLogWriteFormattedStringToLogFile(func_name, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
/* Write hex string representation. */
|
||||||
|
_usbHsFsLogWriteStringToLogFile(data_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (data_str) free(data_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbHsFsLogFlushLogFile(void)
|
||||||
|
{
|
||||||
|
SCOPED_LOCK(&g_logMutex) _usbHsFsLogFlushLogFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void usbHsFsLogCloseLogFile(void)
|
||||||
|
{
|
||||||
|
SCOPED_LOCK(&g_logMutex)
|
||||||
|
{
|
||||||
|
/* Flush log buffer. */
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
|
||||||
|
/* Close logfile. */
|
||||||
|
if (serviceIsActive(&(g_logFile.s)))
|
||||||
|
{
|
||||||
|
fsFileClose(&g_logFile);
|
||||||
|
memset(&g_logFile, 0, sizeof(FsFile));
|
||||||
|
|
||||||
|
/* Commit SD card filesystem changes. */
|
||||||
|
if (g_sdCardFileSystem) fsFsCommit(g_sdCardFileSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free log buffer. */
|
||||||
|
if (g_logBuffer)
|
||||||
|
{
|
||||||
|
free(g_logBuffer);
|
||||||
|
g_logBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset logfile offset. */
|
||||||
|
g_logFileOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _usbHsFsLogWriteStringToLogFile(const char *src)
|
||||||
|
{
|
||||||
|
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
|
||||||
|
if (!src || !*src || !usbHsFsLogAllocateLogBuffer() || !usbHsFsLogOpenLogFile()) return;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
size_t src_len = strlen(src), tmp_len = 0;
|
||||||
|
|
||||||
|
/* Check if the formatted string length is lower than the log buffer size. */
|
||||||
|
if (src_len < LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
/* Flush log buffer contents (if needed). */
|
||||||
|
if ((g_logBufferLength + src_len) >= LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
if (g_logBufferLength) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy string into the log buffer. */
|
||||||
|
strcpy(g_logBuffer + g_logBufferLength, src);
|
||||||
|
g_logBufferLength += src_len;
|
||||||
|
} else {
|
||||||
|
/* Flush log buffer. */
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
if (g_logBufferLength) return;
|
||||||
|
|
||||||
|
/* Write string data until it no longer exceeds the log buffer size. */
|
||||||
|
while(src_len >= LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
rc = fsFileWrite(&g_logFile, g_logFileOffset, src + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush);
|
||||||
|
if (R_FAILED(rc)) return;
|
||||||
|
|
||||||
|
g_logFileOffset += LOG_BUF_SIZE;
|
||||||
|
tmp_len += LOG_BUF_SIZE;
|
||||||
|
src_len -= LOG_BUF_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy any remaining data from the string into the log buffer. */
|
||||||
|
if (src_len)
|
||||||
|
{
|
||||||
|
strcpy(g_logBuffer, src + tmp_len);
|
||||||
|
g_logBufferLength = src_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LOG_FORCE_FLUSH == 1
|
||||||
|
/* Flush log buffer. */
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _usbHsFsLogWriteFormattedStringToLogFile(const char *func_name, const char *fmt, va_list args)
|
||||||
|
{
|
||||||
|
/* Make sure we have allocated memory for the log buffer and opened the logfile. */
|
||||||
|
if (!func_name || !*func_name || !fmt || !*fmt || !usbHsFsLogAllocateLogBuffer() || !usbHsFsLogOpenLogFile()) return;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
int str1_len = 0, str2_len = 0;
|
||||||
|
size_t log_str_len = 0;
|
||||||
|
|
||||||
|
char *tmp_str = NULL;
|
||||||
|
size_t tmp_len = 0;
|
||||||
|
|
||||||
|
struct tm ts = {0};
|
||||||
|
struct timespec now = {0};
|
||||||
|
|
||||||
|
/* Get current time with nanosecond precision. */
|
||||||
|
clock_gettime(CLOCK_REALTIME, &now);
|
||||||
|
|
||||||
|
/* Get local time. */
|
||||||
|
localtime_r(&(now.tv_sec), &ts);
|
||||||
|
ts.tm_year += 1900;
|
||||||
|
ts.tm_mon++;
|
||||||
|
|
||||||
|
/* Get formatted string length. */
|
||||||
|
str1_len = snprintf(NULL, 0, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
|
||||||
|
if (str1_len <= 0) return;
|
||||||
|
|
||||||
|
str2_len = vsnprintf(NULL, 0, fmt, args);
|
||||||
|
if (str2_len <= 0) return;
|
||||||
|
|
||||||
|
log_str_len = (size_t)(str1_len + str2_len + 2);
|
||||||
|
|
||||||
|
/* Check if the formatted string length is less than the log buffer size. */
|
||||||
|
if (log_str_len < LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
/* Flush log buffer contents (if needed). */
|
||||||
|
if ((g_logBufferLength + log_str_len) >= LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
if (g_logBufferLength) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nice and easy string formatting using the log buffer. */
|
||||||
|
sprintf(g_logBuffer + g_logBufferLength, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
|
||||||
|
vsprintf(g_logBuffer + g_logBufferLength + (size_t)str1_len, fmt, args);
|
||||||
|
strcat(g_logBuffer, g_logLineBreak);
|
||||||
|
g_logBufferLength += log_str_len;
|
||||||
|
} else {
|
||||||
|
/* Flush log buffer. */
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
if (g_logBufferLength) return;
|
||||||
|
|
||||||
|
/* Allocate memory for a temporary buffer. This will hold the formatted string. */
|
||||||
|
tmp_str = calloc(log_str_len + 1, sizeof(char));
|
||||||
|
if (!tmp_str) return;
|
||||||
|
|
||||||
|
/* Generate formatted string. */
|
||||||
|
sprintf(tmp_str, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
|
||||||
|
vsprintf(tmp_str + (size_t)str1_len, fmt, args);
|
||||||
|
strcat(tmp_str, g_logLineBreak);
|
||||||
|
|
||||||
|
/* Write formatted string data until it no longer exceeds the log buffer size. */
|
||||||
|
while(log_str_len >= LOG_BUF_SIZE)
|
||||||
|
{
|
||||||
|
rc = fsFileWrite(&g_logFile, g_logFileOffset, tmp_str + tmp_len, LOG_BUF_SIZE, FsWriteOption_Flush);
|
||||||
|
if (R_FAILED(rc)) goto end;
|
||||||
|
|
||||||
|
g_logFileOffset += LOG_BUF_SIZE;
|
||||||
|
tmp_len += LOG_BUF_SIZE;
|
||||||
|
log_str_len -= LOG_BUF_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy any remaining data from the formatted string into the log buffer. */
|
||||||
|
if (log_str_len)
|
||||||
|
{
|
||||||
|
strcpy(g_logBuffer, tmp_str + tmp_len);
|
||||||
|
g_logBufferLength = log_str_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LOG_FORCE_FLUSH == 1
|
||||||
|
/* Flush log buffer. */
|
||||||
|
_usbHsFsLogFlushLogFile();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (tmp_str) free(tmp_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _usbHsFsLogFlushLogFile(void)
|
||||||
|
{
|
||||||
|
if (!serviceIsActive(&(g_logFile.s)) || !g_logBuffer || !g_logBufferLength) return;
|
||||||
|
|
||||||
|
/* Write log buffer contents and flush the written data right away. */
|
||||||
|
Result rc = fsFileWrite(&g_logFile, g_logFileOffset, g_logBuffer, g_logBufferLength, FsWriteOption_Flush);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
/* Update global variables. */
|
||||||
|
g_logFileOffset += (s64)g_logBufferLength;
|
||||||
|
*g_logBuffer = '\0';
|
||||||
|
g_logBufferLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsLogAllocateLogBuffer(void)
|
||||||
|
{
|
||||||
|
if (g_logBuffer) return true;
|
||||||
|
g_logBuffer = memalign(LOG_BUF_SIZE, LOG_BUF_SIZE);
|
||||||
|
return (g_logBuffer != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsLogOpenLogFile(void)
|
||||||
|
{
|
||||||
|
if (serviceIsActive(&(g_logFile.s))) return true;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
/* Get SD card FsFileSystem object. */
|
||||||
|
g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:");
|
||||||
|
if (!g_sdCardFileSystem) return false;
|
||||||
|
|
||||||
|
/* Create file. This will fail if the logfile exists, so we don't check its return value. */
|
||||||
|
fsFsCreateFile(g_sdCardFileSystem, LOG_FILE_NAME, 0, 0);
|
||||||
|
|
||||||
|
/* Open file. */
|
||||||
|
rc = fsFsOpenFile(g_sdCardFileSystem, LOG_FILE_NAME, FsOpenMode_Write | FsOpenMode_Append, &g_logFile);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
/* Get file size. */
|
||||||
|
rc = fsFileGetSize(&g_logFile, &g_logFileOffset);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
/* Write UTF-8 BOM right away (if needed). */
|
||||||
|
if (!g_logFileOffset)
|
||||||
|
{
|
||||||
|
size_t utf8_bom_len = strlen(g_utf8Bom);
|
||||||
|
fsFileWrite(&g_logFile, g_logFileOffset, g_utf8Bom, utf8_bom_len, FsWriteOption_Flush);
|
||||||
|
g_logFileOffset += (s64)utf8_bom_len;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fsFileClose(&g_logFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return R_SUCCEEDED(rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbHsFsLogGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size)
|
||||||
|
{
|
||||||
|
if (!src || !src_size || !dst || dst_size < ((src_size * 2) + 1)) return;
|
||||||
|
|
||||||
|
size_t i, j;
|
||||||
|
const u8 *src_u8 = (const u8*)src;
|
||||||
|
|
||||||
|
for(i = 0, j = 0; i < src_size; i++)
|
||||||
|
{
|
||||||
|
char h_nib = ((src_u8[i] >> 4) & 0xF);
|
||||||
|
char l_nib = (src_u8[i] & 0xF);
|
||||||
|
|
||||||
|
dst[j++] = (h_nib + (h_nib < 0xA ? 0x30 : 0x37));
|
||||||
|
dst[j++] = (l_nib + (l_nib < 0xA ? 0x30 : 0x37));
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DEBUG */
|
42
include/libusbhsfs/source/usbhsfs_log.h
Normal file
42
include/libusbhsfs/source/usbhsfs_log.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_log.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_LOG_H__
|
||||||
|
#define __USBHSFS_LOG_H__
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
/// Helper macros.
|
||||||
|
#define USBHSFS_LOG_MSG(fmt, ...) usbHsFsLogWriteFormattedStringToLogFile(__func__, fmt, ##__VA_ARGS__)
|
||||||
|
#define USBHSFS_LOG_DATA(data, data_size, fmt, ...) usbHsFsLogWriteBinaryDataToLogFile(data, data_size, __func__, fmt, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/// Writes the provided string to the logfile.
|
||||||
|
void usbHsFsLogWriteStringToLogFile(const char *src);
|
||||||
|
|
||||||
|
/// Writes a formatted log string to the logfile.
|
||||||
|
__attribute__((format(printf, 2, 3))) void usbHsFsLogWriteFormattedStringToLogFile(const char *func_name, const char *fmt, ...);
|
||||||
|
|
||||||
|
/// Writes a formatted log string + a hex string representation of the provided binary data to the logfile.
|
||||||
|
__attribute__((format(printf, 4, 5))) void usbHsFsLogWriteBinaryDataToLogFile(const void *data, size_t data_size, const char *func_name, const char *fmt, ...);
|
||||||
|
|
||||||
|
/// Forces a flush operation on the logfile.
|
||||||
|
void usbHsFsLogFlushLogFile(void);
|
||||||
|
|
||||||
|
/// Closes the logfile.
|
||||||
|
void usbHsFsLogCloseLogFile(void);
|
||||||
|
|
||||||
|
#else /* DEBUG */
|
||||||
|
|
||||||
|
#define USBHSFS_LOG_MSG(fmt, ...) do {} while(0)
|
||||||
|
#define USBHSFS_LOG_DATA(data, data_size, fmt, ...) do {} while(0)
|
||||||
|
|
||||||
|
#endif /* DEBUG */
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_LOG_H__ */
|
1035
include/libusbhsfs/source/usbhsfs_manager.c
Normal file
1035
include/libusbhsfs/source/usbhsfs_manager.c
Normal file
File diff suppressed because it is too large
Load Diff
28
include/libusbhsfs/source/usbhsfs_manager.h
Normal file
28
include/libusbhsfs/source/usbhsfs_manager.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_manager.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_MANAGER_H__
|
||||||
|
#define __USBHSFS_MANAGER_H__
|
||||||
|
|
||||||
|
#include "usbhsfs_drive.h"
|
||||||
|
|
||||||
|
/// Locks the drive manager mutex to prevent the background thread from updating drive contexts while working with them, then tries to find a match for the provided drive context in the pointer array.
|
||||||
|
/// If a match is found, the drive context mutex is locked. The drive manager mutex is unlocked right before this function returns.
|
||||||
|
/// This function is thread-safe.
|
||||||
|
bool usbHsFsManagerIsDriveContextPointerValid(UsbHsFsDriveContext *drive_ctx);
|
||||||
|
|
||||||
|
/// Locks the drive manager mutex to prevent the background thread from updating drive contexts while working with them.
|
||||||
|
/// Then looks for a filesystem context with a FatFs object that holds a physical drive number matching the provided one. If a match is found, its parent LUN context is returned.
|
||||||
|
/// Otherwise, this function returns NULL. The drive manager mutex is unlocked right before this function returns.
|
||||||
|
/// This function is thread-safe.
|
||||||
|
UsbHsFsDriveLogicalUnitContext *usbHsFsManagerGetLogicalUnitContextForFatFsDriveNumber(u8 pdrv);
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_MANAGER_H__ */
|
1342
include/libusbhsfs/source/usbhsfs_mount.c
Normal file
1342
include/libusbhsfs/source/usbhsfs_mount.c
Normal file
File diff suppressed because it is too large
Load Diff
41
include/libusbhsfs/source/usbhsfs_mount.h
Normal file
41
include/libusbhsfs/source/usbhsfs_mount.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_mount.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_MOUNT_H__
|
||||||
|
#define __USBHSFS_MOUNT_H__
|
||||||
|
|
||||||
|
#include "usbhsfs_drive.h"
|
||||||
|
|
||||||
|
extern __thread char __usbhsfs_dev_path_buf[MAX_PATH_LENGTH];
|
||||||
|
|
||||||
|
/// None of these functions are thread safe - make sure to (un)lock mutexes elsewhere.
|
||||||
|
|
||||||
|
/// Initializes filesystem contexts for the provided LUN context.
|
||||||
|
/// If this function succeeds, at least one filesystem will have been both mounted and registered as a devoptab virtual device.
|
||||||
|
bool usbHsFsMountInitializeLogicalUnitFileSystemContexts(UsbHsFsDriveLogicalUnitContext *lun_ctx);
|
||||||
|
|
||||||
|
/// Destroys the provided filesystem context, unregistering the devoptab virtual device and unmounting the filesystem in the process.
|
||||||
|
void usbHsFsMountDestroyLogicalUnitFileSystemContext(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx);
|
||||||
|
|
||||||
|
/// Returns the total number of registered devoptab virtual devices.
|
||||||
|
u32 usbHsFsMountGetDevoptabDeviceCount(void);
|
||||||
|
|
||||||
|
/// Sets the devoptab device from the provided filesystem context as the default devoptab device.
|
||||||
|
/// Called by the chdir() function from devoptab interfaces.
|
||||||
|
bool usbHsFsMountSetDefaultDevoptabDevice(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx);
|
||||||
|
|
||||||
|
/// Returns a bitmask with the current filesystem mount flags.
|
||||||
|
u32 usbHsFsMountGetFileSystemMountFlags(void);
|
||||||
|
|
||||||
|
/// Takes an input bitmask with the desired filesystem mount flags, which will be used for all mount operations.
|
||||||
|
void usbHsFsMountSetFileSystemMountFlags(u32 flags);
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_MOUNT_H__ */
|
409
include/libusbhsfs/source/usbhsfs_request.c
Normal file
409
include/libusbhsfs/source/usbhsfs_request.c
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_request.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "usbhsfs_utils.h"
|
||||||
|
#include "usbhsfs_request.h"
|
||||||
|
|
||||||
|
void *usbHsFsRequestAllocateXferBuffer(void)
|
||||||
|
{
|
||||||
|
return memalign(USB_XFER_BUF_ALIGNMENT, USB_XFER_BUF_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 7). */
|
||||||
|
Result usbHsFsRequestGetMaxLogicalUnits(UsbHsClientIfSession *usb_if_session, u8 *out)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u8 *max_lun = NULL;
|
||||||
|
u16 if_num = 0, len = 1;
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !out)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if_num = usb_if_session->inf.inf.interface_desc.bInterfaceNumber;
|
||||||
|
|
||||||
|
/* Allocate memory for the control transfer. */
|
||||||
|
max_lun = memalign(USB_XFER_BUF_ALIGNMENT, len);
|
||||||
|
if (!max_lun)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory! (interface %d).", usb_if_session->ID);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform control transfer. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_IN | USB_REQUEST_TYPE_CLASS | USB_RECIPIENT_INTERFACE, USB_REQUEST_BOT_GET_MAX_LUN, 0, if_num, len, max_lun, &xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check transferred data size. */
|
||||||
|
if (xfer_size != len)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer read 0x%X byte(s), expected 0x%X! (interface %d).", xfer_size, len, usb_if_session->ID);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = (*max_lun + 1);
|
||||||
|
if (*out > UMS_MAX_LUN) *out = 1;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (max_lun) free(max_lun);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (pages 7 and 16). */
|
||||||
|
Result usbHsFsRequestMassStorageReset(UsbHsClientIfSession *usb_if_session)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u16 if_num = 0;
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if_num = usb_if_session->inf.inf.interface_desc.bInterfaceNumber;
|
||||||
|
|
||||||
|
/* Perform control transfer. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_OUT | USB_REQUEST_TYPE_CLASS | USB_RECIPIENT_INTERFACE, USB_REQUEST_BOT_RESET, 0, if_num, 0, NULL, &xfer_size);
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
||||||
|
|
||||||
|
end:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.beyondlogic.org/usbnutshell/usb6.shtml. */
|
||||||
|
Result usbHsFsRequestGetConfigurationDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u8 **out_buf, u32 *out_buf_size)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u16 desc = ((USB_DT_CONFIG << 8) | idx);
|
||||||
|
u16 len = sizeof(struct usb_config_descriptor);
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
struct usb_config_descriptor *config_desc = NULL;
|
||||||
|
u8 *buf = NULL;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || idx >= usb_if_session->inf.device_desc.bNumConfigurations || !out_buf || !out_buf_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the minimal configuration descriptor. */
|
||||||
|
config_desc = memalign(USB_XFER_BUF_ALIGNMENT, len);
|
||||||
|
if (!config_desc)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%X bytes for the minimal configuration descriptor! (interface %d, index %u).", len, usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get minimal configuration descriptor. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_IN | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_DEVICE, USB_REQUEST_GET_DESCRIPTOR, desc, 0, len, config_desc, &xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (minimal) (interface %d, index %u).", rc, usb_if_session->ID, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check transferred data size. */
|
||||||
|
if (xfer_size != len)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer got 0x%X byte(s), expected 0x%X! (minimal) (interface %d, index %u).", xfer_size, len, usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_DATA(config_desc, len, "Minimal configuration descriptor data (interface %d, index %u):", usb_if_session->ID, idx);
|
||||||
|
|
||||||
|
/* Verify configuration descriptor. */
|
||||||
|
if (config_desc->bLength != len || config_desc->bDescriptorType != USB_DT_CONFIG || config_desc->wTotalLength <= config_desc->bLength)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid configuration descriptor! (minimal) (interface %d, index %u).", usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_IoError);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the full configuration descriptor. */
|
||||||
|
/* An extra byte is allocated for parsing purposes. It won't be reflected in the returned size. */
|
||||||
|
buf = memalign(USB_XFER_BUF_ALIGNMENT, config_desc->wTotalLength + 1);
|
||||||
|
if (!buf)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%X bytes for the full configuration descriptor! (interface %d, index %u).", config_desc->wTotalLength + 1, usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get full configuration descriptor. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_IN | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_DEVICE, USB_REQUEST_GET_DESCRIPTOR, desc, 0, config_desc->wTotalLength, buf, &xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (full) (interface %d, index %u).", rc, usb_if_session->ID, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check transferred data size. */
|
||||||
|
if (xfer_size != config_desc->wTotalLength)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer got 0x%X byte(s), expected 0x%X! (full) (interface %d, index %u).", xfer_size, config_desc->wTotalLength, usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_DATA(buf, config_desc->wTotalLength, "Full configuration descriptor data (interface %d, index %u):", usb_if_session->ID, idx);
|
||||||
|
|
||||||
|
/* Verify configuration descriptor. */
|
||||||
|
struct usb_config_descriptor *full_config_desc = (struct usb_config_descriptor*)buf;
|
||||||
|
if (memcmp(config_desc, full_config_desc, len) != 0)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid configuration descriptor! (full) (interface %d, index %u).", usb_if_session->ID, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_IoError);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update output. */
|
||||||
|
*out_buf = buf;
|
||||||
|
*out_buf_size = config_desc->wTotalLength;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (R_FAILED(rc) && buf) free(buf);
|
||||||
|
|
||||||
|
if (config_desc) free(config_desc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.beyondlogic.org/usbnutshell/usb6.shtml. */
|
||||||
|
Result usbHsFsRequestGetStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, u16 **out_buf, u32 *out_buf_size)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u16 desc = ((USB_DT_STRING << 8) | idx);
|
||||||
|
u16 len = sizeof(struct _usb_string_descriptor);
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
struct _usb_string_descriptor *string_desc = NULL;
|
||||||
|
u16 *buf = NULL;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !out_buf || !out_buf_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the minimal configuration descriptor. */
|
||||||
|
string_desc = memalign(USB_XFER_BUF_ALIGNMENT, len);
|
||||||
|
if (!string_desc)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%X bytes for the string descriptor! (interface %d, language ID 0x%04X, index 0x%02X).", len, usb_if_session->ID, lang_id, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get string descriptor. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_IN | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_DEVICE, USB_REQUEST_GET_DESCRIPTOR, desc, lang_id, len, string_desc, &xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d, language ID 0x%04X, index 0x%02X).", rc, usb_if_session->ID, lang_id, idx);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check transferred data size. */
|
||||||
|
if (!xfer_size || (xfer_size % 2) != 0)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer got 0x%X byte(s)! (interface %d, language ID 0x%04X, index 0x%02X).", xfer_size, usb_if_session->ID, lang_id, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBHSFS_LOG_DATA(string_desc, xfer_size, "String descriptor data (interface %d, language ID 0x%04X, index 0x%02X):", usb_if_session->ID, lang_id, idx);
|
||||||
|
|
||||||
|
/* Verify string descriptor. */
|
||||||
|
if (string_desc->bLength != xfer_size || string_desc->bDescriptorType != USB_DT_STRING)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid string descriptor! (interface %d, language ID 0x%04X, index 0x%02X).", usb_if_session->ID, lang_id, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_IoError);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the string descriptor data. Two extra bytes are reserved, but they're not reflected in the returned size. */
|
||||||
|
/* This is useful for UTF-16 to UTF-8 conversions requiring a NULL terminator. */
|
||||||
|
buf = calloc(1, xfer_size);
|
||||||
|
if (!buf)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate 0x%X bytes for the string descriptor data! (interface %d, language ID 0x%04X, index 0x%02X).", xfer_size, usb_if_session->ID, lang_id, idx);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy string descriptor data. */
|
||||||
|
memcpy(buf, string_desc->wData, xfer_size - 2);
|
||||||
|
|
||||||
|
/* Update output. */
|
||||||
|
*out_buf = buf;
|
||||||
|
*out_buf_size = (xfer_size - 2);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (string_desc) free(string_desc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.beyondlogic.org/usbnutshell/usb6.shtml. */
|
||||||
|
Result usbHsFsRequestGetEndpointStatus(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool *out)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u16 *status = NULL;
|
||||||
|
u16 len = sizeof(u16), ep_addr = 0;
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !usb_ep_session || !serviceIsActive(&(usb_ep_session->s)) || !out)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ep_addr = usb_ep_session->desc.bEndpointAddress;
|
||||||
|
|
||||||
|
/* Allocate memory for the control transfer. */
|
||||||
|
status = memalign(USB_XFER_BUF_ALIGNMENT, len);
|
||||||
|
if (!status)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Failed to allocate memory! (interface %d, endpoint 0x%02X).", usb_if_session->ID, ep_addr);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform control transfer. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_IN | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_ENDPOINT, USB_REQUEST_GET_STATUS, 0, ep_addr, len, status, &xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d, endpoint 0x%02X).", rc, usb_if_session->ID, ep_addr);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check transferred data size. */
|
||||||
|
if (xfer_size != len)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsIfCtrlXfer got 0x%X byte(s), expected 0x%X! (interface %d, endpoint 0x%02X).", xfer_size, len, usb_if_session->ID, ep_addr);
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadUsbCommsRead);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = (*status != 0);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (status) free(status);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.beyondlogic.org/usbnutshell/usb6.shtml. */
|
||||||
|
Result usbHsFsRequestClearEndpointHaltFeature(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u16 ep_addr = 0;
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !usb_ep_session || !serviceIsActive(&(usb_ep_session->s)))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
ep_addr = usb_ep_session->desc.bEndpointAddress;
|
||||||
|
|
||||||
|
/* Perform control transfer. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_OUT | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_ENDPOINT, USB_REQUEST_CLEAR_FEATURE, USB_FEATURE_ENDPOINT_HALT, ep_addr, 0, NULL, &xfer_size);
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d, endpoint 0x%02X).", rc, usb_if_session->ID, ep_addr);
|
||||||
|
|
||||||
|
end:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.beyondlogic.org/usbnutshell/usb6.shtml. */
|
||||||
|
Result usbHsFsRequestSetInterface(UsbHsClientIfSession *usb_if_session)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
u8 if_num = 0, if_alt_setting = 0;
|
||||||
|
u32 xfer_size = 0;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if_num = usb_if_session->inf.inf.interface_desc.bInterfaceNumber;
|
||||||
|
if_alt_setting = usb_if_session->inf.inf.interface_desc.bAlternateSetting;
|
||||||
|
|
||||||
|
/* Perform control transfer. */
|
||||||
|
rc = usbHsIfCtrlXfer(usb_if_session, USB_ENDPOINT_OUT | USB_REQUEST_TYPE_STANDARD | USB_RECIPIENT_INTERFACE, USB_REQUEST_SET_INTERFACE, if_alt_setting, if_num, 0, NULL, &xfer_size);
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsIfCtrlXfer failed! (0x%08X) (interface %d, number %u, alt %u).", rc, usb_if_session->ID, if_num, if_alt_setting);
|
||||||
|
|
||||||
|
end:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (pages: 19 - 22). */
|
||||||
|
Result usbHsFsRequestPostBuffer(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, void *buf, u32 size, u32 *xfer_size, bool retry)
|
||||||
|
{
|
||||||
|
Result rc = 0, rc_halt = 0;
|
||||||
|
bool status = false;
|
||||||
|
|
||||||
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !usb_ep_session || !serviceIsActive(&(usb_ep_session->s)) || !buf || !size || !xfer_size)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
u8 ep_addr = usb_ep_session->desc.bEndpointAddress;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
rc = usbHsEpPostBuffer(usb_ep_session, buf, size, xfer_size);
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("usbHsEpPostBuffer failed! (0x%08X) (interface %d, endpoint 0x%02X).", rc, usb_if_session->ID, ep_addr);
|
||||||
|
|
||||||
|
/* Attempt to clear this endpoint if it was STALLed. */
|
||||||
|
rc_halt = usbHsFsRequestGetEndpointStatus(usb_if_session, usb_ep_session, &status);
|
||||||
|
if (R_SUCCEEDED(rc_halt) && status)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Clearing STALL status (interface %d, endpoint 0x%02X).", usb_if_session->ID, ep_addr);
|
||||||
|
rc_halt = usbHsFsRequestClearEndpointHaltFeature(usb_if_session, usb_ep_session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retry the transfer if needed. */
|
||||||
|
if (R_SUCCEEDED(rc_halt) && retry)
|
||||||
|
{
|
||||||
|
rc = usbHsEpPostBuffer(usb_ep_session, buf, size, xfer_size);
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsEpPostBuffer failed! (0x%08X) (retry) (interface %d, endpoint 0x%02X).", rc, usb_if_session->ID, ep_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
return rc;
|
||||||
|
}
|
51
include/libusbhsfs/source/usbhsfs_request.h
Normal file
51
include/libusbhsfs/source/usbhsfs_request.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_request.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_REQUEST_H__
|
||||||
|
#define __USBHSFS_REQUEST_H__
|
||||||
|
|
||||||
|
/// None of these functions are thread safe - make sure to (un)lock mutexes elsewhere.
|
||||||
|
|
||||||
|
/// Returns a pointer to a dynamic, memory-aligned buffer suitable for USB transfers.
|
||||||
|
void *usbHsFsRequestAllocateXferBuffer(void);
|
||||||
|
|
||||||
|
/// Performs a get max logical units class-specific request.
|
||||||
|
Result usbHsFsRequestGetMaxLogicalUnits(UsbHsClientIfSession *usb_if_session, u8 *out);
|
||||||
|
|
||||||
|
/// Performs a bulk-only mass storage reset class-specific request.
|
||||||
|
Result usbHsFsRequestMassStorageReset(UsbHsClientIfSession *usb_if_session);
|
||||||
|
|
||||||
|
/// Performs a GET_ENDPOINT request on the device pointed to by the provided interface session to retrieve the full configuration descriptor for the provided zero-based index.
|
||||||
|
/// The provided index must be lower than the bNumConfigurations value from the device descriptor in the provided interface session.
|
||||||
|
/// If the call succeeds, both 'out_buf' and 'out_buf_size' pointers will be updated.
|
||||||
|
/// The pointer to the dynamically allocated buffer stored in 'out_buf' must be freed by the user.
|
||||||
|
Result usbHsFsRequestGetConfigurationDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u8 **out_buf, u32 *out_buf_size);
|
||||||
|
|
||||||
|
/// Performs a GET_ENDPOINT request on the device pointed to by the provided interface session to retrieve the string descriptor for the provided index and language ID.
|
||||||
|
/// If the call succeeds, both 'out_buf' and 'out_buf_size' pointers will be updated.
|
||||||
|
/// The pointer to the dynamically allocated buffer stored in 'out_buf' must be freed by the user.
|
||||||
|
Result usbHsFsRequestGetStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, u16 **out_buf, u32 *out_buf_size);
|
||||||
|
|
||||||
|
/// Performs a GET_STATUS request on the provided endpoint.
|
||||||
|
/// If the call succeeds, the current STALL status from the endpoint is saved to 'out'.
|
||||||
|
Result usbHsFsRequestGetEndpointStatus(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool *out);
|
||||||
|
|
||||||
|
/// Performs a CLEAR_FEATURE request on the provided endpoint to clear a STALL status.
|
||||||
|
Result usbHsFsRequestClearEndpointHaltFeature(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session);
|
||||||
|
|
||||||
|
/// Performs a SET_INTERFACE request on the device pointed to by the provided interface session.
|
||||||
|
Result usbHsFsRequestSetInterface(UsbHsClientIfSession *usb_if_session);
|
||||||
|
|
||||||
|
/// Performs a data transfer on the provided endpoint.
|
||||||
|
/// If an error occurs, a STALL status check is performed on the target endpoint. If present, the STALL status is cleared and the transfer is retried one more time (if retry == true).
|
||||||
|
/// This is essentially a nice wrapper for usbHsEpPostBuffer(), which can be used in data transfer stages and CSW transfers from SCSI commands.
|
||||||
|
Result usbHsFsRequestPostBuffer(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, void *buf, u32 size, u32 *xfer_size, bool retry);
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_REQUEST_H__ */
|
1199
include/libusbhsfs/source/usbhsfs_scsi.c
Normal file
1199
include/libusbhsfs/source/usbhsfs_scsi.c
Normal file
File diff suppressed because it is too large
Load Diff
33
include/libusbhsfs/source/usbhsfs_scsi.h
Normal file
33
include/libusbhsfs/source/usbhsfs_scsi.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_scsi.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
* Copyright (c) 2020-2021, XorTroll.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_SCSI_H__
|
||||||
|
#define __USBHSFS_SCSI_H__
|
||||||
|
|
||||||
|
#include "usbhsfs_manager.h"
|
||||||
|
|
||||||
|
/// None of these functions are thread safe - make sure to (un)lock mutexes elsewhere.
|
||||||
|
|
||||||
|
/// Starts the LUN represented by the provided LUN context using SCSI commands and fills the LUN context.
|
||||||
|
bool usbHsFsScsiStartDriveLogicalUnit(UsbHsFsDriveLogicalUnitContext *lun_ctx);
|
||||||
|
|
||||||
|
/// Stops the LUN represented by the provided LUN context using SCSI commands, as long as it's removable (returns right away if it isn't).
|
||||||
|
void usbHsFsScsiStopDriveLogicalUnit(UsbHsFsDriveLogicalUnitContext *lun_ctx);
|
||||||
|
|
||||||
|
/// Reads logical blocks from a LUN using the provided LUN context. Suitable for filesystem libraries.
|
||||||
|
/// In order to speed up transfers, this function performs no checks on the provided arguments.
|
||||||
|
bool usbHsFsScsiReadLogicalUnitBlocks(UsbHsFsDriveLogicalUnitContext *lun_ctx, void *buf, u64 block_addr, u32 block_count);
|
||||||
|
|
||||||
|
/// Writes logical blocks to a LUN using the provided LUN context. Suitable for filesystem libraries.
|
||||||
|
/// In order to speed up transfers, this function performs no checks on the provided arguments.
|
||||||
|
bool usbHsFsScsiWriteLogicalUnitBlocks(UsbHsFsDriveLogicalUnitContext *lun_ctx, const void *buf, u64 block_addr, u32 block_count);
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_SCSI_H__ */
|
140
include/libusbhsfs/source/usbhsfs_utils.c
Normal file
140
include/libusbhsfs/source/usbhsfs_utils.c
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_utils.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "usbhsfs_utils.h"
|
||||||
|
|
||||||
|
/* Global variables. */
|
||||||
|
|
||||||
|
static u32 g_atmosphereVersion = 0;
|
||||||
|
static Mutex g_atmosphereVersionMutex = 0;
|
||||||
|
|
||||||
|
/* Atmosphère-related constants. */
|
||||||
|
|
||||||
|
static const u32 g_smAtmosphereHasService = 65100;
|
||||||
|
static const SplConfigItem SplConfigItem_ExosphereApiVersion = (SplConfigItem)65000;
|
||||||
|
static const u32 g_atmosphereTipcVersion = MAKEHOSVERSION(0, 19, 0);
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static bool usbHsFsUtilsCheckRunningServiceByName(const char *name);
|
||||||
|
static Result usbHsFsUtilsAtmosphereHasService(bool *out, SmServiceName name);
|
||||||
|
static Result usbHsFsUtilsGetExosphereApiVersion(u32 *out);
|
||||||
|
|
||||||
|
void usbHsFsUtilsTrimString(char *str)
|
||||||
|
{
|
||||||
|
size_t strsize = 0;
|
||||||
|
char *start = NULL, *end = NULL;
|
||||||
|
|
||||||
|
if (!str || !(strsize = strlen(str))) return;
|
||||||
|
|
||||||
|
start = str;
|
||||||
|
end = (start + strsize);
|
||||||
|
|
||||||
|
while(--end >= start)
|
||||||
|
{
|
||||||
|
if (!isspace((unsigned char)*end)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*(++end) = '\0';
|
||||||
|
|
||||||
|
while(isspace((unsigned char)*start)) start++;
|
||||||
|
|
||||||
|
if (start != str) memmove(str, start, end - start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usbHsFsUtilsIsFspUsbRunning(void)
|
||||||
|
{
|
||||||
|
return usbHsFsUtilsCheckRunningServiceByName("fsp-usb");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usbHsFsUtilsSXOSCustomFirmwareCheck(void)
|
||||||
|
{
|
||||||
|
return (usbHsFsUtilsCheckRunningServiceByName("tx") && !usbHsFsUtilsCheckRunningServiceByName("rnx"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbHsFsUtilsCheckRunningServiceByName(const char *name)
|
||||||
|
{
|
||||||
|
if (!name || !*name)
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_atmosphereVersionMutex)
|
||||||
|
{
|
||||||
|
Result rc = usbHsFsUtilsAtmosphereHasService(&ret, smEncodeName(name));
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsFsUtilsAtmosphereHasService failed for \"%s\"! (0x%08X).", name, rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SM API extension available in Atmosphère and Atmosphère-based CFWs. */
|
||||||
|
static Result usbHsFsUtilsAtmosphereHasService(bool *out, SmServiceName name)
|
||||||
|
{
|
||||||
|
if (!out || !name.name[0]) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
|
||||||
|
u8 tmp = 0;
|
||||||
|
Result rc = 0;
|
||||||
|
|
||||||
|
/* Get Exosphère API version. */
|
||||||
|
if (!g_atmosphereVersion)
|
||||||
|
{
|
||||||
|
rc = usbHsFsUtilsGetExosphereApiVersion(&g_atmosphereVersion);
|
||||||
|
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsFsUtilsGetExosphereApiVersion failed! (0x%08X).", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if service is running. */
|
||||||
|
/* Dispatch IPC request using CMIF or TIPC serialization depending on our current environment. */
|
||||||
|
if (hosversionAtLeast(12, 0, 0) || g_atmosphereVersion >= g_atmosphereTipcVersion)
|
||||||
|
{
|
||||||
|
rc = tipcDispatchInOut(smGetServiceSessionTipc(), g_smAtmosphereHasService, name, tmp);
|
||||||
|
} else {
|
||||||
|
rc = serviceDispatchInOut(smGetServiceSession(), g_smAtmosphereHasService, name, tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc)) *out = (tmp != 0);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SMC config item available in Atmosphère and Atmosphère-based CFWs. */
|
||||||
|
static Result usbHsFsUtilsGetExosphereApiVersion(u32 *out)
|
||||||
|
{
|
||||||
|
if (!out) return MAKERESULT(Module_Libnx, LibnxError_BadInput);
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
|
u64 cfg = 0;
|
||||||
|
u32 version = 0;
|
||||||
|
|
||||||
|
/* Initialize spl service. */
|
||||||
|
rc = splInitialize();
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
USBHSFS_LOG_MSG("splInitialize failed! (0x%08X).", rc);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get Exosphère API version config item. */
|
||||||
|
rc = splGetConfig(SplConfigItem_ExosphereApiVersion, &cfg);
|
||||||
|
|
||||||
|
/* Close spl service. */
|
||||||
|
splExit();
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
*out = version = (u32)((cfg >> 40) & 0xFFFFFF);
|
||||||
|
USBHSFS_LOG_MSG("Exosphère API version: %u.%u.%u.", HOSVER_MAJOR(version), HOSVER_MINOR(version), HOSVER_MICRO(version));
|
||||||
|
} else {
|
||||||
|
USBHSFS_LOG_MSG("splGetConfig failed! (0x%08X).", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
68
include/libusbhsfs/source/usbhsfs_utils.h
Normal file
68
include/libusbhsfs/source/usbhsfs_utils.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* usbhsfs_utils.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
|
*
|
||||||
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __USBHSFS_UTILS_H__
|
||||||
|
#define __USBHSFS_UTILS_H__
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#include "usb_common.h"
|
||||||
|
#include "usbhsfs.h"
|
||||||
|
#include "usbhsfs_log.h"
|
||||||
|
|
||||||
|
#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1))
|
||||||
|
|
||||||
|
#define SCOPED_LOCK(mtx) for(UsbHsFsUtilsScopedLock scoped_lock __attribute__((__cleanup__(usbHsFsUtilsUnlockScope))) = usbHsFsUtilsLockScope(mtx); scoped_lock.cond; scoped_lock.cond = 0)
|
||||||
|
|
||||||
|
/// Used by scoped locks.
|
||||||
|
typedef struct {
|
||||||
|
Mutex *mtx;
|
||||||
|
bool lock;
|
||||||
|
int cond;
|
||||||
|
} UsbHsFsUtilsScopedLock;
|
||||||
|
|
||||||
|
/// Trims whitespace characters from the provided string.
|
||||||
|
void usbHsFsUtilsTrimString(char *str);
|
||||||
|
|
||||||
|
/// Returns true if the fsp-usb service is running in the background.
|
||||||
|
bool usbHsFsUtilsIsFspUsbRunning(void);
|
||||||
|
|
||||||
|
/// Returns true if we're running under SX OS.
|
||||||
|
bool usbHsFsUtilsSXOSCustomFirmwareCheck(void);
|
||||||
|
|
||||||
|
/// Simple wrapper to sleep the current thread for a specific number of full seconds.
|
||||||
|
NX_INLINE void usbHsFsUtilsSleep(u64 seconds)
|
||||||
|
{
|
||||||
|
if (seconds) svcSleepThread(seconds * (u64)1000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrappers used in scoped locks.
|
||||||
|
NX_INLINE UsbHsFsUtilsScopedLock usbHsFsUtilsLockScope(Mutex *mtx)
|
||||||
|
{
|
||||||
|
UsbHsFsUtilsScopedLock scoped_lock = { mtx, !mutexIsLockedByCurrentThread(mtx), 1 };
|
||||||
|
if (scoped_lock.lock) mutexLock(scoped_lock.mtx);
|
||||||
|
return scoped_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
NX_INLINE void usbHsFsUtilsUnlockScope(UsbHsFsUtilsScopedLock *scoped_lock)
|
||||||
|
{
|
||||||
|
if (scoped_lock->lock) mutexUnlock(scoped_lock->mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __USBHSFS_UTILS_H__ */
|
Loading…
x
Reference in New Issue
Block a user