mirror of
https://github.com/wiiu-env/ftpiiu_plugin.git
synced 2024-09-28 19:18:38 +02:00
Compare commits
No commits in common. "9d06dcf35dfe93be55f312395387effb50a99206" and "3e1d862aff3aa730db212480b6c911d0ab7afe68" have entirely different histories.
9d06dcf35d
...
3e1d862aff
126
.clang-format
126
.clang-format
@ -1,67 +1,95 @@
|
|||||||
# Generated from CLion C/C++ Code Style settings
|
---
|
||||||
BasedOnStyle: LLVM
|
Language: Cpp
|
||||||
|
# BasedOnStyle: LLVM
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
AlignAfterOpenBracket: Align
|
AlignAfterOpenBracket: DontAlign
|
||||||
AlignConsecutiveAssignments: Consecutive
|
AlignConsecutiveAssignments: true
|
||||||
AlignConsecutiveMacros: AcrossEmptyLinesAndComments
|
AlignConsecutiveDeclarations: false
|
||||||
AlignOperands: Align
|
AlignEscapedNewlinesLeft: false
|
||||||
AllowAllArgumentsOnNextLine: false
|
AlignOperands: true
|
||||||
AllowAllConstructorInitializersOnNextLine: false
|
AlignTrailingComments: true
|
||||||
AllowAllParametersOfDeclarationOnNextLine: false
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
AllowShortBlocksOnASingleLine: Always
|
AllowShortBlocksOnASingleLine: false
|
||||||
AllowShortCaseLabelsOnASingleLine: false
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
AllowShortFunctionsOnASingleLine: All
|
AllowShortFunctionsOnASingleLine: None
|
||||||
AllowShortIfStatementsOnASingleLine: Always
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
AllowShortLambdasOnASingleLine: All
|
AllowShortLoopsOnASingleLine: false
|
||||||
AllowShortLoopsOnASingleLine: true
|
#AlwaysBreakAfterDefinitionReturnType: None
|
||||||
AlwaysBreakAfterReturnType: None
|
AlwaysBreakAfterReturnType: None
|
||||||
AlwaysBreakTemplateDeclarations: Yes
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
BreakBeforeBraces: Custom
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
BraceWrapping:
|
BraceWrapping:
|
||||||
AfterCaseLabel: false
|
AfterCaseLabel: true
|
||||||
AfterClass: false
|
AfterClass: true
|
||||||
AfterControlStatement: Never
|
AfterControlStatement: true
|
||||||
AfterEnum: false
|
AfterEnum: true
|
||||||
AfterFunction: false
|
AfterFunction: true
|
||||||
AfterNamespace: false
|
AfterNamespace: true
|
||||||
AfterUnion: false
|
AfterStruct: true
|
||||||
BeforeCatch: false
|
AfterUnion: true
|
||||||
BeforeElse: false
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
IndentBraces: false
|
IndentBraces: false
|
||||||
SplitEmptyFunction: false
|
# SplitEmptyFunction: true
|
||||||
SplitEmptyRecord: true
|
# SplitEmptyRecord: true
|
||||||
|
# SplitEmptyNamespace: true
|
||||||
BreakBeforeBinaryOperators: None
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
BreakBeforeTernaryOperators: true
|
BreakBeforeTernaryOperators: true
|
||||||
BreakConstructorInitializers: BeforeColon
|
#BreakConstructorInitializers: AfterColon
|
||||||
BreakInheritanceList: BeforeColon
|
BreakConstructorInitializersBeforeComma: false
|
||||||
ColumnLimit: 0
|
BreakStringLiterals: true
|
||||||
CompactNamespaces: false
|
ColumnLimit: 100
|
||||||
ContinuationIndentWidth: 8
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
IndentCaseLabels: true
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
IndentPPDirectives: None
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
ForEachMacros: [ foreach ]
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^(<|"(gtest|isl|json)/)'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
IncludeIsMainRegex: '$'
|
||||||
|
IndentCaseLabels: false
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
IndentWrappedFunctionNames: true
|
||||||
MaxEmptyLinesToKeep: 2
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
NamespaceIndentation: All
|
MacroBlockBegin: ''
|
||||||
ObjCSpaceAfterProperty: false
|
MacroBlockEnd: ''
|
||||||
ObjCSpaceBeforeProtocolList: true
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: None
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
ReflowComments: false
|
ReflowComments: true
|
||||||
SpaceAfterCStyleCast: true
|
SortIncludes: true
|
||||||
SpaceAfterLogicalNot: false
|
SpaceAfterCStyleCast: false
|
||||||
SpaceAfterTemplateKeyword: false
|
SpaceAfterTemplateKeyword: true
|
||||||
SpaceBeforeAssignmentOperators: true
|
SpaceBeforeAssignmentOperators: true
|
||||||
SpaceBeforeCpp11BracedList: false
|
SpaceBeforeParens: Always
|
||||||
SpaceBeforeCtorInitializerColon: true
|
|
||||||
SpaceBeforeInheritanceColon: true
|
|
||||||
SpaceBeforeParens: ControlStatements
|
|
||||||
SpaceBeforeRangeBasedForLoopColon: true
|
|
||||||
SpaceInEmptyParentheses: false
|
SpaceInEmptyParentheses: false
|
||||||
SpacesBeforeTrailingComments: 1
|
SpacesBeforeTrailingComments: 1
|
||||||
SpacesInAngles: false
|
SpacesInAngles: false
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
SpacesInCStyleCastParentheses: false
|
SpacesInCStyleCastParentheses: false
|
||||||
SpacesInContainerLiterals: false
|
|
||||||
SpacesInParentheses: false
|
SpacesInParentheses: false
|
||||||
SpacesInSquareBrackets: false
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Cpp11
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
UseTab: Never
|
UseTab: ForIndentation
|
||||||
|
...
|
||||||
|
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: clang-format
|
- name: clang-format
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src
|
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include
|
||||||
build-binary:
|
build-binary:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: clang-format
|
needs: clang-format
|
||||||
@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: create version.h
|
- name: create version.h
|
||||||
run: |
|
run: |
|
||||||
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||||
cat <<EOF > ./src/version.h
|
cat <<EOF > ./source/wiiu/version.h
|
||||||
#pragma once
|
#pragma once
|
||||||
#define VERSION_EXTRA " (nightly-$git_hash)"
|
#define VERSION_EXTRA " (nightly-$git_hash)"
|
||||||
EOF
|
EOF
|
||||||
@ -48,7 +48,7 @@ jobs:
|
|||||||
- name: zip artifact
|
- name: zip artifact
|
||||||
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wps
|
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wps
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: "softprops/action-gh-release@v1"
|
uses: "softprops/action-gh-release@v2"
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
|
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
|
||||||
draft: false
|
draft: false
|
||||||
|
15
.github/workflows/pr.yml
vendored
15
.github/workflows/pr.yml
vendored
@ -9,18 +9,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: clang-format
|
- name: clang-format
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src
|
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include
|
||||||
check-build-with-logging:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs: clang-format
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: build binary with logging
|
|
||||||
run: |
|
|
||||||
docker build . -t builder
|
|
||||||
docker run --rm -v ${PWD}:/project builder make DEBUG=VERBOSE
|
|
||||||
docker run --rm -v ${PWD}:/project builder make clean
|
|
||||||
docker run --rm -v ${PWD}:/project builder make DEBUG=1
|
|
||||||
build-binary:
|
build-binary:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: clang-format
|
needs: clang-format
|
||||||
@ -29,7 +18,7 @@ jobs:
|
|||||||
- name: create version.h
|
- name: create version.h
|
||||||
run: |
|
run: |
|
||||||
git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}")
|
git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}")
|
||||||
cat <<EOF > ./src/version.h
|
cat <<EOF > ./source/wiiu/version.h
|
||||||
#pragma once
|
#pragma once
|
||||||
#define VERSION_EXTRA " (nightly-$git_hash)"
|
#define VERSION_EXTRA " (nightly-$git_hash)"
|
||||||
EOF
|
EOF
|
||||||
|
35
.gitignore
vendored
35
.gitignore
vendored
@ -1,9 +1,30 @@
|
|||||||
build/*
|
*.nds
|
||||||
*.mod
|
*.nds.xz
|
||||||
sysapp.layout
|
*.3dsx
|
||||||
sysapp.cbp
|
*.3dsx.xz
|
||||||
|
*.cia
|
||||||
|
*.cia.xz
|
||||||
*.elf
|
*.elf
|
||||||
*.wps
|
*.nacp
|
||||||
|
*.nds
|
||||||
|
*.nro
|
||||||
|
*.nro.xz
|
||||||
|
*.nso
|
||||||
|
*.pfs0
|
||||||
|
*.smdh
|
||||||
|
.gdb_history
|
||||||
|
3ds/build
|
||||||
|
3ds-classic/build
|
||||||
|
3ds/romfs/*.t3x
|
||||||
|
linux/build
|
||||||
|
linux/ftpd
|
||||||
|
nds/build
|
||||||
|
switch/build
|
||||||
|
switch-classic/build
|
||||||
|
switch/romfs/*.zst
|
||||||
|
switch/romfs/shaders/*.dksh
|
||||||
.idea/
|
.idea/
|
||||||
cmake-build-debug/
|
build/
|
||||||
CMakeLists.txt
|
*.rpx
|
||||||
|
*.wuhb
|
||||||
|
*.wps
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM ghcr.io/wiiu-env/devkitppc:20230621
|
FROM ghcr.io/wiiu-env/devkitppc:20240423
|
||||||
|
|
||||||
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230719 /artifacts $DEVKITPRO
|
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240425 /artifacts $DEVKITPRO
|
||||||
COPY --from=ghcr.io/wiiu-env/libmocha:20230621 /artifacts $DEVKITPRO
|
COPY --from=ghcr.io/wiiu-env/libmocha:20231127 /artifacts $DEVKITPRO
|
||||||
|
|
||||||
WORKDIR project
|
WORKDIR project
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
19
LICENSE.txt
19
LICENSE.txt
@ -1,19 +0,0 @@
|
|||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
8
Makefile
8
Makefile
@ -11,6 +11,7 @@ TOPDIR ?= $(CURDIR)
|
|||||||
include $(DEVKITPRO)/wups/share/wups_rules
|
include $(DEVKITPRO)/wups/share/wups_rules
|
||||||
|
|
||||||
WUT_ROOT := $(DEVKITPRO)/wut
|
WUT_ROOT := $(DEVKITPRO)/wut
|
||||||
|
WUPS_ROOT := $(DEVKITPRO)/wups
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
# TARGET is the name of the output
|
# TARGET is the name of the output
|
||||||
@ -21,9 +22,9 @@ WUT_ROOT := $(DEVKITPRO)/wut
|
|||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
TARGET := ftpiiu
|
TARGET := ftpiiu
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := src src/utils
|
SOURCES := source source/wiiu
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := src
|
INCLUDES := source include
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
# options for code generation
|
# options for code generation
|
||||||
@ -31,7 +32,8 @@ INCLUDES := src
|
|||||||
CFLAGS := -Wall -O2 -ffunction-sections \
|
CFLAGS := -Wall -O2 -ffunction-sections \
|
||||||
$(MACHDEP)
|
$(MACHDEP)
|
||||||
|
|
||||||
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
|
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ -DSTATUS_STRING="\"ftpd v$(VERSION)\"" \
|
||||||
|
-DNO_IPV6 -DCLASSIC -DNO_CONSOLE -DFTPDCONFIG="\"/config/ftpd/ftpd.cfg\""
|
||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -std=gnu++20
|
CXXFLAGS := $(CFLAGS) -std=gnu++20
|
||||||
|
|
||||||
|
18
README.md
18
README.md
@ -1,5 +1,7 @@
|
|||||||
[![CI-Release](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml)
|
[![CI-Release](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/ftpiiu_plugin/actions/workflows/ci.yml)
|
||||||
|
|
||||||
|
# ftpiiu - A ftp server plugin for the Wii U based on ftpd
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
(`[ENVIRONMENT]` is a placeholder for the actual environment name.)
|
(`[ENVIRONMENT]` is a placeholder for the actual environment name.)
|
||||||
|
|
||||||
@ -21,16 +23,10 @@ Via the plugin config menu (press L, DPAD Down and Minus on the gamepad) you can
|
|||||||
- Allows you to access all system files. If this option is disabled, you can only access `/fs/vol/content`, `/fs/vol/save` and `/fs/vol/external01` (SD card). Changes take effect when so close the config menu, but the server may restart. (Default is false).
|
- Allows you to access all system files. If this option is disabled, you can only access `/fs/vol/content`, `/fs/vol/save` and `/fs/vol/external01` (SD card). Changes take effect when so close the config menu, but the server may restart. (Default is false).
|
||||||
- Additionally, the config menu will display the IP of your console and the port the server is running at.
|
- Additionally, the config menu will display the IP of your console and the port the server is running at.
|
||||||
|
|
||||||
## Buildflags
|
See the [ftpd repository](https://github.com/mtheall/ftpd?tab=readme-ov-file#supported-commands) for a list of all supported commands.
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`.
|
Logs will only appear in the system log (OSReport).
|
||||||
|
|
||||||
`make` Logs errors only (via OSReport).
|
|
||||||
`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
|
|
||||||
`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule).
|
|
||||||
|
|
||||||
If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it'll fallback to UDP (Port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging.
|
|
||||||
|
|
||||||
## Building using the Dockerfile
|
## Building using the Dockerfile
|
||||||
|
|
||||||
@ -49,4 +45,8 @@ docker run -it --rm -v ${PWD}:/project ftpiiuplugin-builder make clean
|
|||||||
|
|
||||||
## Format the code via docker
|
## Format the code via docker
|
||||||
|
|
||||||
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src -i`
|
`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include -i`
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This plugin is based on [ftpd](https://github.com/mtheall/ftpd) by mtheall
|
43
include/IOAbstraction.h
Normal file
43
include/IOAbstraction.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/dirent.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class IOAbstraction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::string convertPath (std::string_view inPath);
|
||||||
|
|
||||||
|
static FILE *fopen (const char *_name, const char *_type);
|
||||||
|
|
||||||
|
static int fseek (FILE *f, long pos, int origin);
|
||||||
|
|
||||||
|
static size_t fread (void *buffer, size_t _size, size_t _n, FILE *f);
|
||||||
|
|
||||||
|
static size_t fwrite (const void *buffer, size_t _size, size_t _n, FILE *f);
|
||||||
|
|
||||||
|
static int closedir (DIR *dirp);
|
||||||
|
|
||||||
|
static DIR *opendir (const char *dirname);
|
||||||
|
|
||||||
|
static struct dirent *readdir (DIR *dirp);
|
||||||
|
|
||||||
|
static int stat (const char *path, struct stat *sbuf);
|
||||||
|
|
||||||
|
static int lstat (const char *path, struct stat *buf);
|
||||||
|
|
||||||
|
static int mkdir (const char *path, mode_t mode);
|
||||||
|
|
||||||
|
static int rmdir (const char *path);
|
||||||
|
|
||||||
|
static int rename (const char *path, const char *path2);
|
||||||
|
|
||||||
|
static int unlink (const char *path);
|
||||||
|
|
||||||
|
static void addVirtualPath (const std::string &virtualPath,
|
||||||
|
const std::vector<std::string> &subDirectories);
|
||||||
|
|
||||||
|
static void clear ();
|
||||||
|
};
|
177
include/fs.h
Normal file
177
include/fs.h
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ioBuffer.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace fs
|
||||||
|
{
|
||||||
|
/// \brief Print size in human-readable format (KiB, MiB, etc)
|
||||||
|
/// \param size_ Size to print
|
||||||
|
std::string printSize (std::uint64_t size_);
|
||||||
|
|
||||||
|
/// \brief File I/O object
|
||||||
|
class File
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~File ();
|
||||||
|
|
||||||
|
File ();
|
||||||
|
|
||||||
|
File (File const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
File (File &&that_);
|
||||||
|
|
||||||
|
File &operator= (File const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
File &operator= (File &&that_);
|
||||||
|
|
||||||
|
/// \brief bool cast operator
|
||||||
|
explicit operator bool () const;
|
||||||
|
|
||||||
|
/// \brief std::FILE* cast operator
|
||||||
|
operator std::FILE * () const;
|
||||||
|
|
||||||
|
/// \brief Set buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
|
void setBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Open file
|
||||||
|
/// \param path_ Path to open
|
||||||
|
/// \param mode_ Access mode (\sa std::fopen)
|
||||||
|
bool open (char const *path_, char const *mode_ = "rb");
|
||||||
|
|
||||||
|
/// \brief Close file
|
||||||
|
void close ();
|
||||||
|
|
||||||
|
/// \brief Seek to file position
|
||||||
|
/// \param pos_ File position
|
||||||
|
/// \param origin_ Reference position (\sa std::fseek)
|
||||||
|
std::make_signed_t<std::size_t> seek (std::size_t pos_, int origin_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \note Can return partial reads
|
||||||
|
std::make_signed_t<std::size_t> read (void *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \note Can return partial reads
|
||||||
|
std::make_signed_t<std::size_t> read (IOBuffer &buffer_);
|
||||||
|
|
||||||
|
/// \brief Read line
|
||||||
|
std::string_view readLine ();
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \note Fails on partial reads and errors
|
||||||
|
bool readAll (void *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input data
|
||||||
|
/// \param size_ Size to write
|
||||||
|
/// \note Can return partial writes
|
||||||
|
std::make_signed_t<std::size_t> write (void const *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input data
|
||||||
|
/// \note Can return partial writes
|
||||||
|
std::make_signed_t<std::size_t> write (IOBuffer &buffer_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input data
|
||||||
|
/// \param size_ Size to write
|
||||||
|
/// \note Fails on partials writes and errors
|
||||||
|
bool writeAll (void const *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Underlying std::FILE*
|
||||||
|
std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr};
|
||||||
|
|
||||||
|
/// \brief Buffer
|
||||||
|
std::unique_ptr<char[]> m_buffer;
|
||||||
|
|
||||||
|
/// \brief Buffer size
|
||||||
|
std::size_t m_bufferSize = 0;
|
||||||
|
|
||||||
|
/// \brief Line buffer
|
||||||
|
char *m_lineBuffer = nullptr;
|
||||||
|
|
||||||
|
/// \brief Line buffer size
|
||||||
|
std::size_t m_lineBufferSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Directory object
|
||||||
|
class Dir
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~Dir ();
|
||||||
|
|
||||||
|
Dir ();
|
||||||
|
|
||||||
|
Dir (Dir const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
Dir (Dir &&that_);
|
||||||
|
|
||||||
|
Dir &operator= (Dir const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
Dir &operator= (Dir &&that_);
|
||||||
|
|
||||||
|
/// \brief bool cast operator
|
||||||
|
explicit operator bool () const;
|
||||||
|
|
||||||
|
/// \brief DIR* cast operator
|
||||||
|
operator DIR * () const;
|
||||||
|
|
||||||
|
/// \brief Open directory
|
||||||
|
/// \param path_ Path to open
|
||||||
|
bool open (char const *const path_);
|
||||||
|
|
||||||
|
/// \brief Close directory
|
||||||
|
void close ();
|
||||||
|
|
||||||
|
/// \brief Read a directory entry
|
||||||
|
/// \note Returns nullptr on end-of-directory or error; check errno
|
||||||
|
struct dirent *read ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Underlying DIR*
|
||||||
|
std::unique_ptr<DIR, int (*) (DIR *)> m_dp{nullptr, nullptr};
|
||||||
|
};
|
||||||
|
}
|
148
include/ftpConfig.h
Normal file
148
include/ftpConfig.h
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class FtpConfig;
|
||||||
|
using UniqueFtpConfig = std::unique_ptr<FtpConfig>;
|
||||||
|
|
||||||
|
/// \brief FTP config
|
||||||
|
class FtpConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~FtpConfig ();
|
||||||
|
|
||||||
|
/// \brief Create config
|
||||||
|
static UniqueFtpConfig create ();
|
||||||
|
|
||||||
|
/// \brief Load config
|
||||||
|
/// \param path_ Path to config file
|
||||||
|
static UniqueFtpConfig load (char const *path_);
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
std::scoped_lock<platform::Mutex> lockGuard ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Save config
|
||||||
|
/// \param path_ Path to config file
|
||||||
|
bool save (char const *path_);
|
||||||
|
|
||||||
|
/// \brief Get user
|
||||||
|
std::string const &user () const;
|
||||||
|
|
||||||
|
/// \brief Get password
|
||||||
|
std::string const &pass () const;
|
||||||
|
|
||||||
|
/// \brief Get port
|
||||||
|
std::uint16_t port () const;
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief Whether to get mtime
|
||||||
|
/// \note only effective on 3DS
|
||||||
|
bool getMTime () const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Whether to enable access point
|
||||||
|
bool enableAP () const;
|
||||||
|
|
||||||
|
/// \brief Access point SSID
|
||||||
|
std::string const &ssid () const;
|
||||||
|
|
||||||
|
/// \brief Access point passphrase
|
||||||
|
std::string const &passphrase () const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Set user
|
||||||
|
/// \param user_ User
|
||||||
|
void setUser (std::string const &user_);
|
||||||
|
|
||||||
|
/// \brief Set password
|
||||||
|
/// \param pass_ Password
|
||||||
|
void setPass (std::string const &pass_);
|
||||||
|
|
||||||
|
/// \brief Set listen port
|
||||||
|
/// \param port_ Listen port
|
||||||
|
bool setPort (std::string const &port_);
|
||||||
|
|
||||||
|
/// \brief Set listen port
|
||||||
|
/// \param port_ Listen port
|
||||||
|
bool setPort (std::uint16_t port_);
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief Set whether to get mtime
|
||||||
|
/// \param getMTime_ Whether to get mtime
|
||||||
|
void setGetMTime (bool getMTime_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Set whether to enable access point
|
||||||
|
/// \param enable_ Whether to enable access point
|
||||||
|
void setEnableAP (bool enable_);
|
||||||
|
|
||||||
|
/// \brief Set access point SSID
|
||||||
|
/// \param ssid_ SSID
|
||||||
|
void setSSID (std::string const &ssid_);
|
||||||
|
|
||||||
|
/// \brief Set access point passphrase
|
||||||
|
/// \param passphrase_ Passphrase
|
||||||
|
void setPassphrase (std::string const &passphrase_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
FtpConfig ();
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Mutex
|
||||||
|
mutable platform::Mutex m_lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Username
|
||||||
|
std::string m_user;
|
||||||
|
|
||||||
|
/// \brief Password
|
||||||
|
std::string m_pass;
|
||||||
|
|
||||||
|
/// \brief Listen port
|
||||||
|
std::uint16_t m_port;
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief Whether to get mtime
|
||||||
|
bool m_getMTime = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Whether to enable access point
|
||||||
|
bool m_enableAP = false;
|
||||||
|
|
||||||
|
/// \brief Access point SSID
|
||||||
|
std::string m_ssid = "ftpd";
|
||||||
|
|
||||||
|
/// \brief Access point passphrase
|
||||||
|
std::string m_passphrase;
|
||||||
|
#endif
|
||||||
|
};
|
168
include/ftpServer.h
Normal file
168
include/ftpServer.h
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ftpConfig.h"
|
||||||
|
#include "ftpSession.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class FtpServer;
|
||||||
|
using UniqueFtpServer = std::unique_ptr<FtpServer>;
|
||||||
|
|
||||||
|
/// \brief FTP server
|
||||||
|
class FtpServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~FtpServer ();
|
||||||
|
|
||||||
|
/// \brief Draw server and all of its sessions
|
||||||
|
void draw ();
|
||||||
|
|
||||||
|
/// \brief Create server
|
||||||
|
static UniqueFtpServer create ();
|
||||||
|
|
||||||
|
/// \brief Get free space
|
||||||
|
static std::string getFreeSpace ();
|
||||||
|
|
||||||
|
/// \brief Update free space
|
||||||
|
static void updateFreeSpace ();
|
||||||
|
|
||||||
|
/// \brief Server start time
|
||||||
|
static std::time_t startTime ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Paramterized constructor
|
||||||
|
/// \param config_ FTP config
|
||||||
|
FtpServer (UniqueFtpConfig config_);
|
||||||
|
|
||||||
|
/// \brief Handle when network is found
|
||||||
|
void handleNetworkFound ();
|
||||||
|
|
||||||
|
/// \brief Handle when network is lost
|
||||||
|
void handleNetworkLost ();
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
/// \brief Show menu in the current window
|
||||||
|
void showMenu ();
|
||||||
|
|
||||||
|
/// \brief Show settings menu
|
||||||
|
void showSettings ();
|
||||||
|
|
||||||
|
/// \brief Show about window
|
||||||
|
void showAbout ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Server loop
|
||||||
|
void loop ();
|
||||||
|
|
||||||
|
/// \brief Thread entry point
|
||||||
|
void threadFunc ();
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Thread
|
||||||
|
platform::Thread m_thread;
|
||||||
|
|
||||||
|
/// \brief Mutex
|
||||||
|
platform::Mutex m_lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Config
|
||||||
|
UniqueFtpConfig m_config;
|
||||||
|
|
||||||
|
/// \brief Listen socket
|
||||||
|
UniqueSocket m_socket;
|
||||||
|
|
||||||
|
/// \brief ImGui window name
|
||||||
|
std::string m_name;
|
||||||
|
|
||||||
|
/// \brief Sessions
|
||||||
|
std::vector<UniqueFtpSession> m_sessions;
|
||||||
|
|
||||||
|
/// \brief Whether thread should quit
|
||||||
|
std::atomic<bool> m_quit;
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
/// \brief Log upload cURL context
|
||||||
|
CURLM *m_uploadLogCurlM = nullptr;
|
||||||
|
/// \brief Log upload mime context
|
||||||
|
curl_mime *m_uploadLogMime = nullptr;
|
||||||
|
/// \brief Log upload cURL context
|
||||||
|
std::atomic<CURL *> m_uploadLogCurl = nullptr;
|
||||||
|
|
||||||
|
/// \brief Log upload data
|
||||||
|
std::string m_uploadLogData;
|
||||||
|
/// \brief Log upload result
|
||||||
|
std::string m_uploadLogResult;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
/// \brief Whether to show settings menu
|
||||||
|
bool m_showSettings = false;
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Whether to show access point menu
|
||||||
|
bool m_showAP = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Whether to show about window
|
||||||
|
bool m_showAbout = false;
|
||||||
|
|
||||||
|
/// \brief User name setting
|
||||||
|
std::string m_userSetting;
|
||||||
|
|
||||||
|
/// \brief Password setting
|
||||||
|
std::string m_passSetting;
|
||||||
|
|
||||||
|
/// \brief Port setting
|
||||||
|
std::uint16_t m_portSetting;
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief getMTime setting
|
||||||
|
bool m_getMTimeSetting;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Whether an error occurred enabling access point
|
||||||
|
std::atomic<bool> m_apError = false;
|
||||||
|
|
||||||
|
/// \brief Enable access point setting
|
||||||
|
bool m_enableAPSetting;
|
||||||
|
|
||||||
|
/// \brief Access point SSID setting
|
||||||
|
std::string m_ssidSetting;
|
||||||
|
|
||||||
|
/// \brief Access point passphrase setting
|
||||||
|
std::string m_passphraseSetting;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
};
|
475
include/ftpSession.h
Normal file
475
include/ftpSession.h
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2023 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fs.h"
|
||||||
|
#include "ftpConfig.h"
|
||||||
|
#include "ioBuffer.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class FtpSession;
|
||||||
|
using UniqueFtpSession = std::unique_ptr<FtpSession>;
|
||||||
|
|
||||||
|
/// \brief FTP session
|
||||||
|
class FtpSession
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~FtpSession ();
|
||||||
|
|
||||||
|
/// \brief Whether session sockets are all inactive
|
||||||
|
bool dead ();
|
||||||
|
|
||||||
|
/// \brief Draw session status
|
||||||
|
void draw ();
|
||||||
|
|
||||||
|
/// \brief Create session
|
||||||
|
/// \param config_ FTP config
|
||||||
|
/// \param commandSocket_ Command socket
|
||||||
|
static UniqueFtpSession create (FtpConfig &config_, UniqueSocket commandSocket_);
|
||||||
|
|
||||||
|
/// \brief Poll for activity
|
||||||
|
/// \param sessions_ Sessions to poll
|
||||||
|
static bool poll (std::vector<UniqueFtpSession> const &sessions_);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Command buffer size
|
||||||
|
constexpr static auto COMMAND_BUFFERSIZE = 4096;
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
/// \brief Response buffer size
|
||||||
|
constexpr static auto RESPONSE_BUFFERSIZE = 4096;
|
||||||
|
|
||||||
|
/// \brief Transfer buffersize
|
||||||
|
constexpr static auto XFER_BUFFERSIZE = 8192;
|
||||||
|
#else
|
||||||
|
/// \brief Response buffer size
|
||||||
|
constexpr static auto RESPONSE_BUFFERSIZE = 16 * 1024;
|
||||||
|
|
||||||
|
/// \brief Transfer buffersize
|
||||||
|
constexpr static auto XFER_BUFFERSIZE = 32 * 1024;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief File buffersize
|
||||||
|
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
|
||||||
|
|
||||||
|
#if defined(NDS)
|
||||||
|
/// \brief Socket buffer size
|
||||||
|
constexpr static auto SOCK_BUFFERSIZE = 4096;
|
||||||
|
|
||||||
|
/// \brief Amount of file position history to keep
|
||||||
|
constexpr static auto POSITION_HISTORY = 60;
|
||||||
|
#elif defined(__3DS__)
|
||||||
|
/// \brief Socket buffer size
|
||||||
|
constexpr static auto SOCK_BUFFERSIZE = 32768;
|
||||||
|
|
||||||
|
/// \brief Amount of file position history to keep
|
||||||
|
constexpr static auto POSITION_HISTORY = 100;
|
||||||
|
#else
|
||||||
|
/// \brief Socket buffer size
|
||||||
|
constexpr static auto SOCK_BUFFERSIZE = XFER_BUFFERSIZE;
|
||||||
|
|
||||||
|
/// \brief Amount of file position history to keep
|
||||||
|
constexpr static auto POSITION_HISTORY = 300;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Session state
|
||||||
|
enum class State
|
||||||
|
{
|
||||||
|
COMMAND,
|
||||||
|
DATA_CONNECT,
|
||||||
|
DATA_TRANSFER,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Transfer file mode
|
||||||
|
enum class XferFileMode
|
||||||
|
{
|
||||||
|
RETR,
|
||||||
|
STOR,
|
||||||
|
APPE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Transfer directory mode
|
||||||
|
enum class XferDirMode
|
||||||
|
{
|
||||||
|
LIST,
|
||||||
|
MLSD,
|
||||||
|
MLST,
|
||||||
|
NLST,
|
||||||
|
STAT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param config_ FTP config
|
||||||
|
/// \param commandSocket_ Command socket
|
||||||
|
FtpSession (FtpConfig &config_, UniqueSocket commandSocket_);
|
||||||
|
|
||||||
|
/// \brief Whether session is authorized
|
||||||
|
bool authorized () const;
|
||||||
|
|
||||||
|
/// \brief Set session state
|
||||||
|
/// \param state_ State to set
|
||||||
|
/// \param closePasv_ Whether to close listening socket
|
||||||
|
/// \param closeData_ Whether to close data socket
|
||||||
|
void setState (State state_, bool closePasv_, bool closeData_);
|
||||||
|
|
||||||
|
/// \brief Close socket
|
||||||
|
/// \param socket_ Socket to close
|
||||||
|
void closeSocket (SharedSocket &socket_);
|
||||||
|
/// \brief Close command socket
|
||||||
|
void closeCommand ();
|
||||||
|
/// \brief Close passive socket
|
||||||
|
void closePasv ();
|
||||||
|
/// \brief Close data socket
|
||||||
|
void closeData ();
|
||||||
|
|
||||||
|
/// \brief Change working directory
|
||||||
|
bool changeDir (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Accept connection as data socket
|
||||||
|
bool dataAccept ();
|
||||||
|
|
||||||
|
/// \brief Connect data socket
|
||||||
|
bool dataConnect ();
|
||||||
|
|
||||||
|
/// \brief Fill directory entry
|
||||||
|
/// \param st_ Entry status
|
||||||
|
/// \param path_ Path name
|
||||||
|
/// \param type_ MLST type
|
||||||
|
int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr);
|
||||||
|
|
||||||
|
/// \brief Fill directory entry
|
||||||
|
/// \param path_ Path name
|
||||||
|
/// \param type_ MLST type
|
||||||
|
int fillDirent (std::string const &path_, char const *type_ = nullptr);
|
||||||
|
|
||||||
|
/// \brief Transfer file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \param mode_ Transfer file mode
|
||||||
|
void xferFile (char const *args_, XferFileMode mode_);
|
||||||
|
|
||||||
|
/// \brief Transfer directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \param mode_ Transfer directory mode
|
||||||
|
/// \param workaround_ Workaround broken clients who use LIST -a/-l
|
||||||
|
void xferDir (char const *args_, XferDirMode mode_, bool workaround_);
|
||||||
|
|
||||||
|
/// \brief Read command
|
||||||
|
/// \param events_ Poll events
|
||||||
|
void readCommand (int events_);
|
||||||
|
|
||||||
|
/// \brief Write response
|
||||||
|
void writeResponse ();
|
||||||
|
|
||||||
|
/// \brief Send response
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 2, 3))) void sendResponse (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Send response
|
||||||
|
/// \param response_ Response message
|
||||||
|
void sendResponse (std::string_view response_);
|
||||||
|
|
||||||
|
/// \brief Transfer function
|
||||||
|
bool (FtpSession::*m_transfer) () = nullptr;
|
||||||
|
|
||||||
|
/// \brief Transfer directory list
|
||||||
|
bool listTransfer ();
|
||||||
|
|
||||||
|
/// \brief Transfer download
|
||||||
|
bool retrieveTransfer ();
|
||||||
|
|
||||||
|
/// \brief Transfer upload
|
||||||
|
bool storeTransfer ();
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Mutex
|
||||||
|
platform::Mutex m_lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief FTP config
|
||||||
|
FtpConfig &m_config;
|
||||||
|
|
||||||
|
/// \brief Command socket
|
||||||
|
SharedSocket m_commandSocket;
|
||||||
|
|
||||||
|
/// \brief Data listen socker
|
||||||
|
UniqueSocket m_pasvSocket;
|
||||||
|
|
||||||
|
/// \brief Data socket
|
||||||
|
SharedSocket m_dataSocket;
|
||||||
|
|
||||||
|
/// \brief Sockets pending close
|
||||||
|
std::vector<SharedSocket> m_pendingCloseSocket;
|
||||||
|
|
||||||
|
/// \brief Command buffer
|
||||||
|
IOBuffer m_commandBuffer;
|
||||||
|
|
||||||
|
/// \brief Response buffer
|
||||||
|
IOBuffer m_responseBuffer;
|
||||||
|
|
||||||
|
/// \brief Transfer buffer
|
||||||
|
IOBuffer m_xferBuffer;
|
||||||
|
|
||||||
|
/// \brief Address from last PORT command
|
||||||
|
SockAddr m_portAddr;
|
||||||
|
|
||||||
|
/// \brief Current working directory
|
||||||
|
std::string m_cwd = "/fs/vol/external01/";
|
||||||
|
|
||||||
|
/// \brief List working directory
|
||||||
|
std::string m_lwd;
|
||||||
|
|
||||||
|
/// \brief Path from RNFR command
|
||||||
|
std::string m_rename;
|
||||||
|
|
||||||
|
/// \brief Current work item
|
||||||
|
std::string m_workItem;
|
||||||
|
|
||||||
|
/// \brief ImGui window name
|
||||||
|
std::string m_windowName;
|
||||||
|
|
||||||
|
/// \brief ImGui plot widget name
|
||||||
|
std::string m_plotName;
|
||||||
|
|
||||||
|
/// \brief Position from REST command
|
||||||
|
std::uint64_t m_restartPosition = 0;
|
||||||
|
|
||||||
|
/// \brief Current file position
|
||||||
|
std::uint64_t m_filePosition = 0;
|
||||||
|
|
||||||
|
/// \brief File size of current transfer
|
||||||
|
std::uint64_t m_fileSize = 0;
|
||||||
|
|
||||||
|
/// \brief Last file position update timestamp
|
||||||
|
platform::steady_clock::time_point m_filePositionTime;
|
||||||
|
|
||||||
|
/// \brief File position history
|
||||||
|
std::uint64_t m_filePositionHistory[POSITION_HISTORY];
|
||||||
|
|
||||||
|
/// \brief File position history deltas
|
||||||
|
float m_filePositionDeltas[POSITION_HISTORY];
|
||||||
|
|
||||||
|
/// \brief Transfer rate (EWMA low-pass filtered)
|
||||||
|
float m_xferRate;
|
||||||
|
|
||||||
|
/// \brief Session state
|
||||||
|
State m_state = State::COMMAND;
|
||||||
|
|
||||||
|
/// \brief File being transferred
|
||||||
|
fs::File m_file;
|
||||||
|
|
||||||
|
/// \brief Directory being transferred
|
||||||
|
fs::Dir m_dir;
|
||||||
|
|
||||||
|
/// \brief Directory transfer mode
|
||||||
|
XferDirMode m_xferDirMode;
|
||||||
|
|
||||||
|
/// \brief Last command timestamp
|
||||||
|
time_t m_timestamp;
|
||||||
|
|
||||||
|
/// \brief Whether user has been authorized
|
||||||
|
bool m_authorizedUser : 1;
|
||||||
|
/// \brief Whether password has been authorized
|
||||||
|
bool m_authorizedPass : 1;
|
||||||
|
/// \brief Whether previous command was PASV
|
||||||
|
bool m_pasv : 1;
|
||||||
|
/// \brief Whether previous command was PORT
|
||||||
|
bool m_port : 1;
|
||||||
|
/// \brief Whether receiving data
|
||||||
|
bool m_recv : 1;
|
||||||
|
/// \brief Whether sending data
|
||||||
|
bool m_send : 1;
|
||||||
|
/// \brief Whether urgent (out-of-band) data is on the way
|
||||||
|
bool m_urgent : 1;
|
||||||
|
|
||||||
|
/// \brief Whether MLST type fact is enabled
|
||||||
|
bool m_mlstType : 1;
|
||||||
|
/// \brief Whether MLST size fact is enabled
|
||||||
|
bool m_mlstSize : 1;
|
||||||
|
/// \brief Whether MLST modify fact is enabled
|
||||||
|
bool m_mlstModify : 1;
|
||||||
|
/// \brief Whether MLST perm fact is enabled
|
||||||
|
bool m_mlstPerm : 1;
|
||||||
|
/// \brief Whether MLST unix.mode fact is enabled
|
||||||
|
bool m_mlstUnixMode : 1;
|
||||||
|
|
||||||
|
/// \brief Whether emulating /dev/zero
|
||||||
|
bool m_devZero : 1;
|
||||||
|
|
||||||
|
/// \brief Abort a transfer
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void ABOR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Allocate space
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void ALLO (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Append data to a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void APPE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief CWD to parent directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void CDUP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Change working directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void CWD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Delete a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void DELE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief List server features
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void FEAT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Print server help
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void HELP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief List directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void LIST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Last modification time
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void MDTM (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Create a directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void MKD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Machine list directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void MLSD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Machine list
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void MLST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set transfer mode
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void MODE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Name list
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void NLST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief No-op
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void NOOP (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set server options
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void OPTS (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Password
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void PASS (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Request an address to connect to for data transfers
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void PASV (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Provide an address to connect to for data transfers
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void PORT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Print working directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void PWD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Terminate session
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void QUIT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Restart a file transfer
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void REST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Retrieve a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \note Requires a PASV or PORT connection
|
||||||
|
void RETR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Remove a directory
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void RMD (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Rename from
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void RNFR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Rename to
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void RNTO (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Site command
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void SITE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Get file size
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void SIZE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Get status
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
/// \note If no argument is supplied, and a transfer is occurring, get the current transfer
|
||||||
|
/// status. If no argument is supplied, and no transfer is occurring, get the server status. If
|
||||||
|
/// an argument is supplied, this is equivalent to LIST, except the data is sent over the
|
||||||
|
/// command socket.
|
||||||
|
void STAT (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Store a file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void STOR (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Store a unique file
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void STOU (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set file structure
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void STRU (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Identify system
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void SYST (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Set representation type
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void TYPE (char const *args_);
|
||||||
|
|
||||||
|
/// \brief User name
|
||||||
|
/// \param args_ Command arguments
|
||||||
|
void USER (char const *args_);
|
||||||
|
|
||||||
|
/// \brief Map of command handlers
|
||||||
|
static std::vector<std::pair<std::string_view, void (FtpSession::*) (char const *)>> const
|
||||||
|
handlers;
|
||||||
|
};
|
88
include/ioBuffer.h
Normal file
88
include/ioBuffer.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/// \brief I/O buffer
|
||||||
|
/// [unusable][usedArea][freeArea]
|
||||||
|
class IOBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~IOBuffer ();
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param size_ Buffer size
|
||||||
|
IOBuffer (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Get pointer to writable area
|
||||||
|
char *freeArea () const;
|
||||||
|
/// \brief Get size of writable area
|
||||||
|
std::size_t freeSize () const;
|
||||||
|
|
||||||
|
/// \brief Get pointer to readable area
|
||||||
|
char *usedArea () const;
|
||||||
|
/// \brief Get size of readable area
|
||||||
|
std::size_t usedSize () const;
|
||||||
|
|
||||||
|
/// \brief Consume data from the beginning of usedArea
|
||||||
|
/// \param size_ Size to consume
|
||||||
|
/// [unusable][+++++usedArea][freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [unusable+++++][usedArea][freeArea]
|
||||||
|
void markFree (std::size_t size_);
|
||||||
|
/// \brief Produce data to the end of usedArea from freeArea
|
||||||
|
/// [unusable][usedArea][++++++freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [unusable][usedArea++++++][freeArea]
|
||||||
|
void markUsed (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Whether usedArea is empty
|
||||||
|
bool empty () const;
|
||||||
|
|
||||||
|
/// \brief Get buffer capacity
|
||||||
|
std::size_t capacity () const;
|
||||||
|
|
||||||
|
/// \brief Clear buffer; usedArea becomes empty
|
||||||
|
/// [unusable][usedArea][++++++freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [freeArea++++++++++++++++++++++++++]
|
||||||
|
void clear ();
|
||||||
|
|
||||||
|
/// \brief Move usedArea to the beginning of the buffer
|
||||||
|
/// [unusable][usedArea][freeArea]
|
||||||
|
/// becomes
|
||||||
|
/// [usedArea][freeArea++++++++++]
|
||||||
|
void coalesce ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Buffer
|
||||||
|
std::unique_ptr<char[]> m_buffer;
|
||||||
|
|
||||||
|
/// \brief Buffer size
|
||||||
|
std::size_t const m_size;
|
||||||
|
|
||||||
|
/// \brief Start of usedArea
|
||||||
|
std::size_t m_start = 0;
|
||||||
|
/// \brief Start of freeArea
|
||||||
|
std::size_t m_end = 0;
|
||||||
|
};
|
74
include/log.h
Normal file
74
include/log.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
/// \brief Log level
|
||||||
|
enum LogLevel
|
||||||
|
{
|
||||||
|
DEBUGLOG,
|
||||||
|
INFO,
|
||||||
|
ERROR,
|
||||||
|
COMMAND,
|
||||||
|
RESPONSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Draw log
|
||||||
|
void drawLog ();
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
/// \brief Get log
|
||||||
|
std::string getLog ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Add debug message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 1, 2))) void debug (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add info message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 1, 2))) void info (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add error message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 1, 2))) void error (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add command message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 1, 2))) void command (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add response message to bound log
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
__attribute__ ((format (printf, 1, 2))) void response (char const *fmt_, ...);
|
||||||
|
|
||||||
|
/// \brief Add log message to bound log
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param fmt_ Message format
|
||||||
|
/// \param ap_ Message arguments
|
||||||
|
void addLog (LogLevel level_, char const *fmt_, va_list ap_);
|
||||||
|
|
||||||
|
/// \brief Add log message to bound log
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param message_ Message to log
|
||||||
|
void addLog (LogLevel level_, std::string_view message_);
|
171
include/platform.h
Normal file
171
include/platform.h
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sockAddr.h"
|
||||||
|
|
||||||
|
#if defined(NDS)
|
||||||
|
#include <nds.h>
|
||||||
|
#elif defined(__3DS__)
|
||||||
|
#include <3ds.h>
|
||||||
|
#elif defined(__SWITCH__)
|
||||||
|
#include <switch.h>
|
||||||
|
#elif defined(__WIIU__)
|
||||||
|
#include <coreinit/debug.h>
|
||||||
|
#include <wut.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#if defined(CLASSIC) && !defined(__WIIU__)
|
||||||
|
extern PrintConsole g_statusConsole;
|
||||||
|
extern PrintConsole g_logConsole;
|
||||||
|
extern PrintConsole g_sessionConsole;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace platform
|
||||||
|
{
|
||||||
|
/// \brief Initialize platform
|
||||||
|
bool init ();
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
/// \brief Enable access point
|
||||||
|
/// \param enable_ Whether to enable access point
|
||||||
|
/// \param ssid_ SSID
|
||||||
|
/// \param passphrase_ Passphrase
|
||||||
|
bool enableAP (bool enable_, std::string const &ssid_, std::string const &passphrase_);
|
||||||
|
|
||||||
|
/// \brief Check if SSID is valid
|
||||||
|
/// \param ssid_ SSID to check
|
||||||
|
/// \returns empty string on success, error message on failure
|
||||||
|
char const *validateSSID (std::string const &ssid_);
|
||||||
|
|
||||||
|
/// \brief Check if passphrase is valid
|
||||||
|
/// \param passphrase_ Passphrase to check
|
||||||
|
/// \returns empty string on success, error message on failure
|
||||||
|
char const *validatePassphrase (std::string const &passphrase_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Whether network is visible
|
||||||
|
bool networkVisible ();
|
||||||
|
|
||||||
|
/// \brief Get network address
|
||||||
|
/// \param[out] addr_ Network address
|
||||||
|
bool networkAddress (SockAddr &addr_);
|
||||||
|
|
||||||
|
/// \brief Platform loop
|
||||||
|
bool loop ();
|
||||||
|
|
||||||
|
/// \brief Platform render
|
||||||
|
void render ();
|
||||||
|
|
||||||
|
/// \brief Deinitialize platform
|
||||||
|
void exit ();
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief Steady clock
|
||||||
|
struct steady_clock
|
||||||
|
{
|
||||||
|
/// \brief Type representing number of ticks
|
||||||
|
using rep = std::uint64_t;
|
||||||
|
|
||||||
|
/// \brief Type representing ratio of clock period in seconds
|
||||||
|
using period = std::ratio<1, SYSCLOCK_ARM11>;
|
||||||
|
|
||||||
|
/// \brief Duration type
|
||||||
|
using duration = std::chrono::duration<rep, period>;
|
||||||
|
|
||||||
|
/// \brief Timestamp type
|
||||||
|
using time_point = std::chrono::time_point<steady_clock>;
|
||||||
|
|
||||||
|
/// \brief Whether clock is steady
|
||||||
|
constexpr static bool is_steady = true;
|
||||||
|
|
||||||
|
/// \brief Current timestamp
|
||||||
|
static time_point now () noexcept;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
/// \brief Steady clock
|
||||||
|
using steady_clock = std::chrono::steady_clock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Platform thread
|
||||||
|
class Thread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~Thread ();
|
||||||
|
Thread ();
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entrypoint
|
||||||
|
Thread (std::function<void ()> &&func_);
|
||||||
|
|
||||||
|
Thread (Thread const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
Thread (Thread &&that_);
|
||||||
|
|
||||||
|
Thread &operator= (Thread const &that_) = delete;
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
Thread &operator= (Thread &&that_);
|
||||||
|
|
||||||
|
/// \brief Join thread
|
||||||
|
void join ();
|
||||||
|
|
||||||
|
/// \brief Suspend current thread
|
||||||
|
/// \param timeout_ Minimum time to sleep
|
||||||
|
static void sleep (std::chrono::milliseconds timeout_);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class privateData_t;
|
||||||
|
|
||||||
|
/// \brief pimpl
|
||||||
|
std::unique_ptr<privateData_t> m_d;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Platform mutex
|
||||||
|
class Mutex
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~Mutex ();
|
||||||
|
Mutex ();
|
||||||
|
|
||||||
|
/// \brief Lock mutex
|
||||||
|
void lock ();
|
||||||
|
|
||||||
|
/// \brief Unlock mutex
|
||||||
|
void unlock ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class privateData_t;
|
||||||
|
|
||||||
|
/// \brief pimpl
|
||||||
|
std::unique_ptr<privateData_t> m_d;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
}
|
116
include/sockAddr.h
Normal file
116
include/sockAddr.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
struct sockaddr_storage
|
||||||
|
{
|
||||||
|
unsigned short ss_family;
|
||||||
|
char ss_data[sizeof (struct sockaddr_in) - sizeof (unsigned short)];
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Socket address
|
||||||
|
class SockAddr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~SockAddr ();
|
||||||
|
|
||||||
|
SockAddr ();
|
||||||
|
|
||||||
|
/// \brief Copy constructor
|
||||||
|
/// \param that_ Object to copy
|
||||||
|
SockAddr (SockAddr const &that_);
|
||||||
|
|
||||||
|
/// \brief Move constructor
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
SockAddr (SockAddr &&that_);
|
||||||
|
|
||||||
|
/// \brief Copy assignment
|
||||||
|
/// \param that_ Object to copy
|
||||||
|
SockAddr &operator= (SockAddr const &that_);
|
||||||
|
|
||||||
|
/// \brief Move assignment
|
||||||
|
/// \param that_ Object to move from
|
||||||
|
SockAddr &operator= (SockAddr &&that_);
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
|
SockAddr (struct sockaddr const &addr_);
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
|
SockAddr (struct sockaddr_in const &addr_);
|
||||||
|
|
||||||
|
#ifndef __3DS__
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
|
SockAddr (struct sockaddr_in6 const &addr_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \param Parameterized constructor
|
||||||
|
/// \param addr_ Address
|
||||||
|
SockAddr (struct sockaddr_storage const &addr_);
|
||||||
|
|
||||||
|
/// \param sockaddr_in cast operator
|
||||||
|
operator struct sockaddr_in const & () const;
|
||||||
|
|
||||||
|
#ifndef __3DS__
|
||||||
|
/// \param sockaddr_in6 cast operator
|
||||||
|
operator struct sockaddr_in6 const & () const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \param sockaddr_storage cast operator
|
||||||
|
operator struct sockaddr_storage const & () const;
|
||||||
|
|
||||||
|
/// \param sockaddr* cast operator
|
||||||
|
operator struct sockaddr * ();
|
||||||
|
/// \param sockaddr const* cast operator
|
||||||
|
operator struct sockaddr const * () const;
|
||||||
|
|
||||||
|
/// \brief Address port
|
||||||
|
std::uint16_t port () const;
|
||||||
|
|
||||||
|
/// \brief Set address port
|
||||||
|
/// \param port_ Port to set
|
||||||
|
bool setPort (std::uint16_t port_);
|
||||||
|
|
||||||
|
/// \brief Address name
|
||||||
|
/// \param buffer_ Buffer to hold name
|
||||||
|
/// \param size_ Size of buffer_
|
||||||
|
/// \retval buffer_ success
|
||||||
|
/// \retval nullptr failure
|
||||||
|
char const *name (char *buffer_, std::size_t size_) const;
|
||||||
|
|
||||||
|
/// \brief Address name
|
||||||
|
/// \retval nullptr failure
|
||||||
|
/// \note This function is not reentrant
|
||||||
|
char const *name () const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// \brief Address storage
|
||||||
|
struct sockaddr_storage m_addr = {};
|
||||||
|
};
|
188
include/socket.h
Normal file
188
include/socket.h
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ioBuffer.h"
|
||||||
|
#include "sockAddr.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
struct pollfd
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int events;
|
||||||
|
int revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
using socklen_t = int;
|
||||||
|
using nfds_t = unsigned int;
|
||||||
|
|
||||||
|
extern "C" int poll (struct pollfd *fds_, nfds_t nfds_, int timeout_);
|
||||||
|
|
||||||
|
#define POLLIN (1 << 0)
|
||||||
|
#define POLLPRI (1 << 1)
|
||||||
|
#define POLLOUT (1 << 2)
|
||||||
|
#define POLLERR (1 << 3)
|
||||||
|
#define POLLHUP (1 << 4)
|
||||||
|
#else
|
||||||
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Socket;
|
||||||
|
using UniqueSocket = std::unique_ptr<Socket>;
|
||||||
|
using SharedSocket = std::shared_ptr<Socket>;
|
||||||
|
|
||||||
|
/// \brief Socket object
|
||||||
|
class Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// \brief Poll info
|
||||||
|
struct PollInfo
|
||||||
|
{
|
||||||
|
/// \brief Socket to poll
|
||||||
|
std::reference_wrapper<Socket> socket;
|
||||||
|
|
||||||
|
/// \brief Input events
|
||||||
|
int events;
|
||||||
|
|
||||||
|
/// \brief Output events
|
||||||
|
int revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
~Socket ();
|
||||||
|
|
||||||
|
/// \brief Accept connection
|
||||||
|
UniqueSocket accept ();
|
||||||
|
|
||||||
|
/// \brief Whether socket is at out-of-band mark
|
||||||
|
int atMark ();
|
||||||
|
|
||||||
|
/// \brief Bind socket to address
|
||||||
|
/// \param addr_ Address to bind
|
||||||
|
bool bind (SockAddr const &addr_);
|
||||||
|
|
||||||
|
/// \brief Connect to a peer
|
||||||
|
/// \param addr_ Peer address
|
||||||
|
bool connect (SockAddr const &addr_);
|
||||||
|
|
||||||
|
/// \brief Listen for connections
|
||||||
|
/// \param backlog_ Queue size for incoming connections
|
||||||
|
bool listen (int backlog_);
|
||||||
|
|
||||||
|
/// \brief Shutdown socket
|
||||||
|
/// \param how_ Type of shutdown (\sa ::shutdown)
|
||||||
|
bool shutdown (int how_);
|
||||||
|
|
||||||
|
/// \brief Set linger option
|
||||||
|
/// \param enable_ Whether to enable linger
|
||||||
|
/// \param time_ Linger timeout
|
||||||
|
bool setLinger (bool enable_, std::chrono::seconds time_);
|
||||||
|
|
||||||
|
/// \brief Set non-blocking
|
||||||
|
/// \param nonBlocking_ Whether to set non-blocking
|
||||||
|
bool setNonBlocking (bool nonBlocking_ = true);
|
||||||
|
|
||||||
|
bool setWinScale (const int val);
|
||||||
|
|
||||||
|
/// \brief Set reuse address in subsequent bind
|
||||||
|
/// \param reuse_ Whether to reuse address
|
||||||
|
bool setReuseAddress (bool reuse_ = true);
|
||||||
|
|
||||||
|
/// \brief Set recv buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
|
bool setRecvBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Set send buffer size
|
||||||
|
/// \param size_ Buffer size
|
||||||
|
bool setSendBufferSize (std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param size_ Size to read
|
||||||
|
/// \param oob_ Whether to read from out-of-band
|
||||||
|
std::make_signed_t<std::size_t> read (void *buffer_, std::size_t size_, bool oob_ = false);
|
||||||
|
|
||||||
|
/// \brief Read data
|
||||||
|
/// \param buffer_ Output buffer
|
||||||
|
/// \param oob_ Whether to read from out-of-band
|
||||||
|
std::make_signed_t<std::size_t> read (IOBuffer &buffer_, bool oob_ = false);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input buffer
|
||||||
|
/// \param size_ Size to write
|
||||||
|
std::make_signed_t<std::size_t> write (void const *buffer_, std::size_t size_);
|
||||||
|
|
||||||
|
/// \brief Write data
|
||||||
|
/// \param buffer_ Input buffer
|
||||||
|
/// \param size_ Size to write
|
||||||
|
std::make_signed_t<std::size_t> write (IOBuffer &buffer_);
|
||||||
|
|
||||||
|
/// \brief Local name
|
||||||
|
SockAddr const &sockName () const;
|
||||||
|
/// \brief Peer name
|
||||||
|
SockAddr const &peerName () const;
|
||||||
|
|
||||||
|
/// \brief Create socket
|
||||||
|
static UniqueSocket create ();
|
||||||
|
|
||||||
|
/// \brief Poll sockets
|
||||||
|
/// \param info_ Poll info
|
||||||
|
/// \param count_ Number of poll entries
|
||||||
|
/// \param timeout_ Poll timeout
|
||||||
|
static int poll (PollInfo *info_, std::size_t count_, std::chrono::milliseconds timeout_);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Socket () = delete;
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param fd_ Socket fd
|
||||||
|
Socket (int fd_);
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param fd_ Socket fd
|
||||||
|
/// \param sockName_ Local name
|
||||||
|
/// \param peerName_ Peer name
|
||||||
|
Socket (int fd_, SockAddr const &sockName_, SockAddr const &peerName_);
|
||||||
|
|
||||||
|
Socket (Socket const &that_) = delete;
|
||||||
|
|
||||||
|
Socket (Socket &&that_) = delete;
|
||||||
|
|
||||||
|
Socket &operator= (Socket const &that_) = delete;
|
||||||
|
|
||||||
|
Socket &operator= (Socket &&that_) = delete;
|
||||||
|
|
||||||
|
/// \param Local name
|
||||||
|
SockAddr m_sockName;
|
||||||
|
/// \param Peer name
|
||||||
|
SockAddr m_peerName;
|
||||||
|
|
||||||
|
/// \param Socket fd
|
||||||
|
int const m_fd;
|
||||||
|
|
||||||
|
/// \param Whether listening
|
||||||
|
bool m_listening : 1;
|
||||||
|
|
||||||
|
/// \param Whether connected
|
||||||
|
bool m_connected : 1;
|
||||||
|
};
|
233
source/IOAbstraction.cpp
Normal file
233
source/IOAbstraction.cpp
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
#include "IOAbstraction.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <sys/dirent.h>
|
||||||
|
#include <sys/unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class VirtualDirectory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
VirtualDirectory (const std::vector<std::string> &directories)
|
||||||
|
{
|
||||||
|
mDirectories.push_back (".");
|
||||||
|
mDirectories.push_back ("..");
|
||||||
|
mDirectories.insert (mDirectories.end (), directories.begin (), directories.end ());
|
||||||
|
|
||||||
|
mCurIterator = mDirectories.begin ();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const DIR *getAsDir () const
|
||||||
|
{
|
||||||
|
return &mDirPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *readdir ()
|
||||||
|
{
|
||||||
|
if (mCurIterator == mDirectories.end ())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
mDir = {};
|
||||||
|
snprintf (mDir.d_name, sizeof (mDir.d_name), "%s", mCurIterator->c_str ());
|
||||||
|
#ifdef _DIRENT_HAVE_D_STAT
|
||||||
|
mDir.d_stat.st_mode = _IFDIR;
|
||||||
|
#endif
|
||||||
|
mCurIterator++;
|
||||||
|
mDirPtr.position++;
|
||||||
|
return &mDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DIR mDirPtr = {};
|
||||||
|
std::vector<std::string> mDirectories;
|
||||||
|
struct dirent mDir = {};
|
||||||
|
std::vector<std::string>::iterator mCurIterator{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<VirtualDirectory>> sOpenVirtualDirectories;
|
||||||
|
std::mutex sOpenVirtualDirectoriesMutex;
|
||||||
|
std::map<std::string, std::vector<std::string>> sVirtualDirs;
|
||||||
|
|
||||||
|
template <typename Container, typename Predicate>
|
||||||
|
typename std::enable_if<std::is_same<Container, std::vector<typename Container::value_type>>::value,
|
||||||
|
bool>::type
|
||||||
|
remove_first_if (Container &container, Predicate pred)
|
||||||
|
{
|
||||||
|
auto it = container.begin ();
|
||||||
|
while (it != container.end ())
|
||||||
|
{
|
||||||
|
if (pred (*it))
|
||||||
|
{
|
||||||
|
container.erase (it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Container, typename Predicate>
|
||||||
|
bool remove_locked_first_if (std::mutex &mutex, Container &container, Predicate pred)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock (mutex);
|
||||||
|
return remove_first_if (container, pred);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const DIR *getVirtualDir (const std::vector<std::string> &subDirectories)
|
||||||
|
{
|
||||||
|
auto virtDir = std::make_unique<VirtualDirectory> (subDirectories);
|
||||||
|
auto *result = virtDir->getAsDir ();
|
||||||
|
std::lock_guard lock (sOpenVirtualDirectoriesMutex);
|
||||||
|
sOpenVirtualDirectories.push_back (std::move (virtDir));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IOAbstraction::convertPath (std::string_view inPath)
|
||||||
|
{
|
||||||
|
#ifdef __WIIU__
|
||||||
|
if (!inPath.starts_with ('/') || inPath.find (':') != std::string::npos)
|
||||||
|
{
|
||||||
|
return std::string (inPath);
|
||||||
|
}
|
||||||
|
std::string path = std::string (inPath);
|
||||||
|
size_t secondSlashPos = path.find ('/', 1);
|
||||||
|
if (secondSlashPos != std::string::npos)
|
||||||
|
{
|
||||||
|
// Extract the substring between the first and second slashes
|
||||||
|
std::string prefix = path.substr (1, secondSlashPos - 1);
|
||||||
|
std::string suffix = path.substr (secondSlashPos);
|
||||||
|
|
||||||
|
// Concatenate the modified prefix and suffix
|
||||||
|
path = prefix + ":" + suffix;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = std::string (inPath.substr (1)) + ":/";
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
#else
|
||||||
|
return std::string (inPath);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::closedir (DIR *dirp)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lock (sOpenVirtualDirectoriesMutex);
|
||||||
|
if (remove_locked_first_if (sOpenVirtualDirectoriesMutex,
|
||||||
|
sOpenVirtualDirectories,
|
||||||
|
[dirp] (auto &cur) { return cur->getAsDir () == dirp; }))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ::closedir (dirp);
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *IOAbstraction::opendir (const char *dirname)
|
||||||
|
{
|
||||||
|
auto convertedPath = convertPath (dirname);
|
||||||
|
auto *res = ::opendir (convertedPath.c_str ());
|
||||||
|
if (res == nullptr)
|
||||||
|
{
|
||||||
|
if (sVirtualDirs.count (convertedPath) > 0)
|
||||||
|
{
|
||||||
|
return (DIR *)getVirtualDir (sVirtualDirs[convertedPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *IOAbstraction::fopen (const char *_name, const char *_type)
|
||||||
|
{
|
||||||
|
return std::fopen (convertPath (_name).c_str (), _type);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::fseek (FILE *f, long pos, int origin)
|
||||||
|
{
|
||||||
|
return std::fseek (f, pos, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IOAbstraction::fread (void *buffer, size_t _size, size_t _n, FILE *f)
|
||||||
|
{
|
||||||
|
return std::fread (buffer, _size, _n, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IOAbstraction::fwrite (const void *buffer, size_t _size, size_t _n, FILE *f)
|
||||||
|
{
|
||||||
|
return std::fwrite (buffer, _size, _n, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *IOAbstraction::readdir (DIR *dirp)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lock (sOpenVirtualDirectoriesMutex);
|
||||||
|
auto itr = std::find_if (sOpenVirtualDirectories.begin (),
|
||||||
|
sOpenVirtualDirectories.end (),
|
||||||
|
[dirp] (auto &cur) { return cur->getAsDir () == dirp; });
|
||||||
|
if (itr != sOpenVirtualDirectories.end ())
|
||||||
|
{
|
||||||
|
return (*itr)->readdir ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ::readdir (dirp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::stat (const char *path, struct stat *sbuf)
|
||||||
|
{
|
||||||
|
auto convertedPath = convertPath (path);
|
||||||
|
auto r = ::stat (convertedPath.c_str (), sbuf);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
if (sVirtualDirs.contains (convertedPath))
|
||||||
|
{
|
||||||
|
*sbuf = {};
|
||||||
|
// TODO: init other values?
|
||||||
|
sbuf->st_mode = _IFDIR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::lstat (const char *path, struct stat *buf)
|
||||||
|
{
|
||||||
|
return IOAbstraction::stat (path, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOAbstraction::addVirtualPath (const std::string &virtualPath,
|
||||||
|
const std::vector<std::string> &subDirectories)
|
||||||
|
{
|
||||||
|
sVirtualDirs.insert (std::make_pair (virtualPath, subDirectories));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOAbstraction::clear ()
|
||||||
|
{
|
||||||
|
std::lock_guard lock (sOpenVirtualDirectoriesMutex);
|
||||||
|
sOpenVirtualDirectories.clear ();
|
||||||
|
sVirtualDirs.clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::mkdir (const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return ::mkdir (convertPath (path).c_str (), mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::rmdir (const char *path)
|
||||||
|
{
|
||||||
|
return ::rmdir (convertPath (path).c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::unlink (const char *path)
|
||||||
|
{
|
||||||
|
return ::unlink (convertPath (path).c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
int IOAbstraction::rename (const char *path, const char *path2)
|
||||||
|
{
|
||||||
|
return ::rename (convertPath (path).c_str (), convertPath (path2).c_str ());
|
||||||
|
}
|
283
source/fs.cpp
Normal file
283
source/fs.cpp
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "fs.h"
|
||||||
|
#include "IOAbstraction.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__)
|
||||||
|
#define getline __getline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string fs::printSize (std::uint64_t const size_)
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t const KiB = 1024;
|
||||||
|
constexpr std::uint64_t const MiB = 1024 * KiB;
|
||||||
|
constexpr std::uint64_t const GiB = 1024 * MiB;
|
||||||
|
constexpr std::uint64_t const TiB = 1024 * GiB;
|
||||||
|
constexpr std::uint64_t const PiB = 1024 * TiB;
|
||||||
|
constexpr std::uint64_t const EiB = 1024 * PiB;
|
||||||
|
|
||||||
|
char buffer[64] = {};
|
||||||
|
|
||||||
|
for (auto const &[name, bin] : {
|
||||||
|
// clang-format off
|
||||||
|
std::make_pair ("EiB", EiB),
|
||||||
|
std::make_pair ("PiB", PiB),
|
||||||
|
std::make_pair ("TiB", TiB),
|
||||||
|
std::make_pair ("GiB", GiB),
|
||||||
|
std::make_pair ("MiB", MiB),
|
||||||
|
std::make_pair ("KiB", KiB)
|
||||||
|
// clang-format on
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// get the integral portion of the number
|
||||||
|
auto const whole = size_ / bin;
|
||||||
|
if (size_ >= 100 * bin)
|
||||||
|
{
|
||||||
|
// >= 100, print xxxXiB
|
||||||
|
std::sprintf (buffer, "%" PRIu64 "%s", whole, name);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the fractional portion of the number
|
||||||
|
auto const frac = size_ - whole * bin;
|
||||||
|
if (size_ >= 10 * bin)
|
||||||
|
{
|
||||||
|
// >= 10, print xx.xXiB
|
||||||
|
std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size_ >= 1000 * (bin / KiB))
|
||||||
|
{
|
||||||
|
// >= 1000 of lesser bin, print x.xxXiB
|
||||||
|
std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 1KiB, just print the number
|
||||||
|
std::sprintf (buffer, "%" PRIu64 "B", size_);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
fs::File::~File ()
|
||||||
|
{
|
||||||
|
std::free (m_lineBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::File::File () = default;
|
||||||
|
|
||||||
|
fs::File::File (File &&that_) = default;
|
||||||
|
|
||||||
|
fs::File &fs::File::operator= (File &&that_) = default;
|
||||||
|
|
||||||
|
fs::File::operator bool () const
|
||||||
|
{
|
||||||
|
return static_cast<bool> (m_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::File::operator FILE * () const
|
||||||
|
{
|
||||||
|
return m_fp.get ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::File::setBufferSize (std::size_t const size_)
|
||||||
|
{
|
||||||
|
if (m_bufferSize != size_)
|
||||||
|
{
|
||||||
|
m_buffer = std::make_unique<char[]> (size_);
|
||||||
|
m_bufferSize = size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fp)
|
||||||
|
std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs::File::open (char const *const path_, char const *const mode_)
|
||||||
|
{
|
||||||
|
auto const fp = IOAbstraction::fopen (path_, mode_);
|
||||||
|
if (!fp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_fp = std::unique_ptr<std::FILE, int (*) (std::FILE *)> (fp, &std::fclose);
|
||||||
|
|
||||||
|
if (m_buffer)
|
||||||
|
std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::File::close ()
|
||||||
|
{
|
||||||
|
m_fp.reset ();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> fs::File::seek (std::size_t const pos_, int const origin_)
|
||||||
|
{
|
||||||
|
return IOAbstraction::fseek (m_fp.get (), pos_, origin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> fs::File::read (void *const buffer_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_ > 0);
|
||||||
|
|
||||||
|
return IOAbstraction::fread (buffer_, 1, size_, m_fp.get ());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> fs::File::read (IOBuffer &buffer_)
|
||||||
|
{
|
||||||
|
assert (buffer_.freeSize () > 0);
|
||||||
|
|
||||||
|
auto const rc = read (buffer_.freeArea (), buffer_.freeSize ());
|
||||||
|
if (rc > 0)
|
||||||
|
buffer_.markUsed (rc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view fs::File::readLine ()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto rc = ::getline (&m_lineBuffer, &m_lineBufferSize, m_fp.get ());
|
||||||
|
if (rc < 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
while (rc > 0)
|
||||||
|
{
|
||||||
|
if (m_lineBuffer[rc - 1] != '\r' && m_lineBuffer[rc - 1] != '\n')
|
||||||
|
break;
|
||||||
|
|
||||||
|
m_lineBuffer[--rc] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc > 0)
|
||||||
|
return std::string_view (m_lineBuffer, rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs::File::readAll (void *const buffer_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_ > 0);
|
||||||
|
|
||||||
|
auto p = static_cast<char *> (buffer_);
|
||||||
|
|
||||||
|
std::size_t bytes = 0;
|
||||||
|
while (bytes < size_)
|
||||||
|
{
|
||||||
|
auto const rc = read (p, size_ - bytes);
|
||||||
|
if (rc <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
p += rc;
|
||||||
|
bytes += rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> fs::File::write (void const *const buffer_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_ > 0);
|
||||||
|
|
||||||
|
return IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> fs::File::write (IOBuffer &buffer_)
|
||||||
|
{
|
||||||
|
assert (buffer_.usedSize () > 0);
|
||||||
|
|
||||||
|
auto const rc = write (buffer_.usedArea (), buffer_.usedSize ());
|
||||||
|
if (rc > 0)
|
||||||
|
buffer_.markFree (rc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs::File::writeAll (void const *const buffer_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_ > 0);
|
||||||
|
|
||||||
|
auto p = static_cast<char const *> (buffer_);
|
||||||
|
|
||||||
|
std::size_t bytes = 0;
|
||||||
|
while (bytes < size_)
|
||||||
|
{
|
||||||
|
auto const rc = write (p, size_ - bytes);
|
||||||
|
if (rc <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
p += rc;
|
||||||
|
bytes += rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
fs::Dir::~Dir () = default;
|
||||||
|
|
||||||
|
fs::Dir::Dir () = default;
|
||||||
|
|
||||||
|
fs::Dir::Dir (Dir &&that_) = default;
|
||||||
|
|
||||||
|
fs::Dir &fs::Dir::operator= (Dir &&that_) = default;
|
||||||
|
|
||||||
|
fs::Dir::operator bool () const
|
||||||
|
{
|
||||||
|
return static_cast<bool> (m_dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::Dir::operator DIR * () const
|
||||||
|
{
|
||||||
|
return m_dp.get ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs::Dir::open (char const *const path_)
|
||||||
|
{
|
||||||
|
auto const dp = IOAbstraction::opendir (path_);
|
||||||
|
if (!dp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_dp = std::unique_ptr<DIR, int (*) (DIR *)> (dp, &IOAbstraction::closedir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs::Dir::close ()
|
||||||
|
{
|
||||||
|
m_dp.reset ();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *fs::Dir::read ()
|
||||||
|
{
|
||||||
|
errno = 0;
|
||||||
|
return IOAbstraction::readdir (m_dp.get ());
|
||||||
|
}
|
333
source/ftpConfig.cpp
Normal file
333
source/ftpConfig.cpp
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "ftpConfig.h"
|
||||||
|
|
||||||
|
#include "fs.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
#if defined(__WIIU__) && defined(__WUPS__)
|
||||||
|
constexpr std::uint16_t DEFAULT_PORT = 21;
|
||||||
|
#else
|
||||||
|
constexpr std::uint16_t DEFAULT_PORT = 5000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool mkdirParent (std::string const &path_)
|
||||||
|
{
|
||||||
|
auto pos = path_.find_first_of ('/');
|
||||||
|
while (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
auto const dir = path_.substr (0, pos);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
auto const rc = ::stat (dir.c_str (), &st);
|
||||||
|
if (rc < 0 && errno != ENOENT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (rc < 0 && errno == ENOENT)
|
||||||
|
{
|
||||||
|
auto const rc = ::mkdir (dir.c_str (), 0755);
|
||||||
|
if (rc < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = path_.find_first_of ('/', pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string strip (std::string const &str_)
|
||||||
|
{
|
||||||
|
auto const start = str_.find_first_not_of (" \t");
|
||||||
|
if (start == std::string::npos)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto const end = str_.find_last_not_of (" \t");
|
||||||
|
|
||||||
|
if (end == std::string::npos)
|
||||||
|
return str_.substr (start);
|
||||||
|
|
||||||
|
return str_.substr (start, end + 1 - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool parseInt (T &out_, std::string const &val_)
|
||||||
|
{
|
||||||
|
T val = 0;
|
||||||
|
|
||||||
|
for (auto const &c : val_)
|
||||||
|
{
|
||||||
|
if (!std::isdigit (c))
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::numeric_limits<T>::max () / 10 < val)
|
||||||
|
{
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
val *= 10;
|
||||||
|
|
||||||
|
auto const v = c - '0';
|
||||||
|
if (std::numeric_limits<T>::max () - v < val)
|
||||||
|
{
|
||||||
|
errno = EOVERFLOW;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
val += v;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_ = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
FtpConfig::~FtpConfig () = default;
|
||||||
|
|
||||||
|
FtpConfig::FtpConfig () : m_port (DEFAULT_PORT)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueFtpConfig FtpConfig::create ()
|
||||||
|
{
|
||||||
|
return UniqueFtpConfig (new FtpConfig ());
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueFtpConfig FtpConfig::load (char const *const path_)
|
||||||
|
{
|
||||||
|
auto config = create ();
|
||||||
|
|
||||||
|
auto fp = fs::File ();
|
||||||
|
if (!fp.open (path_))
|
||||||
|
return config;
|
||||||
|
|
||||||
|
std::uint16_t port = DEFAULT_PORT;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (!(line = fp.readLine ()).empty ())
|
||||||
|
{
|
||||||
|
auto const pos = line.find_first_of ('=');
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
{
|
||||||
|
error ("Ignoring '%s'\n", line.c_str ());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const key = strip (line.substr (0, pos));
|
||||||
|
auto const val = strip (line.substr (pos + 1));
|
||||||
|
if (key.empty () || val.empty ())
|
||||||
|
{
|
||||||
|
error ("Ignoring '%s'\n", line.c_str ());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == "user")
|
||||||
|
config->m_user = val;
|
||||||
|
else if (key == "pass")
|
||||||
|
config->m_pass = val;
|
||||||
|
else if (key == "port")
|
||||||
|
parseInt (port, val);
|
||||||
|
#ifdef __3DS__
|
||||||
|
else if (key == "mtime")
|
||||||
|
{
|
||||||
|
if (val == "0")
|
||||||
|
config->m_getMTime = false;
|
||||||
|
else if (val == "1")
|
||||||
|
config->m_getMTime = true;
|
||||||
|
else
|
||||||
|
error ("Invalid value for mtime: %s\n", val.c_str ());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
else if (key == "ap")
|
||||||
|
{
|
||||||
|
if (val == "0")
|
||||||
|
config->m_enableAP = false;
|
||||||
|
else if (val == "1")
|
||||||
|
config->m_enableAP = true;
|
||||||
|
else
|
||||||
|
error ("Invalid value for ap: %s\n", val.c_str ());
|
||||||
|
}
|
||||||
|
else if (key == "ssid")
|
||||||
|
config->m_ssid = val;
|
||||||
|
else if (key == "passphrase")
|
||||||
|
config->m_passphrase = val;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
config->setPort (port);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
std::scoped_lock<platform::Mutex> FtpConfig::lockGuard ()
|
||||||
|
{
|
||||||
|
return std::scoped_lock<platform::Mutex> (m_lock);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool FtpConfig::save (char const *const path_)
|
||||||
|
{
|
||||||
|
if (!mkdirParent (path_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto fp = fs::File ();
|
||||||
|
if (!fp.open (path_, "wb"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!m_user.empty ())
|
||||||
|
std::fprintf (fp, "user=%s\n", m_user.c_str ());
|
||||||
|
if (!m_pass.empty ())
|
||||||
|
std::fprintf (fp, "pass=%s\n", m_pass.c_str ());
|
||||||
|
std::fprintf (fp, "port=%u\n", m_port);
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
std::fprintf (fp, "mtime=%u\n", m_getMTime);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
std::fprintf (fp, "ap=%u\n", m_enableAP);
|
||||||
|
if (!m_ssid.empty ())
|
||||||
|
std::fprintf (fp, "ssid=%s\n", m_ssid.c_str ());
|
||||||
|
if (!m_passphrase.empty ())
|
||||||
|
std::fprintf (fp, "passphrase=%s\n", m_passphrase.c_str ());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const &FtpConfig::user () const
|
||||||
|
{
|
||||||
|
return m_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const &FtpConfig::pass () const
|
||||||
|
{
|
||||||
|
return m_pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t FtpConfig::port () const
|
||||||
|
{
|
||||||
|
return m_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
bool FtpConfig::getMTime () const
|
||||||
|
{
|
||||||
|
return m_getMTime;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
bool FtpConfig::enableAP () const
|
||||||
|
{
|
||||||
|
return m_enableAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const &FtpConfig::ssid () const
|
||||||
|
{
|
||||||
|
return m_ssid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const &FtpConfig::passphrase () const
|
||||||
|
{
|
||||||
|
return m_passphrase;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void FtpConfig::setUser (std::string const &user_)
|
||||||
|
{
|
||||||
|
m_user = user_.substr (0, user_.find_first_of ('\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpConfig::setPass (std::string const &pass_)
|
||||||
|
{
|
||||||
|
m_pass = pass_.substr (0, pass_.find_first_of ('\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FtpConfig::setPort (std::string const &port_)
|
||||||
|
{
|
||||||
|
std::uint16_t parsed;
|
||||||
|
if (!parseInt (parsed, port_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return setPort (parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FtpConfig::setPort (std::uint16_t const port_)
|
||||||
|
{
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// Switch is not allowed < 1024, except 0
|
||||||
|
if (port_ < 1024 && port_ != 0)
|
||||||
|
{
|
||||||
|
errno = EPERM;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#elif defined(NDS) || defined(__3DS__)
|
||||||
|
// 3DS is allowed < 1024, but not 0
|
||||||
|
// NDS is allowed < 1024, but 0 crashes the app
|
||||||
|
if (port_ == 0)
|
||||||
|
{
|
||||||
|
errno = EPERM;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_port = port_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
void FtpConfig::setGetMTime (bool const getMTime_)
|
||||||
|
{
|
||||||
|
m_getMTime = getMTime_;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
void FtpConfig::setEnableAP (bool const enable_)
|
||||||
|
{
|
||||||
|
m_enableAP = enable_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpConfig::setSSID (std::string const &ssid_)
|
||||||
|
{
|
||||||
|
m_ssid = ssid_.substr (0, ssid_.find_first_of ('\0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpConfig::setPassphrase (std::string const &passphrase_)
|
||||||
|
{
|
||||||
|
m_passphrase = passphrase_.substr (0, passphrase_.find_first_of ('\0'));
|
||||||
|
}
|
||||||
|
#endif
|
940
source/ftpServer.cpp
Normal file
940
source/ftpServer.cpp
Normal file
@ -0,0 +1,940 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2023 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "ftpServer.h"
|
||||||
|
|
||||||
|
#include "fs.h"
|
||||||
|
#ifndef __WIIU__
|
||||||
|
#include "licenses.h"
|
||||||
|
#endif
|
||||||
|
#include "log.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#ifndef __WIIU__
|
||||||
|
#include "imgui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
#include <dswifi9.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/statvfs.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
#define LOCKED(x) x
|
||||||
|
#else
|
||||||
|
#define LOCKED(x) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
auto const lock = std::scoped_lock (m_lock); \
|
||||||
|
x; \
|
||||||
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// \brief Application start time
|
||||||
|
auto const s_startTime = std::time (nullptr);
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Mutex for s_freeSpace
|
||||||
|
platform::Mutex s_lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Free space string
|
||||||
|
std::string s_freeSpace;
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::string printable (char *const data_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result.reserve (size_);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < size_; ++i)
|
||||||
|
{
|
||||||
|
if (std::isprint (data_[i]) || std::isspace (data_[i]))
|
||||||
|
result.push_back (data_[i]);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char buffer[5];
|
||||||
|
std::snprintf (
|
||||||
|
buffer, sizeof (buffer), "%%%02u", static_cast<unsigned char> (data_[i]));
|
||||||
|
result += buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int curlDebug (CURL *const handle_,
|
||||||
|
curl_infotype const type_,
|
||||||
|
char *const data_,
|
||||||
|
std::size_t const size_,
|
||||||
|
void *const user_)
|
||||||
|
{
|
||||||
|
(void)user_;
|
||||||
|
|
||||||
|
auto const text = printable (data_, size_);
|
||||||
|
|
||||||
|
switch (type_)
|
||||||
|
{
|
||||||
|
case CURLINFO_TEXT:
|
||||||
|
info ("== Info: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_HEADER_OUT:
|
||||||
|
info ("=> Send header: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_DATA_OUT:
|
||||||
|
info ("=> Send data: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_SSL_DATA_OUT:
|
||||||
|
info ("=> Send SSL data: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_HEADER_IN:
|
||||||
|
info ("<= Receive header: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_DATA_IN:
|
||||||
|
info ("<= Receive data: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURLINFO_SSL_DATA_IN:
|
||||||
|
info ("<= Receive SSL data: %s", text.c_str ());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::size_t curlCallback (void *const contents_,
|
||||||
|
std::size_t const size_,
|
||||||
|
std::size_t const count_,
|
||||||
|
void *const user_)
|
||||||
|
{
|
||||||
|
auto const total = size_ * count_;
|
||||||
|
auto const start = static_cast<char *> (contents_);
|
||||||
|
auto const end = start + total;
|
||||||
|
|
||||||
|
auto &result = *static_cast<std::string *> (user_);
|
||||||
|
result.insert (std::end (result), start, end);
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
FtpServer::~FtpServer ()
|
||||||
|
{
|
||||||
|
m_quit = true;
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
m_thread.join ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
if (m_uploadLogCurl)
|
||||||
|
{
|
||||||
|
curl_multi_remove_handle (m_uploadLogCurlM, m_uploadLogCurl);
|
||||||
|
curl_easy_cleanup (m_uploadLogCurl);
|
||||||
|
curl_mime_free (m_uploadLogMime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_uploadLogCurlM)
|
||||||
|
curl_multi_cleanup (m_uploadLogCurlM);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
FtpServer::FtpServer (UniqueFtpConfig config_) : m_config (std::move (config_)), m_quit (false)
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::draw ()
|
||||||
|
{
|
||||||
|
#ifdef NDS
|
||||||
|
loop ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CLASSIC
|
||||||
|
{
|
||||||
|
char port[7];
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
#endif
|
||||||
|
if (m_socket)
|
||||||
|
std::sprintf (port, ":%u", m_socket->sockName ().port ());
|
||||||
|
|
||||||
|
#ifndef NO_CONSOLE
|
||||||
|
consoleSelect (&g_statusConsole);
|
||||||
|
std::printf ("\x1b[0;0H\x1b[32;1m%s \x1b[36;1m%s%s",
|
||||||
|
STATUS_STRING,
|
||||||
|
m_socket ? m_socket->sockName ().name () : "Waiting on WiFi",
|
||||||
|
m_socket ? port : "");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
char timeBuffer[16];
|
||||||
|
auto const now = std::time (nullptr);
|
||||||
|
std::strftime (timeBuffer, sizeof (timeBuffer), "%H:%M:%S", std::localtime (&now));
|
||||||
|
|
||||||
|
std::printf (" \x1b[37;1m%s", timeBuffer);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::fputs ("\x1b[K", stdout);
|
||||||
|
std::fflush (stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
if (!s_freeSpace.empty ())
|
||||||
|
{
|
||||||
|
#ifndef NO_CONSOLE
|
||||||
|
consoleSelect (&g_statusConsole);
|
||||||
|
std::printf ("\x1b[0;%uH\x1b[32;1m%s",
|
||||||
|
static_cast<unsigned> (g_statusConsole.windowWidth - s_freeSpace.size () + 1),
|
||||||
|
s_freeSpace.c_str ());
|
||||||
|
std::fflush (stdout);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
#endif
|
||||||
|
#ifndef NO_CONSOLE
|
||||||
|
consoleSelect (&g_sessionConsole);
|
||||||
|
std::fputs ("\x1b[2J", stdout);
|
||||||
|
for (auto &session : m_sessions)
|
||||||
|
{
|
||||||
|
session->draw ();
|
||||||
|
if (&session != &m_sessions.back ())
|
||||||
|
std::fputc ('\n', stdout);
|
||||||
|
}
|
||||||
|
std::fflush (stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
drawLog ();
|
||||||
|
#else
|
||||||
|
auto const &io = ImGui::GetIO ();
|
||||||
|
auto const width = io.DisplaySize.x;
|
||||||
|
auto const height = io.DisplaySize.y;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos (ImVec2 (0, 0), ImGuiCond_FirstUseEver);
|
||||||
|
#ifdef __3DS__
|
||||||
|
// top screen
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width, height * 0.5f));
|
||||||
|
#else
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width, height));
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
char title[64];
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const serverLock = std::scoped_lock (m_lock);
|
||||||
|
std::snprintf (title,
|
||||||
|
sizeof (title),
|
||||||
|
STATUS_STRING " %s###ftpd",
|
||||||
|
m_socket ? m_name.c_str () : "Waiting for WiFi...");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Begin (title,
|
||||||
|
nullptr,
|
||||||
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
|
||||||
|
#ifndef __3DS__
|
||||||
|
| ImGuiWindowFlags_MenuBar
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __3DS__
|
||||||
|
showMenu ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __3DS__
|
||||||
|
ImGui::BeginChild (
|
||||||
|
"Logs", ImVec2 (0, 0.5f * height), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
#endif
|
||||||
|
drawLog ();
|
||||||
|
#ifndef __3DS__
|
||||||
|
ImGui::EndChild ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
ImGui::End ();
|
||||||
|
|
||||||
|
// bottom screen
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.5f));
|
||||||
|
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f), ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::Begin ("Sessions",
|
||||||
|
nullptr,
|
||||||
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |
|
||||||
|
ImGuiWindowFlags_MenuBar);
|
||||||
|
|
||||||
|
showMenu ();
|
||||||
|
#else
|
||||||
|
ImGui::Separator ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
for (auto &session : m_sessions)
|
||||||
|
session->draw ();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End ();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueFtpServer FtpServer::create ()
|
||||||
|
{
|
||||||
|
updateFreeSpace ();
|
||||||
|
|
||||||
|
auto config = FtpConfig::load (FTPDCONFIG);
|
||||||
|
|
||||||
|
return UniqueFtpServer (new FtpServer (std::move (config)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FtpServer::getFreeSpace ()
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
return s_freeSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::updateFreeSpace ()
|
||||||
|
{
|
||||||
|
struct statvfs st;
|
||||||
|
#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__)
|
||||||
|
if (::statvfs ("sdmc:/", &st) != 0)
|
||||||
|
#else
|
||||||
|
if (::statvfs ("/", &st) != 0)
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree);
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
if (freeSpace != s_freeSpace)
|
||||||
|
s_freeSpace = std::move (freeSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::time_t FtpServer::startTime ()
|
||||||
|
{
|
||||||
|
return s_startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::handleNetworkFound ()
|
||||||
|
{
|
||||||
|
SockAddr addr;
|
||||||
|
if (!platform::networkAddress (addr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::uint16_t port;
|
||||||
|
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = m_config->lockGuard ();
|
||||||
|
#endif
|
||||||
|
port = m_config->port ();
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.setPort (port);
|
||||||
|
|
||||||
|
auto socket = Socket::create ();
|
||||||
|
if (!socket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (port != 0 && !socket->setReuseAddress (true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!socket->bind (addr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!socket->listen (10))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto const &sockName = socket->sockName ();
|
||||||
|
auto const name = sockName.name ();
|
||||||
|
|
||||||
|
m_name.resize (std::strlen (name) + 3 + 5);
|
||||||
|
m_name.resize (std::sprintf (m_name.data (), "[%s]:%u", name, sockName.port ()));
|
||||||
|
|
||||||
|
info ("Started server at %s\n", m_name.c_str ());
|
||||||
|
|
||||||
|
LOCKED (m_socket = std::move (socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::handleNetworkLost ()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// destroy sessions
|
||||||
|
std::vector<UniqueFtpSession> sessions;
|
||||||
|
LOCKED (sessions = std::move (m_sessions));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// destroy command socket
|
||||||
|
UniqueSocket sock;
|
||||||
|
LOCKED (sock = std::move (m_socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
info ("Stopped server at %s\n", m_name.c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
void FtpServer::showMenu ()
|
||||||
|
{
|
||||||
|
auto const prevShowSettings = m_showSettings;
|
||||||
|
auto const prevShowAbout = m_showAbout;
|
||||||
|
|
||||||
|
if (ImGui::BeginMenuBar ())
|
||||||
|
{
|
||||||
|
#if defined(__3DS__) || defined(__SWITCH__)
|
||||||
|
if (ImGui::BeginMenu ("Menu \xee\x80\x83")) // Y Button
|
||||||
|
#else
|
||||||
|
if (ImGui::BeginMenu ("Menu"))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (ImGui::MenuItem ("Settings"))
|
||||||
|
m_showSettings = true;
|
||||||
|
|
||||||
|
if (ImGui::MenuItem ("Upload Log"))
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
#endif
|
||||||
|
if (!m_uploadLogCurlM)
|
||||||
|
m_uploadLogCurlM = curl_multi_init ();
|
||||||
|
|
||||||
|
if (m_uploadLogCurlM && !m_uploadLogCurl.load (std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
m_uploadLogData = getLog ();
|
||||||
|
|
||||||
|
auto const handle = curl_easy_init ();
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
// 3DS CA fails peer verification, so add CA here
|
||||||
|
curl_easy_setopt (handle, CURLOPT_CAINFO, "romfs:/sni.cloudflaressl.com.ca");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
curl_easy_setopt (handle, CURLOPT_DEBUGFUNCTION, &curlDebug);
|
||||||
|
curl_easy_setopt (handle, CURLOPT_DEBUGDATA, nullptr);
|
||||||
|
curl_easy_setopt (handle, CURLOPT_VERBOSE, 1L);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// write result into string
|
||||||
|
m_uploadLogResult.clear ();
|
||||||
|
curl_easy_setopt (handle, CURLOPT_WRITEFUNCTION, &curlCallback);
|
||||||
|
curl_easy_setopt (handle, CURLOPT_WRITEDATA, &m_uploadLogResult);
|
||||||
|
|
||||||
|
// set headers
|
||||||
|
static char contentType[] = "Content-Type: multipart/form-data";
|
||||||
|
static curl_slist const headers = {contentType, nullptr};
|
||||||
|
curl_easy_setopt (handle, CURLOPT_URL, "https://hastebin.com/documents");
|
||||||
|
curl_easy_setopt (handle, CURLOPT_HTTPHEADER, &headers);
|
||||||
|
|
||||||
|
// set form data
|
||||||
|
auto const mime = curl_mime_init (handle);
|
||||||
|
auto const part = curl_mime_addpart (mime);
|
||||||
|
curl_mime_name (part, "data");
|
||||||
|
curl_mime_data (part, m_uploadLogData.data (), m_uploadLogData.size ());
|
||||||
|
curl_easy_setopt (handle, CURLOPT_MIMEPOST, mime);
|
||||||
|
|
||||||
|
// add to multi handle
|
||||||
|
curl_multi_add_handle (m_uploadLogCurlM, handle);
|
||||||
|
|
||||||
|
// signal network thread to process
|
||||||
|
m_uploadLogMime = mime;
|
||||||
|
m_uploadLogCurl.store (handle, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::MenuItem ("About"))
|
||||||
|
m_showAbout = true;
|
||||||
|
|
||||||
|
ImGui::EndMenu ();
|
||||||
|
}
|
||||||
|
ImGui::EndMenuBar ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_showSettings)
|
||||||
|
{
|
||||||
|
if (!prevShowSettings)
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = m_config->lockGuard ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_userSetting = m_config->user ();
|
||||||
|
m_userSetting.resize (32);
|
||||||
|
|
||||||
|
m_passSetting = m_config->pass ();
|
||||||
|
m_passSetting.resize (32);
|
||||||
|
|
||||||
|
m_portSetting = m_config->port ();
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
m_getMTimeSetting = m_config->getMTime ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
m_enableAPSetting = m_config->enableAP ();
|
||||||
|
|
||||||
|
m_ssidSetting = m_config->ssid ();
|
||||||
|
m_ssidSetting.resize (19);
|
||||||
|
|
||||||
|
m_passphraseSetting = m_config->passphrase ();
|
||||||
|
m_passphraseSetting.resize (63);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ImGui::OpenPopup ("Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
showSettings ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_showAbout)
|
||||||
|
{
|
||||||
|
if (!prevShowAbout)
|
||||||
|
ImGui::OpenPopup ("About");
|
||||||
|
|
||||||
|
showAbout ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::showSettings ()
|
||||||
|
{
|
||||||
|
#ifdef __3DS__
|
||||||
|
auto const &io = ImGui::GetIO ();
|
||||||
|
auto const width = io.DisplaySize.x;
|
||||||
|
auto const height = io.DisplaySize.y;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.5f));
|
||||||
|
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f));
|
||||||
|
if (ImGui::BeginPopupModal ("Settings",
|
||||||
|
nullptr,
|
||||||
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
|
||||||
|
#else
|
||||||
|
if (ImGui::BeginPopupModal ("Settings", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
ImGui::InputText ("User",
|
||||||
|
m_userSetting.data (),
|
||||||
|
m_userSetting.size (),
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
|
||||||
|
ImGui::InputText ("Pass",
|
||||||
|
m_passSetting.data (),
|
||||||
|
m_passSetting.size (),
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_Password);
|
||||||
|
|
||||||
|
ImGui::InputScalar ("Port",
|
||||||
|
ImGuiDataType_U16,
|
||||||
|
&m_portSetting,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
"%u",
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
ImGui::Checkbox ("Get mtime", &m_getMTimeSetting);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
ImGui::Checkbox ("Enable Access Point", &m_enableAPSetting);
|
||||||
|
|
||||||
|
ImGui::InputText ("SSID",
|
||||||
|
m_ssidSetting.data (),
|
||||||
|
m_ssidSetting.size (),
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll);
|
||||||
|
auto const ssidError = platform::validateSSID (m_ssidSetting);
|
||||||
|
if (ssidError)
|
||||||
|
ImGui::TextColored (ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), ssidError);
|
||||||
|
|
||||||
|
ImGui::InputText ("Passphrase",
|
||||||
|
m_passphraseSetting.data (),
|
||||||
|
m_passphraseSetting.size (),
|
||||||
|
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_Password);
|
||||||
|
auto const passphraseError = platform::validatePassphrase (m_passphraseSetting);
|
||||||
|
if (passphraseError)
|
||||||
|
ImGui::TextColored (ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), passphraseError);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static ImVec2 const sizes[] = {
|
||||||
|
ImGui::CalcTextSize ("Apply"),
|
||||||
|
ImGui::CalcTextSize ("Save"),
|
||||||
|
ImGui::CalcTextSize ("Reset"),
|
||||||
|
ImGui::CalcTextSize ("Cancel"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto const maxWidth = std::max_element (
|
||||||
|
std::begin (sizes), std::end (sizes), [] (auto const &lhs_, auto const &rhs_) {
|
||||||
|
return lhs_.x < rhs_.x;
|
||||||
|
})->x;
|
||||||
|
|
||||||
|
static auto const maxHeight = std::max_element (
|
||||||
|
std::begin (sizes), std::end (sizes), [] (auto const &lhs_, auto const &rhs_) {
|
||||||
|
return lhs_.y < rhs_.y;
|
||||||
|
})->y;
|
||||||
|
|
||||||
|
auto const &style = ImGui::GetStyle ();
|
||||||
|
auto const width = maxWidth + 2 * style.FramePadding.x;
|
||||||
|
auto const height = maxHeight + 2 * style.FramePadding.y;
|
||||||
|
|
||||||
|
auto const apply = ImGui::Button ("Apply", ImVec2 (width, height));
|
||||||
|
ImGui::SameLine ();
|
||||||
|
auto const save = ImGui::Button ("Save", ImVec2 (width, height));
|
||||||
|
ImGui::SameLine ();
|
||||||
|
auto const reset = ImGui::Button ("Reset", ImVec2 (width, height));
|
||||||
|
ImGui::SameLine ();
|
||||||
|
auto const cancel = ImGui::Button ("Cancel", ImVec2 (width, height));
|
||||||
|
|
||||||
|
if (apply || save)
|
||||||
|
{
|
||||||
|
m_showSettings = false;
|
||||||
|
ImGui::CloseCurrentPopup ();
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = m_config->lockGuard ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_config->setUser (m_userSetting);
|
||||||
|
m_config->setPass (m_passSetting);
|
||||||
|
m_config->setPort (m_portSetting);
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
m_config->setGetMTime (m_getMTimeSetting);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
m_config->setEnableAP (m_enableAPSetting);
|
||||||
|
m_config->setSSID (m_ssidSetting);
|
||||||
|
m_config->setPassphrase (m_passphraseSetting);
|
||||||
|
m_apError = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UniqueSocket socket;
|
||||||
|
LOCKED (socket = std::move (m_socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = m_config->lockGuard ();
|
||||||
|
#endif
|
||||||
|
if (!m_config->save (FTPDCONFIG))
|
||||||
|
error ("Failed to save config\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset)
|
||||||
|
{
|
||||||
|
static auto const defaults = FtpConfig::create ();
|
||||||
|
|
||||||
|
m_userSetting = defaults->user ();
|
||||||
|
m_passSetting = defaults->pass ();
|
||||||
|
m_portSetting = defaults->port ();
|
||||||
|
#ifdef __3DS__
|
||||||
|
m_getMTimeSetting = defaults->getMTime ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
m_enableAPSetting = defaults->enableAP ();
|
||||||
|
m_ssidSetting = defaults->ssid ();
|
||||||
|
m_passphraseSetting = defaults->passphrase ();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apply || save || cancel)
|
||||||
|
{
|
||||||
|
m_showSettings = false;
|
||||||
|
ImGui::CloseCurrentPopup ();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::showAbout ()
|
||||||
|
{
|
||||||
|
auto const &io = ImGui::GetIO ();
|
||||||
|
auto const width = io.DisplaySize.x;
|
||||||
|
auto const height = io.DisplaySize.y;
|
||||||
|
|
||||||
|
#ifdef __3DS__
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.5f));
|
||||||
|
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.5f));
|
||||||
|
#else
|
||||||
|
ImGui::SetNextWindowSize (ImVec2 (width * 0.8f, height * 0.8f));
|
||||||
|
ImGui::SetNextWindowPos (ImVec2 (width * 0.1f, height * 0.1f));
|
||||||
|
#endif
|
||||||
|
if (ImGui::BeginPopupModal ("About",
|
||||||
|
nullptr,
|
||||||
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted (STATUS_STRING);
|
||||||
|
ImGui::TextWrapped ("Copyright © 2023 Michael Theall, Dave Murphy, TuxSH");
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::Text ("Platform: %s", io.BackendPlatformName);
|
||||||
|
ImGui::Text ("Renderer: %s", io.BackendRendererName);
|
||||||
|
|
||||||
|
if (ImGui::Button ("OK", ImVec2 (100, 0)))
|
||||||
|
{
|
||||||
|
m_showAbout = false;
|
||||||
|
ImGui::CloseCurrentPopup ();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator ();
|
||||||
|
if (ImGui::TreeNode (g_dearImGuiVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_dearImGuiCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_mitLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(NDS)
|
||||||
|
#elif defined(__3DS__)
|
||||||
|
if (ImGui::TreeNode (g_libctruVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_zlibLicense);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_zlibLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode (g_citro3dVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_citro3dCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_zlibLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__SWITCH__)
|
||||||
|
if (ImGui::TreeNode (g_libnxVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_libnxCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_libnxLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode (g_deko3dVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_deko3dCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_zlibLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::TreeNode (g_zstdVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_zstdCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_bsdLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (ImGui::TreeNode (g_glfwVersion))
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped ("%s", g_glfwCopyright);
|
||||||
|
ImGui::Separator ();
|
||||||
|
ImGui::TextWrapped ("%s", g_zlibLicense);
|
||||||
|
ImGui::TreePop ();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ImGui::EndPopup ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void FtpServer::loop ()
|
||||||
|
{
|
||||||
|
if (!m_socket)
|
||||||
|
{
|
||||||
|
#ifndef CLASSIC
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
if (!m_apError)
|
||||||
|
{
|
||||||
|
bool enable;
|
||||||
|
std::string ssid;
|
||||||
|
std::string passphrase;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const lock = m_config->lockGuard ();
|
||||||
|
enable = m_config->enableAP ();
|
||||||
|
ssid = m_config->ssid ();
|
||||||
|
passphrase = m_config->passphrase ();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_apError = !platform::enableAP (enable, ssid, passphrase);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (platform::networkVisible ())
|
||||||
|
handleNetworkFound ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
{
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
if (m_uploadLogCurl.load (std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
int busy = 0;
|
||||||
|
auto const mc = curl_multi_perform (m_uploadLogCurlM, &busy);
|
||||||
|
if (mc != CURLM_OK)
|
||||||
|
{
|
||||||
|
error ("curl_multi_perform: %d %s\n", mc, curl_multi_strerror (mc));
|
||||||
|
|
||||||
|
curl_multi_remove_handle (m_uploadLogCurlM, m_uploadLogCurl);
|
||||||
|
curl_easy_cleanup (m_uploadLogCurl);
|
||||||
|
curl_mime_free (m_uploadLogMime);
|
||||||
|
m_uploadLogMime = nullptr;
|
||||||
|
m_uploadLogCurl.store (nullptr, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
auto const msg = curl_multi_info_read (m_uploadLogCurlM, &count);
|
||||||
|
if (msg && msg->msg == CURLMSG_DONE && msg->easy_handle == m_uploadLogCurl)
|
||||||
|
{
|
||||||
|
if (msg->data.result != CURLE_OK)
|
||||||
|
info ("cURL finished with status %d\n", msg->data.result);
|
||||||
|
|
||||||
|
if (m_uploadLogResult.starts_with ("{\"key\":\""))
|
||||||
|
{
|
||||||
|
auto const key = m_uploadLogResult.substr (8, 10);
|
||||||
|
info ("https://hastebin.com/%s\n", key.c_str ());
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_multi_remove_handle (m_uploadLogCurlM, m_uploadLogCurl);
|
||||||
|
curl_easy_cleanup (m_uploadLogCurl);
|
||||||
|
curl_mime_free (m_uploadLogMime);
|
||||||
|
m_uploadLogMime = nullptr;
|
||||||
|
m_uploadLogCurl.store (nullptr, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// poll listen socket
|
||||||
|
if (m_socket)
|
||||||
|
{
|
||||||
|
Socket::PollInfo info{*m_socket, POLLIN, 0};
|
||||||
|
auto const rc = Socket::poll (&info, 1, 0ms);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
handleNetworkLost ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc > 0 && (info.revents & POLLIN))
|
||||||
|
{
|
||||||
|
auto socket = m_socket->accept ();
|
||||||
|
if (socket)
|
||||||
|
{
|
||||||
|
auto session = FtpSession::create (*m_config, std::move (socket));
|
||||||
|
LOCKED (m_sessions.emplace_back (std::move (session)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleNetworkLost ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<UniqueFtpSession> deadSessions;
|
||||||
|
{
|
||||||
|
// remove dead sessions
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (m_lock);
|
||||||
|
#endif
|
||||||
|
auto it = std::begin (m_sessions);
|
||||||
|
while (it != std::end (m_sessions))
|
||||||
|
{
|
||||||
|
auto &session = *it;
|
||||||
|
if (session->dead ())
|
||||||
|
{
|
||||||
|
deadSessions.emplace_back (std::move (session));
|
||||||
|
it = m_sessions.erase (it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// poll sessions
|
||||||
|
if (!m_sessions.empty ())
|
||||||
|
{
|
||||||
|
if (!FtpSession::poll (m_sessions))
|
||||||
|
handleNetworkLost ();
|
||||||
|
}
|
||||||
|
#ifndef NDS
|
||||||
|
// avoid busy polling in background thread
|
||||||
|
else
|
||||||
|
platform::Thread::sleep (16ms);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void FtpServer::threadFunc ()
|
||||||
|
{
|
||||||
|
while (!m_quit)
|
||||||
|
loop ();
|
||||||
|
}
|
2847
source/ftpSession.cpp
Normal file
2847
source/ftpSession.cpp
Normal file
File diff suppressed because it is too large
Load Diff
108
source/ioBuffer.cpp
Normal file
108
source/ioBuffer.cpp
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "ioBuffer.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
IOBuffer::~IOBuffer () = default;
|
||||||
|
|
||||||
|
IOBuffer::IOBuffer (std::size_t const size_)
|
||||||
|
: m_buffer (std::make_unique<char[]> (size_)), m_size (size_)
|
||||||
|
{
|
||||||
|
assert (size_ > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *IOBuffer::freeArea () const
|
||||||
|
{
|
||||||
|
assert (m_end < m_size);
|
||||||
|
return &m_buffer[m_end];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t IOBuffer::freeSize () const
|
||||||
|
{
|
||||||
|
assert (m_size >= m_end);
|
||||||
|
return m_size - m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOBuffer::markFree (std::size_t size_)
|
||||||
|
{
|
||||||
|
assert (m_end >= m_start);
|
||||||
|
assert (m_end - m_start >= size_);
|
||||||
|
m_start += size_;
|
||||||
|
|
||||||
|
// reset back to beginning
|
||||||
|
if (m_start == m_end)
|
||||||
|
{
|
||||||
|
m_start = 0;
|
||||||
|
m_end = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *IOBuffer::usedArea () const
|
||||||
|
{
|
||||||
|
assert (m_start < m_size);
|
||||||
|
return &m_buffer[m_start];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t IOBuffer::usedSize () const
|
||||||
|
{
|
||||||
|
assert (m_end >= m_start);
|
||||||
|
return m_end - m_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOBuffer::markUsed (std::size_t size_)
|
||||||
|
{
|
||||||
|
assert (m_size >= m_end);
|
||||||
|
assert (m_size - m_end >= size_);
|
||||||
|
m_end += size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IOBuffer::empty () const
|
||||||
|
{
|
||||||
|
assert (m_end >= m_start);
|
||||||
|
return (m_end - m_start) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t IOBuffer::capacity () const
|
||||||
|
{
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOBuffer::clear ()
|
||||||
|
{
|
||||||
|
m_start = 0;
|
||||||
|
m_end = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IOBuffer::coalesce ()
|
||||||
|
{
|
||||||
|
assert (m_size >= m_end);
|
||||||
|
assert (m_end >= m_start);
|
||||||
|
|
||||||
|
auto const size = m_end - m_start;
|
||||||
|
if (size != 0)
|
||||||
|
std::memmove (&m_buffer[0], &m_buffer[m_start], size);
|
||||||
|
|
||||||
|
m_end -= m_start;
|
||||||
|
m_start = 0;
|
||||||
|
}
|
304
source/log.cpp
Normal file
304
source/log.cpp
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#ifndef __WIIU__
|
||||||
|
#include "imgui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <ranges>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef __WIIU__
|
||||||
|
#include <coreinit/debug.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
#ifdef __3DS__
|
||||||
|
/// \brief Maximum number of log messages to keep
|
||||||
|
constexpr auto MAX_LOGS = 250;
|
||||||
|
#else
|
||||||
|
/// \brief Maximum number of log messages to keep
|
||||||
|
constexpr auto MAX_LOGS = 10000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CLASSIC
|
||||||
|
bool s_logUpdated = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// \brief Message prefix
|
||||||
|
static char const *const s_prefix[] = {
|
||||||
|
[DEBUGLOG] = "[DEBUG]",
|
||||||
|
[INFO] = "[INFO]",
|
||||||
|
[ERROR] = "[ERROR]",
|
||||||
|
[COMMAND] = "[COMMAND]",
|
||||||
|
[RESPONSE] = "[RESPONSE]",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Log message
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param level_ Log level
|
||||||
|
/// \param message_ Log message
|
||||||
|
Message (LogLevel const level_, std::string message_)
|
||||||
|
: level (level_), message (std::move (message_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Log level
|
||||||
|
LogLevel level;
|
||||||
|
/// \brief Log message
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Log messages
|
||||||
|
std::vector<Message> s_messages;
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
/// \brief Log lock
|
||||||
|
platform::Mutex s_lock;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawLog ()
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CLASSIC
|
||||||
|
if (!s_logUpdated)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s_logUpdated = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto const maxLogs =
|
||||||
|
#ifdef __WIIU__
|
||||||
|
1000;
|
||||||
|
#elif defined(CLASSIC)
|
||||||
|
g_logConsole.windowHeight;
|
||||||
|
#else
|
||||||
|
MAX_LOGS;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (s_messages.size () > static_cast<unsigned> (maxLogs))
|
||||||
|
{
|
||||||
|
auto const begin = std::begin (s_messages);
|
||||||
|
auto const end = std::next (begin, s_messages.size () - maxLogs);
|
||||||
|
s_messages.erase (begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CLASSIC
|
||||||
|
char const *const s_colors[] = {
|
||||||
|
[DEBUGLOG] = "\x1b[33;1m", // yellow
|
||||||
|
[INFO] = "\x1b[37;1m", // white
|
||||||
|
[ERROR] = "\x1b[31;1m", // red
|
||||||
|
[COMMAND] = "\x1b[32;1m", // green
|
||||||
|
[RESPONSE] = "\x1b[36;1m", // cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __WIIU__
|
||||||
|
for (auto &cur : s_messages)
|
||||||
|
{
|
||||||
|
OSReport ("ftpiiu plugin: %s %s\x1b[0m", s_colors[cur.level], cur.message.c_str ());
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
auto it = std::begin (s_messages);
|
||||||
|
if (s_messages.size () > static_cast<unsigned> (g_logConsole.windowHeight))
|
||||||
|
it = std::next (it, s_messages.size () - g_logConsole.windowHeight);
|
||||||
|
|
||||||
|
consoleSelect (&g_logConsole);
|
||||||
|
while (it != std::end (s_messages))
|
||||||
|
{
|
||||||
|
std::fputs (s_colors[it->level], stdout);
|
||||||
|
std::fputs (it->message.c_str (), stdout);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
std::fflush (stdout);
|
||||||
|
#endif
|
||||||
|
s_messages.clear ();
|
||||||
|
#else
|
||||||
|
ImVec4 const s_colors[] = {
|
||||||
|
[DEBUG] = ImVec4 (1.0f, 1.0f, 0.4f, 1.0f), // yellow
|
||||||
|
[INFO] = ImGui::GetStyleColorVec4 (ImGuiCol_Text), // normal
|
||||||
|
[ERROR] = ImVec4 (1.0f, 0.4f, 0.4f, 1.0f), // red
|
||||||
|
[COMMAND] = ImVec4 (0.4f, 1.0f, 0.4f, 1.0f), // green
|
||||||
|
[RESPONSE] = ImVec4 (0.4f, 1.0f, 1.0f, 1.0f), // cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const &message : s_messages)
|
||||||
|
{
|
||||||
|
ImGui::PushStyleColor (ImGuiCol_Text, s_colors[message.level]);
|
||||||
|
ImGui::TextUnformatted (s_prefix[message.level]);
|
||||||
|
ImGui::SameLine ();
|
||||||
|
ImGui::TextUnformatted (message.message.c_str ());
|
||||||
|
ImGui::PopStyleColor ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto-scroll if scroll bar is at end
|
||||||
|
if (ImGui::GetScrollY () >= ImGui::GetScrollMaxY ())
|
||||||
|
ImGui::SetScrollHereY (1.0f);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
std::string getLog ()
|
||||||
|
{
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (s_messages.empty ())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<Message const *> stack;
|
||||||
|
stack.reserve (s_messages.size ());
|
||||||
|
|
||||||
|
std::size_t size = 0;
|
||||||
|
for (auto const &msg : s_messages | std::views::reverse)
|
||||||
|
{
|
||||||
|
if (size + msg.message.size () > 1024 * 1024)
|
||||||
|
break;
|
||||||
|
|
||||||
|
size += msg.message.size ();
|
||||||
|
stack.emplace_back (&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string log;
|
||||||
|
log.reserve (size);
|
||||||
|
|
||||||
|
for (auto const &msg : stack | std::views::reverse)
|
||||||
|
log += msg->message;
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void debug (char const *const fmt_, ...)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt_);
|
||||||
|
addLog (DEBUGLOG, fmt_, ap);
|
||||||
|
va_end (ap);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void info (char const *const fmt_, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt_);
|
||||||
|
addLog (INFO, fmt_, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error (char const *const fmt_, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt_);
|
||||||
|
addLog (ERROR, fmt_, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void command (char const *const fmt_, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt_);
|
||||||
|
addLog (COMMAND, fmt_, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void response (char const *const fmt_, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt_);
|
||||||
|
addLog (RESPONSE, fmt_, ap);
|
||||||
|
va_end (ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLog (LogLevel const level_, char const *const fmt_, va_list ap_)
|
||||||
|
{
|
||||||
|
#ifdef NDEBUG
|
||||||
|
if (level_ == DEBUG)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(NDS) && !defined(__WIIU__)
|
||||||
|
thread_local
|
||||||
|
#endif
|
||||||
|
#if !defined(__WIIU__)
|
||||||
|
static
|
||||||
|
#endif
|
||||||
|
char buffer[1024];
|
||||||
|
|
||||||
|
std::vsnprintf (buffer, sizeof (buffer), fmt_, ap_);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// std::fprintf (stderr, "%s", s_prefix[level_]);
|
||||||
|
// std::fputs (buffer, stderr);
|
||||||
|
#endif
|
||||||
|
s_messages.emplace_back (level_, buffer);
|
||||||
|
#ifdef CLASSIC
|
||||||
|
s_logUpdated = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLog (LogLevel const level_, std::string_view const message_)
|
||||||
|
{
|
||||||
|
#ifdef NDEBUG
|
||||||
|
if (level_ == DEBUG)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto msg = std::string (message_);
|
||||||
|
for (auto &c : msg)
|
||||||
|
{
|
||||||
|
// replace nul-characters with ? to avoid truncation
|
||||||
|
if (c == '\0')
|
||||||
|
c = '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDS
|
||||||
|
auto const lock = std::scoped_lock (s_lock);
|
||||||
|
#endif
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// std::fprintf (stderr, "%s", s_prefix[level_]);
|
||||||
|
// std::fwrite (msg.data (), 1, msg.size (), stderr);
|
||||||
|
#endif
|
||||||
|
s_messages.emplace_back (level_, msg);
|
||||||
|
#ifdef CLASSIC
|
||||||
|
s_logUpdated = true;
|
||||||
|
#endif
|
||||||
|
}
|
80
source/main.cpp
Normal file
80
source/main.cpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2022 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include "ftpServer.h"
|
||||||
|
#include "log.h"
|
||||||
|
#ifndef __WIIU__
|
||||||
|
#include "imgui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
int main (int argc_, char *argv_[])
|
||||||
|
{
|
||||||
|
#ifndef CLASSIC
|
||||||
|
curl_global_init (CURL_GLOBAL_ALL);
|
||||||
|
IMGUI_CHECKVERSION ();
|
||||||
|
ImGui::CreateContext ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!platform::init ())
|
||||||
|
{
|
||||||
|
#ifndef CLASSIC
|
||||||
|
ImGui::DestroyContext ();
|
||||||
|
#endif
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
auto &style = ImGui::GetStyle ();
|
||||||
|
|
||||||
|
// turn off window rounding
|
||||||
|
style.WindowRounding = 0.0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto server = FtpServer::create ();
|
||||||
|
|
||||||
|
while (platform::loop ())
|
||||||
|
{
|
||||||
|
#ifndef NO_CONSOLE
|
||||||
|
server->draw ();
|
||||||
|
platform::render ();
|
||||||
|
#else
|
||||||
|
drawLog ();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up resources before exiting switch/3ds services
|
||||||
|
server.reset ();
|
||||||
|
|
||||||
|
platform::exit ();
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
ImGui::DestroyContext ();
|
||||||
|
curl_global_cleanup ();
|
||||||
|
#endif
|
||||||
|
}
|
189
source/sockAddr.cpp
Normal file
189
source/sockAddr.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "sockAddr.h"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
SockAddr::~SockAddr () = default;
|
||||||
|
|
||||||
|
SockAddr::SockAddr () = default;
|
||||||
|
|
||||||
|
SockAddr::SockAddr (SockAddr const &that_) = default;
|
||||||
|
|
||||||
|
SockAddr::SockAddr (SockAddr &&that_) = default;
|
||||||
|
|
||||||
|
SockAddr &SockAddr::operator= (SockAddr const &that_) = default;
|
||||||
|
|
||||||
|
SockAddr &SockAddr::operator= (SockAddr &&that_) = default;
|
||||||
|
|
||||||
|
SockAddr::SockAddr (struct sockaddr const &addr_)
|
||||||
|
{
|
||||||
|
switch (addr_.sa_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in));
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifndef NO_IPV6
|
||||||
|
case AF_INET6:
|
||||||
|
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6));
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr::SockAddr (struct sockaddr_in const &addr_)
|
||||||
|
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
||||||
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(__3DS__) && !defined(__WIIU__)
|
||||||
|
SockAddr::SockAddr (struct sockaddr_in6 const &addr_)
|
||||||
|
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
||||||
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET6);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SockAddr::SockAddr (struct sockaddr_storage const &addr_)
|
||||||
|
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr::operator struct sockaddr_in const & () const
|
||||||
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET);
|
||||||
|
return reinterpret_cast<struct sockaddr_in const &> (m_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(__3DS__) && !defined(__WIIU__)
|
||||||
|
SockAddr::operator struct sockaddr_in6 const & () const
|
||||||
|
{
|
||||||
|
assert (m_addr.ss_family == AF_INET6);
|
||||||
|
return reinterpret_cast<struct sockaddr_in6 const &> (m_addr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SockAddr::operator struct sockaddr_storage const & () const
|
||||||
|
{
|
||||||
|
return m_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr::operator struct sockaddr * ()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<struct sockaddr *> (&m_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr::operator struct sockaddr const * () const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<struct sockaddr const *> (&m_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SockAddr::setPort (std::uint16_t const port_)
|
||||||
|
{
|
||||||
|
switch (m_addr.ss_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
reinterpret_cast<struct sockaddr_in *> (&m_addr)->sin_port = htons (port_);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
#ifndef NO_IPV6
|
||||||
|
case AF_INET6:
|
||||||
|
reinterpret_cast<struct sockaddr_in6 *> (&m_addr)->sin6_port = htons (port_);
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t SockAddr::port () const
|
||||||
|
{
|
||||||
|
switch (m_addr.ss_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
return ntohs (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_port);
|
||||||
|
|
||||||
|
#ifndef NO_IPV6
|
||||||
|
case AF_INET6:
|
||||||
|
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *SockAddr::name (char *buffer_, std::size_t size_) const
|
||||||
|
{
|
||||||
|
switch (m_addr.ss_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
#ifdef NDS
|
||||||
|
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr);
|
||||||
|
#else
|
||||||
|
return inet_ntop (AF_INET,
|
||||||
|
&reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr,
|
||||||
|
buffer_,
|
||||||
|
size_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NO_IPV6
|
||||||
|
case AF_INET6:
|
||||||
|
return inet_ntop (AF_INET6,
|
||||||
|
&reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_addr,
|
||||||
|
buffer_,
|
||||||
|
size_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::abort ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *SockAddr::name () const
|
||||||
|
{
|
||||||
|
#if defined(NDS) || defined(__WIIU__)
|
||||||
|
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr);
|
||||||
|
#else
|
||||||
|
#ifdef NO_IPV6
|
||||||
|
thread_local static char buffer[INET_ADDRSTRLEN];
|
||||||
|
#else
|
||||||
|
thread_local static char buffer[INET6_ADDRSTRLEN];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return name (buffer, sizeof (buffer));
|
||||||
|
#endif
|
||||||
|
}
|
441
source/socket.cpp
Normal file
441
source/socket.cpp
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
Socket::~Socket ()
|
||||||
|
{
|
||||||
|
if (m_listening)
|
||||||
|
info ("Stop listening on [%s]:%u\n", m_sockName.name (), m_sockName.port ());
|
||||||
|
|
||||||
|
if (m_connected)
|
||||||
|
info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
if (::closesocket (m_fd) != 0)
|
||||||
|
error ("closesocket: %s\n", std::strerror (errno));
|
||||||
|
#else
|
||||||
|
if (::close (m_fd) != 0)
|
||||||
|
error ("close: %s\n", std::strerror (errno));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::Socket (int const fd_) : m_fd (fd_), m_listening (false), m_connected (false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::Socket (int const fd_, SockAddr const &sockName_, SockAddr const &peerName_)
|
||||||
|
: m_sockName (sockName_),
|
||||||
|
m_peerName (peerName_),
|
||||||
|
m_fd (fd_),
|
||||||
|
m_listening (false),
|
||||||
|
m_connected (true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueSocket Socket::accept ()
|
||||||
|
{
|
||||||
|
SockAddr addr;
|
||||||
|
socklen_t addrLen = sizeof (struct sockaddr_storage);
|
||||||
|
|
||||||
|
auto const fd = ::accept (m_fd, addr, &addrLen);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
error ("accept: %s\n", std::strerror (errno));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
info ("Accepted connection from [%s]:%u\n", addr.name (), addr.port ());
|
||||||
|
return UniqueSocket (new Socket (fd, m_sockName, addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::atMark ()
|
||||||
|
{
|
||||||
|
#ifdef NDS
|
||||||
|
errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
#else
|
||||||
|
auto const rc = ::sockatmark (m_fd);
|
||||||
|
|
||||||
|
if (rc < 0)
|
||||||
|
error ("sockatmark: %s\n", std::strerror (errno));
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::bind (SockAddr const &addr_)
|
||||||
|
{
|
||||||
|
switch (static_cast<struct sockaddr_storage const &> (addr_).ss_family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
if (::bind (m_fd, addr_, sizeof (struct sockaddr_in)) != 0)
|
||||||
|
{
|
||||||
|
error ("bind: %s\n", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
#ifndef NO_IPV6
|
||||||
|
case AF_INET6:
|
||||||
|
if (::bind (m_fd, addr_, sizeof (struct sockaddr_in6)) != 0)
|
||||||
|
{
|
||||||
|
error ("bind: %s\n", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
error ("bind: %s\n", std::strerror (errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr_.port () == 0)
|
||||||
|
{
|
||||||
|
// get socket name due to request for ephemeral port
|
||||||
|
socklen_t addrLen = sizeof (struct sockaddr_storage);
|
||||||
|
if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
|
||||||
|
error ("getsockname: %s\n", std::strerror (errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_sockName = addr_;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::connect (SockAddr const &addr_)
|
||||||
|
{
|
||||||
|
if (::connect (m_fd, addr_, sizeof (struct sockaddr_storage)) != 0)
|
||||||
|
{
|
||||||
|
if (errno != EINPROGRESS)
|
||||||
|
error ("connect: %s\n", std::strerror (errno));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_peerName = addr_;
|
||||||
|
m_connected = true;
|
||||||
|
info ("Connecting to [%s]:%u\n", addr_.name (), addr_.port ());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_peerName = addr_;
|
||||||
|
m_connected = true;
|
||||||
|
info ("Connected to [%s]:%u\n", addr_.name (), addr_.port ());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::listen (int const backlog_)
|
||||||
|
{
|
||||||
|
if (::listen (m_fd, backlog_) != 0)
|
||||||
|
{
|
||||||
|
error ("listen: %s\n", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_listening = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::shutdown (int const how_)
|
||||||
|
{
|
||||||
|
if (::shutdown (m_fd, how_) != 0)
|
||||||
|
{
|
||||||
|
error ("shutdown: %s\n", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_)
|
||||||
|
{
|
||||||
|
#ifdef NDS
|
||||||
|
errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
|
#else
|
||||||
|
struct linger linger;
|
||||||
|
linger.l_onoff = enable_;
|
||||||
|
linger.l_linger = time_.count ();
|
||||||
|
|
||||||
|
auto const rc = ::setsockopt (m_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof (linger));
|
||||||
|
if (rc != 0)
|
||||||
|
{
|
||||||
|
error ("setsockopt(SO_LINGER, %s, %lus): %s\n",
|
||||||
|
enable_ ? "on" : "off",
|
||||||
|
static_cast<unsigned long> (time_.count ()),
|
||||||
|
std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setNonBlocking (bool const nonBlocking_)
|
||||||
|
{
|
||||||
|
#ifdef NDS
|
||||||
|
unsigned long enable = nonBlocking_;
|
||||||
|
|
||||||
|
auto const rc = ::ioctl (m_fd, FIONBIO, &enable);
|
||||||
|
if (rc != 0)
|
||||||
|
{
|
||||||
|
error ("fcntl(FIONBIO, %d): %s\n", nonBlocking_, std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
auto flags = ::fcntl (m_fd, F_GETFL, 0);
|
||||||
|
if (flags == -1)
|
||||||
|
{
|
||||||
|
error ("fcntl(F_GETFL): %s\n", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonBlocking_)
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
else
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
|
||||||
|
if (::fcntl (m_fd, F_SETFL, flags) != 0)
|
||||||
|
{
|
||||||
|
error ("fcntl(F_SETFL, %d): %s\n", flags, std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setWinScale (const int val)
|
||||||
|
{
|
||||||
|
int const o = val;
|
||||||
|
|
||||||
|
if (::setsockopt (m_fd, SOL_SOCKET, SO_WINSCALE, &o, sizeof (o)) < 0)
|
||||||
|
{
|
||||||
|
error ("setsockopt(SO_WINSCALE, %d): %s\n", val, std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setReuseAddress (bool const reuse_)
|
||||||
|
{
|
||||||
|
int const reuse = reuse_;
|
||||||
|
if (::setsockopt (m_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)) != 0)
|
||||||
|
{
|
||||||
|
error ("setsockopt(SO_REUSEADDR, %s): %s\n", reuse_ ? "yes" : "no", std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setRecvBufferSize (std::size_t const size_)
|
||||||
|
{
|
||||||
|
int const size = size_;
|
||||||
|
if (::setsockopt (m_fd, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)) != 0)
|
||||||
|
{
|
||||||
|
error ("setsockopt(SO_RCVBUF, %zu): %s\n", size_, std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::setSendBufferSize (std::size_t const size_)
|
||||||
|
{
|
||||||
|
int const size = size_;
|
||||||
|
if (::setsockopt (m_fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size)) != 0)
|
||||||
|
{
|
||||||
|
error ("setsockopt(SO_SNDBUF, %zu): %s\n", size_, std::strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t>
|
||||||
|
Socket::read (void *const buffer_, std::size_t const size_, bool const oob_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_);
|
||||||
|
|
||||||
|
auto const rc = ::recv (m_fd, buffer_, size_, oob_ ? MSG_OOB : 0);
|
||||||
|
if (rc < 0 && errno != EWOULDBLOCK)
|
||||||
|
error ("recv: %s\n", std::strerror (errno));
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> Socket::read (IOBuffer &buffer_, bool const oob_)
|
||||||
|
{
|
||||||
|
assert (buffer_.freeSize () > 0);
|
||||||
|
|
||||||
|
auto const rc = read (buffer_.freeArea (), buffer_.freeSize (), oob_);
|
||||||
|
if (rc > 0)
|
||||||
|
buffer_.markUsed (rc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> Socket::write (void const *const buffer_, std::size_t const size_)
|
||||||
|
{
|
||||||
|
assert (buffer_);
|
||||||
|
assert (size_ > 0);
|
||||||
|
|
||||||
|
auto const rc = ::send (m_fd, buffer_, size_, 0);
|
||||||
|
if (rc < 0 && errno != EWOULDBLOCK)
|
||||||
|
error ("send: %s\n", std::strerror (errno));
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::make_signed_t<std::size_t> Socket::write (IOBuffer &buffer_)
|
||||||
|
{
|
||||||
|
assert (buffer_.usedSize () > 0);
|
||||||
|
|
||||||
|
auto const rc = write (buffer_.usedArea (), buffer_.usedSize ());
|
||||||
|
if (rc > 0)
|
||||||
|
buffer_.markFree (rc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr const &Socket::sockName () const
|
||||||
|
{
|
||||||
|
return m_sockName;
|
||||||
|
}
|
||||||
|
|
||||||
|
SockAddr const &Socket::peerName () const
|
||||||
|
{
|
||||||
|
return m_peerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueSocket Socket::create ()
|
||||||
|
{
|
||||||
|
auto const fd = ::socket (AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
error ("socket: %s\n", std::strerror (errno));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UniqueSocket (new Socket (fd));
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socket::poll (PollInfo *const info_,
|
||||||
|
std::size_t const count_,
|
||||||
|
std::chrono::milliseconds const timeout_)
|
||||||
|
{
|
||||||
|
if (count_ == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto const pfd = std::make_unique<struct pollfd[]> (count_);
|
||||||
|
for (std::size_t i = 0; i < count_; ++i)
|
||||||
|
{
|
||||||
|
pfd[i].fd = info_[i].socket.get ().m_fd;
|
||||||
|
pfd[i].events = info_[i].events;
|
||||||
|
pfd[i].revents = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const rc = ::poll (pfd.get (), count_, timeout_.count ());
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
error ("poll: %s\n", std::strerror (errno));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < count_; ++i)
|
||||||
|
info_[i].revents = pfd[i].revents;
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef NDS
|
||||||
|
extern "C" int poll (struct pollfd *const fds_, nfds_t const nfds_, int const timeout_)
|
||||||
|
{
|
||||||
|
fd_set readFds;
|
||||||
|
fd_set writeFds;
|
||||||
|
fd_set exceptFds;
|
||||||
|
|
||||||
|
FD_ZERO (&readFds);
|
||||||
|
FD_ZERO (&writeFds);
|
||||||
|
FD_ZERO (&exceptFds);
|
||||||
|
|
||||||
|
for (nfds_t i = 0; i < nfds_; ++i)
|
||||||
|
{
|
||||||
|
if (fds_[i].events & POLLIN)
|
||||||
|
FD_SET (fds_[i].fd, &readFds);
|
||||||
|
if (fds_[i].events & POLLOUT)
|
||||||
|
FD_SET (fds_[i].fd, &writeFds);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = timeout_ / 1000;
|
||||||
|
tv.tv_usec = (timeout_ % 1000) * 1000;
|
||||||
|
auto const rc = ::select (nfds_, &readFds, &writeFds, &exceptFds, &tv);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (nfds_t i = 0; i < nfds_; ++i)
|
||||||
|
{
|
||||||
|
bool counted = false;
|
||||||
|
fds_[i].revents = 0;
|
||||||
|
|
||||||
|
if (FD_ISSET (fds_[i].fd, &readFds))
|
||||||
|
{
|
||||||
|
counted = true;
|
||||||
|
fds_[i].revents |= POLLIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET (fds_[i].fd, &writeFds))
|
||||||
|
{
|
||||||
|
counted = true;
|
||||||
|
fds_[i].revents |= POLLOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET (fds_[i].fd, &exceptFds))
|
||||||
|
{
|
||||||
|
counted = true;
|
||||||
|
fds_[i].revents |= POLLERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counted)
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
#endif
|
42
source/wiiu/logger.c
Normal file
42
source/wiiu/logger.c
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifdef DEBUG
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <whb/log_cafe.h>
|
||||||
|
#include <whb/log_module.h>
|
||||||
|
#include <whb/log_udp.h>
|
||||||
|
|
||||||
|
uint32_t moduleLogInit = false;
|
||||||
|
uint32_t cafeLogInit = false;
|
||||||
|
uint32_t udpLogInit = false;
|
||||||
|
#endif // DEBUG
|
||||||
|
|
||||||
|
void initLogging ()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (!(moduleLogInit = WHBLogModuleInit ()))
|
||||||
|
{
|
||||||
|
cafeLogInit = WHBLogCafeInit ();
|
||||||
|
udpLogInit = WHBLogUdpInit ();
|
||||||
|
}
|
||||||
|
#endif // DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
void deinitLogging ()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (moduleLogInit)
|
||||||
|
{
|
||||||
|
WHBLogModuleDeinit ();
|
||||||
|
moduleLogInit = false;
|
||||||
|
}
|
||||||
|
if (cafeLogInit)
|
||||||
|
{
|
||||||
|
WHBLogCafeDeinit ();
|
||||||
|
cafeLogInit = false;
|
||||||
|
}
|
||||||
|
if (udpLogInit)
|
||||||
|
{
|
||||||
|
WHBLogUdpDeinit ();
|
||||||
|
udpLogInit = false;
|
||||||
|
}
|
||||||
|
#endif // DEBUG
|
||||||
|
}
|
67
source/wiiu/logger.h
Normal file
67
source/wiiu/logger.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <coreinit/debug.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <whb/log.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOG_APP_TYPE "P"
|
||||||
|
#define LOG_APP_NAME "homebrew_on_menu"
|
||||||
|
|
||||||
|
#define __FILENAME_X__ (strrchr (__FILE__, '\\') ? strrchr (__FILE__, '\\') + 1 : __FILE__)
|
||||||
|
#define __FILENAME__ (strrchr (__FILE__, '/') ? strrchr (__FILE__, '/') + 1 : __FILENAME_X__)
|
||||||
|
|
||||||
|
#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX (LOG_FUNC, "", "", FMT, ##ARGS)
|
||||||
|
|
||||||
|
#define LOG_EX(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
LOG_FUNC ("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, \
|
||||||
|
LOG_APP_TYPE, \
|
||||||
|
LOG_APP_NAME, \
|
||||||
|
__FILENAME__, \
|
||||||
|
__FUNCTION__, \
|
||||||
|
__LINE__, \
|
||||||
|
##ARGS); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#ifdef VERBOSE_DEBUG
|
||||||
|
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG (WHBLogPrintf, FMT, ##ARGS)
|
||||||
|
#else
|
||||||
|
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG (WHBLogPrintf, FMT, ##ARGS)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG (WHBLogWritef, FMT, ##ARGS)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
|
||||||
|
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
|
||||||
|
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX (WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX (OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
|
||||||
|
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX (OSReport, "##WARN ## ", "\n", FMT, ##ARGS)
|
||||||
|
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX (OSReport, "##INFO ## ", "\n", FMT, ##ARGS)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void initLogging ();
|
||||||
|
|
||||||
|
void deinitLogging ();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
486
source/wiiu/platform.cpp
Normal file
486
source/wiiu/platform.cpp
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
// ftpd is a server implementation based on the following:
|
||||||
|
// - RFC 959 (https://tools.ietf.org/html/rfc959)
|
||||||
|
// - RFC 3659 (https://tools.ietf.org/html/rfc3659)
|
||||||
|
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 Michael Theall
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#include "IOAbstraction.h"
|
||||||
|
#include "ftpServer.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <mocha/mocha.h>
|
||||||
|
#include <nn/ac.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <coreinit/thread.h>
|
||||||
|
#include <whb/proc.h>
|
||||||
|
#include <wups.h>
|
||||||
|
#include <wups/config/WUPSConfigCategory.h>
|
||||||
|
#include <wups/config/WUPSConfigItem.h>
|
||||||
|
#include <wups/config/WUPSConfigItemBoolean.h>
|
||||||
|
#include <wups/config/WUPSConfigItemStub.h>
|
||||||
|
|
||||||
|
#ifndef CLASSIC
|
||||||
|
#error "Wii U must be built in classic mode"
|
||||||
|
#endif
|
||||||
|
#define VERSION "v0.4.0"
|
||||||
|
#define VERSION_FULL VERSION VERSION_EXTRA
|
||||||
|
|
||||||
|
WUPS_PLUGIN_NAME ("ftpiiu");
|
||||||
|
WUPS_PLUGIN_DESCRIPTION ("FTP Server based on ftpd");
|
||||||
|
WUPS_PLUGIN_VERSION (VERSION_FULL);
|
||||||
|
WUPS_PLUGIN_AUTHOR ("mtheall, Maschell");
|
||||||
|
WUPS_PLUGIN_LICENSE ("GPL3");
|
||||||
|
|
||||||
|
WUPS_USE_WUT_DEVOPTAB ();
|
||||||
|
WUPS_USE_STORAGE ("ftpiiu"); // Unique id for the storage api
|
||||||
|
|
||||||
|
#define DEFAULT_FTPIIU_ENABLED_VALUE true
|
||||||
|
#define DEFAULT_SYSTEM_FILES_ALLOWED_VALUE false
|
||||||
|
|
||||||
|
#define FTPIIU_ENABLED_STRING "enabled"
|
||||||
|
#define SYSTEM_FILES_ALLOWED_STRING "systemFilesAllowed"
|
||||||
|
|
||||||
|
bool platform::networkVisible ()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool platform::networkAddress (SockAddr &addr_)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr = {};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
nn::ac::GetAssignedAddress (&addr.sin_addr.s_addr);
|
||||||
|
addr_ = addr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MochaUtilsStatus MountWrapper (const char *mount, const char *dev, const char *mountTo)
|
||||||
|
{
|
||||||
|
auto res = Mocha_MountFS (mount, dev, mountTo);
|
||||||
|
if (res == MOCHA_RESULT_ALREADY_EXISTS)
|
||||||
|
{
|
||||||
|
res = Mocha_MountFS (mount, nullptr, mountTo);
|
||||||
|
}
|
||||||
|
if (res == MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
std::string mountPath = std::string (mount) + ":/";
|
||||||
|
debug ("Mounted %s", mountPath.c_str ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR (
|
||||||
|
"Failed to mount %s: %s [%d]", mount, Mocha_GetStatusStr (res), res);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueFtpServer server = nullptr;
|
||||||
|
static bool sSystemFilesAllowed = DEFAULT_SYSTEM_FILES_ALLOWED_VALUE;
|
||||||
|
static bool sMochaPathsWereMounted = false;
|
||||||
|
static bool sFTPServerEnabled = DEFAULT_FTPIIU_ENABLED_VALUE;
|
||||||
|
|
||||||
|
void start_server ()
|
||||||
|
{
|
||||||
|
if (server != nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MochaUtilsStatus res;
|
||||||
|
if ((res = Mocha_InitLibrary ()) == MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
std::vector<std::string> virtualDirsInRoot;
|
||||||
|
if (sSystemFilesAllowed)
|
||||||
|
{
|
||||||
|
if (MountWrapper ("slccmpt01", "/dev/slccmpt01", "/vol/storage_slccmpt01") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("slccmpt01");
|
||||||
|
IOAbstraction::addVirtualPath ("slccmpt01:/", {});
|
||||||
|
}
|
||||||
|
if (MountWrapper ("storage_odd_tickets", nullptr, "/vol/storage_odd01") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_odd_tickets");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_odd_tickets:/", {});
|
||||||
|
}
|
||||||
|
if (MountWrapper ("storage_odd_updates", nullptr, "/vol/storage_odd02") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_odd_updates");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_odd_updates:/", {});
|
||||||
|
}
|
||||||
|
if (MountWrapper ("storage_odd_content", nullptr, "/vol/storage_odd03") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_odd_content");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_odd_content:/", {});
|
||||||
|
}
|
||||||
|
if (MountWrapper ("storage_odd_content2", nullptr, "/vol/storage_odd04") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_odd_content2");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_odd_content2:/", {});
|
||||||
|
}
|
||||||
|
if (MountWrapper ("storage_slc", "/dev/slc01", "/vol/storage_slc01") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_slc");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_slc:/", {});
|
||||||
|
}
|
||||||
|
if (Mocha_MountFS ("storage_mlc", nullptr, "/vol/storage_mlc01") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_mlc");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_mlc:/", {});
|
||||||
|
}
|
||||||
|
if (Mocha_MountFS ("storage_usb", nullptr, "/vol/storage_usb01") ==
|
||||||
|
MOCHA_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
virtualDirsInRoot.emplace_back ("storage_usb");
|
||||||
|
IOAbstraction::addVirtualPath ("storage_usb:/", {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtualDirsInRoot.emplace_back ("fs");
|
||||||
|
IOAbstraction::addVirtualPath (":/", virtualDirsInRoot);
|
||||||
|
IOAbstraction::addVirtualPath ("fs:/", std::vector<std::string>{"vol"});
|
||||||
|
IOAbstraction::addVirtualPath (
|
||||||
|
"fs:/vol", std::vector<std::string>{"external01", "content", "save"});
|
||||||
|
|
||||||
|
IOAbstraction::addVirtualPath ("fs:/vol/content", {});
|
||||||
|
sMochaPathsWereMounted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR (
|
||||||
|
"Failed to init libmocha: %s [%d]\n", Mocha_GetStatusStr (res), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
server = FtpServer::create ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop_server ()
|
||||||
|
{
|
||||||
|
server.reset ();
|
||||||
|
if (sMochaPathsWereMounted)
|
||||||
|
{
|
||||||
|
Mocha_UnmountFS ("slccmpt01");
|
||||||
|
Mocha_UnmountFS ("storage_odd_tickets");
|
||||||
|
Mocha_UnmountFS ("storage_odd_updates");
|
||||||
|
Mocha_UnmountFS ("storage_odd_content");
|
||||||
|
Mocha_UnmountFS ("storage_odd_content2");
|
||||||
|
Mocha_UnmountFS ("storage_slc");
|
||||||
|
Mocha_UnmountFS ("storage_mlc");
|
||||||
|
Mocha_UnmountFS ("storage_usb");
|
||||||
|
sMochaPathsWereMounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IOAbstraction::clear ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gFTPServerRunningChanged (ConfigItemBoolean *item, bool newValue)
|
||||||
|
{
|
||||||
|
sFTPServerEnabled = newValue;
|
||||||
|
if (!sFTPServerEnabled)
|
||||||
|
{
|
||||||
|
stop_server ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start_server ();
|
||||||
|
}
|
||||||
|
// If the value has changed, we store it in the storage.
|
||||||
|
auto res = WUPSStorageAPI::Store (FTPIIU_ENABLED_STRING, sFTPServerEnabled);
|
||||||
|
if (res != WUPS_STORAGE_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR ("Failed to store gFTPServerEnabled: %s (%d)\n",
|
||||||
|
WUPSStorageAPI::GetStatusStr (res).data (),
|
||||||
|
res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gSystemFilesAllowedChanged (ConfigItemBoolean *item, bool newValue)
|
||||||
|
{
|
||||||
|
// DEBUG_FUNCTION_LINE("New value in gFTPServerEnabled: %d", newValue);
|
||||||
|
if (server != nullptr)
|
||||||
|
{ // If the server is already running we need to restart it.
|
||||||
|
stop_server ();
|
||||||
|
sSystemFilesAllowed = newValue;
|
||||||
|
start_server ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sSystemFilesAllowed = newValue;
|
||||||
|
}
|
||||||
|
// If the value has changed, we store it in the storage.
|
||||||
|
auto res = WUPSStorageAPI::Store (SYSTEM_FILES_ALLOWED_STRING, sSystemFilesAllowed);
|
||||||
|
if (res != WUPS_STORAGE_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR ("Failed to store gSystemFilesAllowed: %s (%d)\n",
|
||||||
|
WUPSStorageAPI::GetStatusStr (res).data (),
|
||||||
|
res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback (WUPSConfigCategoryHandle rootHandle)
|
||||||
|
{
|
||||||
|
uint32_t hostIpAddress = 0;
|
||||||
|
nn::ac::GetAssignedAddress (&hostIpAddress);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WUPSConfigCategory root = WUPSConfigCategory (rootHandle);
|
||||||
|
root.add (WUPSConfigItemBoolean::Create (FTPIIU_ENABLED_STRING,
|
||||||
|
"Enable ftpd",
|
||||||
|
true,
|
||||||
|
sFTPServerEnabled,
|
||||||
|
&gFTPServerRunningChanged));
|
||||||
|
|
||||||
|
root.add (WUPSConfigItemBoolean::Create (SYSTEM_FILES_ALLOWED_STRING,
|
||||||
|
"Allow access to system files",
|
||||||
|
false,
|
||||||
|
sSystemFilesAllowed,
|
||||||
|
&gSystemFilesAllowedChanged));
|
||||||
|
|
||||||
|
root.add (WUPSConfigItemStub::Create ("==="));
|
||||||
|
|
||||||
|
char ipSettings[50];
|
||||||
|
if (hostIpAddress != 0)
|
||||||
|
{
|
||||||
|
snprintf (ipSettings,
|
||||||
|
50,
|
||||||
|
"IP of your console is %u.%u.%u.%u. Port %i",
|
||||||
|
(hostIpAddress >> 24) & 0xFF,
|
||||||
|
(hostIpAddress >> 16) & 0xFF,
|
||||||
|
(hostIpAddress >> 8) & 0xFF,
|
||||||
|
(hostIpAddress >> 0) & 0xFF,
|
||||||
|
21);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf (
|
||||||
|
ipSettings, sizeof (ipSettings), "The console is not connected to a network.");
|
||||||
|
}
|
||||||
|
|
||||||
|
root.add (WUPSConfigItemStub::Create (ipSettings));
|
||||||
|
root.add (WUPSConfigItemStub::Create ("You can connect with empty credentials"));
|
||||||
|
}
|
||||||
|
catch (std::exception &e)
|
||||||
|
{
|
||||||
|
OSReport ("fptiiu plugin: Exception: %s\n", e.what ());
|
||||||
|
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigMenuClosedCallback ()
|
||||||
|
{
|
||||||
|
WUPSStorageAPI::SaveStorage ();
|
||||||
|
}
|
||||||
|
|
||||||
|
INITIALIZE_PLUGIN ()
|
||||||
|
{
|
||||||
|
WUPSConfigAPIOptionsV1 configOptions = {.name = "ftpiiu"};
|
||||||
|
if (WUPSConfigAPI_Init (configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) !=
|
||||||
|
WUPSCONFIG_API_RESULT_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR ("Failed to init config api");
|
||||||
|
OSFatal ("ftpiiu plugin: Failed to init config api");
|
||||||
|
}
|
||||||
|
|
||||||
|
WUPSStorageError err;
|
||||||
|
if ((err = WUPSStorageAPI::GetOrStoreDefault (
|
||||||
|
FTPIIU_ENABLED_STRING, sFTPServerEnabled, DEFAULT_FTPIIU_ENABLED_VALUE)) !=
|
||||||
|
WUPS_STORAGE_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR ("Failed to get or create item \"%s\": %s (%d)\n",
|
||||||
|
FTPIIU_ENABLED_STRING,
|
||||||
|
WUPSStorageAPI_GetStatusStr (err),
|
||||||
|
err);
|
||||||
|
}
|
||||||
|
if ((err = WUPSStorageAPI::GetOrStoreDefault (SYSTEM_FILES_ALLOWED_STRING,
|
||||||
|
sSystemFilesAllowed,
|
||||||
|
DEFAULT_SYSTEM_FILES_ALLOWED_VALUE)) != WUPS_STORAGE_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR ("Failed to get or create item \"%s\": %s (%d)\n",
|
||||||
|
SYSTEM_FILES_ALLOWED_STRING,
|
||||||
|
WUPSStorageAPI_GetStatusStr (err),
|
||||||
|
err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((err = WUPSStorageAPI::SaveStorage ()) != WUPS_STORAGE_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
DEBUG_FUNCTION_LINE_ERR (
|
||||||
|
"Failed to save storage: %s (%d)\n", WUPSStorageAPI_GetStatusStr (err), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wiiu_init ()
|
||||||
|
{
|
||||||
|
nn::ac::Initialize ();
|
||||||
|
nn::ac::ConnectAsync ();
|
||||||
|
if (sFTPServerEnabled)
|
||||||
|
{
|
||||||
|
start_server ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_APPLICATION_START ()
|
||||||
|
{
|
||||||
|
initLogging ();
|
||||||
|
nn::ac::Initialize ();
|
||||||
|
nn::ac::ConnectAsync ();
|
||||||
|
|
||||||
|
wiiu_init ();
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_APPLICATION_ENDS ()
|
||||||
|
{
|
||||||
|
stop_server ();
|
||||||
|
deinitLogging ();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool platform::init ()
|
||||||
|
{
|
||||||
|
WHBProcInit ();
|
||||||
|
wiiu_init ();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool platform::loop ()
|
||||||
|
{
|
||||||
|
return WHBProcIsRunning ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::render ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::exit ()
|
||||||
|
{
|
||||||
|
IOAbstraction::clear ();
|
||||||
|
WHBProcShutdown ();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Platform thread pimpl
|
||||||
|
class platform::Thread::privateData_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
privateData_t () = default;
|
||||||
|
|
||||||
|
/// \brief Parameterized constructor
|
||||||
|
/// \param func_ Thread entry point
|
||||||
|
explicit privateData_t (std::function<void ()> &&func_) : thread (std::move (func_))
|
||||||
|
{
|
||||||
|
auto nativeHandle = (OSThread *)thread.native_handle ();
|
||||||
|
OSSetThreadName (nativeHandle, "ftpiiu");
|
||||||
|
while (!OSSetThreadAffinity (nativeHandle, OS_THREAD_ATTRIB_AFFINITY_CPU2))
|
||||||
|
{
|
||||||
|
OSSleepTicks (OSMillisecondsToTicks (16));
|
||||||
|
}
|
||||||
|
while (!OSSetThreadPriority (nativeHandle, 16))
|
||||||
|
{
|
||||||
|
OSSleepTicks (OSMillisecondsToTicks (16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Underlying thread
|
||||||
|
std::thread thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
platform::Thread::~Thread () = default;
|
||||||
|
|
||||||
|
platform::Thread::Thread () : m_d (new privateData_t ())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
platform::Thread::Thread (std::function<void ()> &&func_)
|
||||||
|
: m_d (new privateData_t (std::move (func_)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
platform::Thread::Thread (Thread &&that_) : m_d (new privateData_t ())
|
||||||
|
{
|
||||||
|
std::swap (m_d, that_.m_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
platform::Thread &platform::Thread::operator= (Thread &&that_)
|
||||||
|
{
|
||||||
|
std::swap (m_d, that_.m_d);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::Thread::join ()
|
||||||
|
{
|
||||||
|
m_d->thread.join ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for (timeout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
#define USE_STD_MUTEX 1
|
||||||
|
|
||||||
|
/// \brief Platform mutex pimpl
|
||||||
|
class platform::Mutex::privateData_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
#if USE_STD_MUTEX
|
||||||
|
/// \brief Underlying mutex
|
||||||
|
std::mutex mutex;
|
||||||
|
#else
|
||||||
|
/// \brief Underlying mutex
|
||||||
|
::Mutex mutex;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
platform::Mutex::~Mutex () = default;
|
||||||
|
|
||||||
|
platform::Mutex::Mutex () : m_d (new privateData_t ())
|
||||||
|
{
|
||||||
|
#if !USE_STD_MUTEX
|
||||||
|
mutexInit (&m_d->mutex);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::Mutex::lock ()
|
||||||
|
{
|
||||||
|
#if USE_STD_MUTEX
|
||||||
|
m_d->mutex.lock ();
|
||||||
|
#else
|
||||||
|
mutexLock (&m_d->mutex);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void platform::Mutex::unlock ()
|
||||||
|
{
|
||||||
|
#if USE_STD_MUTEX
|
||||||
|
m_d->mutex.unlock ();
|
||||||
|
#else
|
||||||
|
mutexUnlock (&m_d->mutex);
|
||||||
|
#endif
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
#include "BackgroundThread.hpp"
|
|
||||||
#include "ftp.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
BackgroundThread *BackgroundThread::instance = nullptr;
|
|
||||||
|
|
||||||
BackgroundThread::BackgroundThread() : BackgroundThreadWrapper(BackgroundThread::getPriority()) {
|
|
||||||
DEBUG_FUNCTION_LINE("Start FTP Server");
|
|
||||||
this->serverSocket = create_server(PORT);
|
|
||||||
OSMemoryBarrier();
|
|
||||||
DEBUG_FUNCTION_LINE_VERBOSE("Resume Thread");
|
|
||||||
CThread::resumeThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
BackgroundThread::~BackgroundThread() {
|
|
||||||
if (!isThreadTerminated()) {
|
|
||||||
DEBUG_FUNCTION_LINE("Shutting down FTP Server");
|
|
||||||
stopThread();
|
|
||||||
while (!hasThreadStopped()) {
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(10));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG_FUNCTION_LINE_WARN("Thread is already terminated");
|
|
||||||
}
|
|
||||||
if (this->serverSocket >= 0) {
|
|
||||||
cleanup_ftp();
|
|
||||||
network_close(this->serverSocket);
|
|
||||||
this->serverSocket = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL BackgroundThread::whileLoop() {
|
|
||||||
if (this->serverSocket >= 0) {
|
|
||||||
network_down = process_ftp_events(this->serverSocket);
|
|
||||||
if (network_down) {
|
|
||||||
DEBUG_FUNCTION_LINE_WARN("Network is down");
|
|
||||||
cleanup_ftp();
|
|
||||||
network_close(this->serverSocket);
|
|
||||||
this->serverSocket = -1;
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this->serverSocket = create_server(PORT);
|
|
||||||
if (this->serverSocket < 0) {
|
|
||||||
if (errno != EBUSY) {
|
|
||||||
DEBUG_FUNCTION_LINE_WARN("Creating server failed: %d", errno);
|
|
||||||
}
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/BackgroundThreadWrapper.hpp"
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include <coreinit/cache.h>
|
|
||||||
|
|
||||||
#define PORT 21
|
|
||||||
|
|
||||||
class BackgroundThread : BackgroundThreadWrapper {
|
|
||||||
public:
|
|
||||||
static BackgroundThread *getInstance() {
|
|
||||||
if (instance == nullptr) {
|
|
||||||
instance = new BackgroundThread();
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void destroyInstance() {
|
|
||||||
if (instance != nullptr) {
|
|
||||||
delete instance;
|
|
||||||
instance = nullptr;
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackgroundThread();
|
|
||||||
|
|
||||||
~BackgroundThread() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int32_t getPriority() {
|
|
||||||
return 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL whileLoop() override;
|
|
||||||
|
|
||||||
static BackgroundThread *instance;
|
|
||||||
|
|
||||||
int serverSocket = -1;
|
|
||||||
int network_down = 0;
|
|
||||||
};
|
|
983
src/ftp.c
983
src/ftp.c
@ -1,983 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
ftpii -- an FTP server for the Wii
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
#include "main.h"
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include <coreinit/thread.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/dir.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
//! TODO: fix those function
|
|
||||||
#define gettime() OSGetTick()
|
|
||||||
|
|
||||||
#include "ftp.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include "virtualpath.h"
|
|
||||||
#include "vrt.h"
|
|
||||||
|
|
||||||
#define UNUSED __attribute__((unused))
|
|
||||||
|
|
||||||
#define FTP_BUFFER_SIZE 1024
|
|
||||||
#define MAX_CLIENTS 5
|
|
||||||
|
|
||||||
static const uint16_t SRC_PORT = 20;
|
|
||||||
static const int32_t EQUIT = 696969;
|
|
||||||
static const char *CRLF = "\r\n";
|
|
||||||
static const uint32_t CRLF_LENGTH = 2;
|
|
||||||
|
|
||||||
static uint8_t num_clients = 0;
|
|
||||||
static uint16_t passive_port = 1024;
|
|
||||||
static char *password = NULL;
|
|
||||||
|
|
||||||
#define console_printf(FMT, ARGS...) DEBUG_FUNCTION_LINE_WRITE(FMT, ##ARGS);
|
|
||||||
|
|
||||||
typedef int32_t (*data_connection_callback)(int32_t data_socket, void *arg);
|
|
||||||
|
|
||||||
struct client_struct {
|
|
||||||
int32_t socket;
|
|
||||||
char representation_type;
|
|
||||||
int32_t passive_socket;
|
|
||||||
int32_t data_socket;
|
|
||||||
char cwd[MAXPATHLEN];
|
|
||||||
char pending_rename[MAXPATHLEN];
|
|
||||||
off_t restart_marker;
|
|
||||||
struct sockaddr_in address;
|
|
||||||
bool authenticated;
|
|
||||||
char buf[FTP_BUFFER_SIZE];
|
|
||||||
int32_t offset;
|
|
||||||
bool data_connection_connected;
|
|
||||||
data_connection_callback data_callback;
|
|
||||||
void *data_connection_callback_arg;
|
|
||||||
|
|
||||||
void (*data_connection_cleanup)(void *arg);
|
|
||||||
|
|
||||||
uint64_t data_connection_timer;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct client_struct client_t;
|
|
||||||
|
|
||||||
static client_t *clients[MAX_CLIENTS] = {NULL};
|
|
||||||
|
|
||||||
void set_ftp_password(char *new_password) {
|
|
||||||
if (password) {
|
|
||||||
free(password);
|
|
||||||
password = NULL;
|
|
||||||
}
|
|
||||||
if (new_password) {
|
|
||||||
password = malloc(strlen(new_password) + 1);
|
|
||||||
if (!password)
|
|
||||||
return;
|
|
||||||
|
|
||||||
strcpy((char *) password, new_password);
|
|
||||||
} else {
|
|
||||||
password = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool compare_ftp_password(char *password_attempt) {
|
|
||||||
return !password || !strcmp((char *) password, password_attempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: support multi-line reply
|
|
||||||
*/
|
|
||||||
static int32_t write_reply(client_t *client, uint16_t code, char *msg) {
|
|
||||||
uint32_t msglen = 4 + strlen(msg) + CRLF_LENGTH;
|
|
||||||
char *msgbuf = (char *) malloc(msglen + 1);
|
|
||||||
if (msgbuf == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
if (code == 211) {
|
|
||||||
sprintf(msgbuf, "%u-%s\r\n", code, msg);
|
|
||||||
} else {
|
|
||||||
sprintf(msgbuf, "%u %s\r\n", code, msg);
|
|
||||||
}
|
|
||||||
console_printf("Wrote reply: %s", msgbuf);
|
|
||||||
int32_t ret = send_exact(client->socket, msgbuf, msglen);
|
|
||||||
free(msgbuf);
|
|
||||||
msgbuf = NULL;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void close_passive_socket(client_t *client) {
|
|
||||||
if (client->passive_socket >= 0) {
|
|
||||||
network_close_blocking(client->passive_socket);
|
|
||||||
client->passive_socket = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
result must be able to hold up to maxsplit+1 null-terminated strings of length strlen(s)
|
|
||||||
returns the number of strings stored in the result array (up to maxsplit+1)
|
|
||||||
*/
|
|
||||||
static uint32_t split(char *s, char sep, uint32_t maxsplit, char *result[]) {
|
|
||||||
uint32_t num_results = 0;
|
|
||||||
uint32_t result_pos = 0;
|
|
||||||
uint32_t trim_pos = 0;
|
|
||||||
bool in_word = false;
|
|
||||||
for (; *s; s++) {
|
|
||||||
if (*s == sep) {
|
|
||||||
if (num_results <= maxsplit) {
|
|
||||||
in_word = false;
|
|
||||||
continue;
|
|
||||||
} else if (!trim_pos) {
|
|
||||||
trim_pos = result_pos;
|
|
||||||
}
|
|
||||||
} else if (trim_pos) {
|
|
||||||
trim_pos = 0;
|
|
||||||
}
|
|
||||||
if (!in_word) {
|
|
||||||
in_word = true;
|
|
||||||
if (num_results <= maxsplit) {
|
|
||||||
num_results++;
|
|
||||||
result_pos = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result[num_results - 1][result_pos++] = *s;
|
|
||||||
result[num_results - 1][result_pos] = '\0';
|
|
||||||
}
|
|
||||||
if (trim_pos) {
|
|
||||||
result[num_results - 1][trim_pos] = '\0';
|
|
||||||
}
|
|
||||||
uint32_t i = num_results;
|
|
||||||
for (i = num_results; i <= maxsplit; i++) {
|
|
||||||
result[i][0] = '\0';
|
|
||||||
}
|
|
||||||
return num_results;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_USER(client_t *client, char *username UNUSED) {
|
|
||||||
return write_reply(client, 331, "User name okay, need password.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_PASS(client_t *client, char *password_attempt) {
|
|
||||||
if (compare_ftp_password(password_attempt)) {
|
|
||||||
client->authenticated = true;
|
|
||||||
return write_reply(client, 230, "User logged in, proceed.");
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 530, "Login incorrect.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_REIN(client_t *client, char *rest UNUSED) {
|
|
||||||
close_passive_socket(client);
|
|
||||||
strcpy(client->cwd, "/");
|
|
||||||
client->representation_type = 'A';
|
|
||||||
client->authenticated = false;
|
|
||||||
return write_reply(client, 220, "Service ready for new user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_QUIT(client_t *client, char *rest UNUSED) {
|
|
||||||
// TODO: dont quit if xfer in progress
|
|
||||||
int32_t result = write_reply(client, 221, "Service closing control connection.");
|
|
||||||
return result < 0 ? result : -EQUIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SYST(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 215, "UNIX Type: L8 Version: ftpii");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_TYPE(client_t *client, char *rest) {
|
|
||||||
char representation_type[FTP_BUFFER_SIZE], param[FTP_BUFFER_SIZE];
|
|
||||||
char *args[] = {representation_type, param};
|
|
||||||
uint32_t num_args = split(rest, ' ', 1, args);
|
|
||||||
if (num_args == 0) {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
} else if ((!strcasecmp("A", representation_type) && (!*param || !strcasecmp("N", param))) ||
|
|
||||||
(!strcasecmp("I", representation_type) && num_args == 1)) {
|
|
||||||
client->representation_type = *representation_type;
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
char msg[15];
|
|
||||||
snprintf(msg, sizeof(msg), "Type set to %s.", representation_type);
|
|
||||||
return write_reply(client, 200, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_MODE(client_t *client, char *rest) {
|
|
||||||
if (!strcasecmp("S", rest)) {
|
|
||||||
return write_reply(client, 200, "Mode S ok.");
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_FEAT(client_t *client, char *rest) {
|
|
||||||
return write_reply(client, 211, "Features:\r\n UTF8\r\n211 End");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_OPTS(client_t *client, char *rest) {
|
|
||||||
if (!strcasecmp("UTF8 ON", rest)) {
|
|
||||||
return write_reply(client, 200, "OK");
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 502, "Command not implemented.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_PWD(client_t *client, char *rest UNUSED) {
|
|
||||||
char msg[MAXPATHLEN + 24];
|
|
||||||
// TODO: escape double-quotes
|
|
||||||
sprintf(msg, "\"%s\" is current directory.", client->cwd);
|
|
||||||
return write_reply(client, 257, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_CWD(client_t *client, char *path) {
|
|
||||||
int32_t result;
|
|
||||||
if (!vrt_chdir(client->cwd, path)) {
|
|
||||||
result = write_reply(client, 250, "CWD command successful.");
|
|
||||||
} else {
|
|
||||||
result = write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_CDUP(client_t *client, char *rest UNUSED) {
|
|
||||||
int32_t result;
|
|
||||||
if (!vrt_chdir(client->cwd, "..")) {
|
|
||||||
result = write_reply(client, 250, "CDUP command successful.");
|
|
||||||
} else {
|
|
||||||
result = write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_DELE(client_t *client, char *path) {
|
|
||||||
if (!vrt_unlink(client->cwd, path)) {
|
|
||||||
return write_reply(client, 250, "File or directory removed.");
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_MKD(client_t *client, char *path) {
|
|
||||||
if (!*path) {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
if (!vrt_mkdir(client->cwd, path, 0777)) {
|
|
||||||
char msg[MAXPATHLEN + 21];
|
|
||||||
char abspath[MAXPATHLEN];
|
|
||||||
strcpy(abspath, client->cwd);
|
|
||||||
vrt_chdir(abspath, path); // TODO: error checking
|
|
||||||
// TODO: escape double-quotes
|
|
||||||
sprintf(msg, "\"%s\" directory created.", abspath);
|
|
||||||
return write_reply(client, 257, msg);
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_RNFR(client_t *client, char *path) {
|
|
||||||
strcpy(client->pending_rename, path);
|
|
||||||
return write_reply(client, 350, "Ready for RNTO.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_RNTO(client_t *client, char *path) {
|
|
||||||
if (!*client->pending_rename) {
|
|
||||||
return write_reply(client, 503, "RNFR required first.");
|
|
||||||
}
|
|
||||||
int32_t result;
|
|
||||||
if (!vrt_rename(client->cwd, client->pending_rename, path)) {
|
|
||||||
result = write_reply(client, 250, "Rename successful.");
|
|
||||||
} else {
|
|
||||||
result = write_reply(client, 550, strerror(
|
|
||||||
|
|
||||||
errno));
|
|
||||||
}
|
|
||||||
*client->pending_rename = '\0';
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SIZE(client_t *client, char *path) {
|
|
||||||
struct stat st;
|
|
||||||
if (!vrt_stat(client->cwd, path, &st)) {
|
|
||||||
char size_buf[12];
|
|
||||||
sprintf(size_buf, "%llu", st.st_size);
|
|
||||||
return write_reply(client, 213, size_buf);
|
|
||||||
} else {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_PASV(client_t *client, char *rest UNUSED) {
|
|
||||||
close_passive_socket(client);
|
|
||||||
|
|
||||||
int32_t result;
|
|
||||||
struct sockaddr_in bindAddress;
|
|
||||||
while (passive_port < 5000) {
|
|
||||||
client->passive_socket = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
||||||
if (client->passive_socket < 0) {
|
|
||||||
return write_reply(client, 520, "Unable to create listening socket.");
|
|
||||||
}
|
|
||||||
set_blocking(client->passive_socket, false);
|
|
||||||
|
|
||||||
memset(&bindAddress, 0, sizeof(bindAddress));
|
|
||||||
bindAddress.sin_family = AF_INET;
|
|
||||||
bindAddress.sin_port = htons(passive_port); // XXX: BUG: This will overflow eventually, with interesting results...
|
|
||||||
bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
|
|
||||||
passive_port++;
|
|
||||||
|
|
||||||
if ((result = network_bind(client->passive_socket, (struct sockaddr *) &bindAddress, sizeof(bindAddress))) >= 0) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
close_passive_socket(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (passive_port >= 5000) {
|
|
||||||
passive_port = 1024;
|
|
||||||
}
|
|
||||||
if (result < 0) {
|
|
||||||
close_passive_socket(client);
|
|
||||||
return write_reply(client, 520, "Unable to bind listening socket.");
|
|
||||||
}
|
|
||||||
if ((result = network_listen(client->passive_socket, 1)) < 0) {
|
|
||||||
close_passive_socket(client);
|
|
||||||
return write_reply(client, 520, "Unable to listen on socket.");
|
|
||||||
}
|
|
||||||
char reply[49];
|
|
||||||
uint16_t port = bindAddress.sin_port;
|
|
||||||
uint32_t ip = network_gethostip();
|
|
||||||
struct in_addr addr = {};
|
|
||||||
addr.s_addr = ip;
|
|
||||||
console_printf("Listening for data connections at %s:%u...\n", inet_ntoa(addr), port);
|
|
||||||
sprintf(reply, "Entering Passive Mode (%u,%u,%u,%u,%u,%u).", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff, (port >> 8) & 0xff, port & 0xff);
|
|
||||||
return write_reply(client, 227, reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_PORT(client_t *client, char *portspec) {
|
|
||||||
uint32_t h1, h2, h3, h4, p1, p2;
|
|
||||||
if (sscanf(portspec, "%3u,%3u,%3u,%3u,%3u,%3u", &h1, &h2, &h3, &h4, &p1, &p2) < 6) {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
char addr_str[44];
|
|
||||||
sprintf(addr_str, "%u.%u.%u.%u", h1, h2, h3, h4);
|
|
||||||
struct in_addr sin_addr;
|
|
||||||
if (!inet_aton(addr_str, &sin_addr)) {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
close_passive_socket(client);
|
|
||||||
uint16_t port = ((p1 & 0xff) << 8) | (p2 & 0xff);
|
|
||||||
client->address.sin_addr = sin_addr;
|
|
||||||
client->address.sin_port = htons(port);
|
|
||||||
console_printf("Set client address to %s:%u\n", addr_str, port);
|
|
||||||
return write_reply(client, 200, "PORT command successful.");
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef int32_t (*data_connection_handler)(client_t *client, data_connection_callback callback, void *arg);
|
|
||||||
|
|
||||||
static int32_t prepare_data_connection_active(client_t *client, data_connection_callback callback UNUSED, void *arg UNUSED) {
|
|
||||||
int32_t data_socket = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
||||||
if (data_socket < 0)
|
|
||||||
return data_socket;
|
|
||||||
set_blocking(data_socket, false);
|
|
||||||
struct sockaddr_in bindAddress;
|
|
||||||
memset(&bindAddress, 0, sizeof(bindAddress));
|
|
||||||
bindAddress.sin_family = AF_INET;
|
|
||||||
bindAddress.sin_port = htons(SRC_PORT);
|
|
||||||
bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
int32_t result;
|
|
||||||
if ((result = network_bind(data_socket, (struct sockaddr *) &bindAddress, sizeof(bindAddress))) < 0) {
|
|
||||||
network_close(data_socket);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->data_socket = data_socket;
|
|
||||||
console_printf("Attempting to connect to client at %s:%u\n", inet_ntoa(client->address.sin_addr), client->address.sin_port);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t prepare_data_connection_passive(client_t *client, data_connection_callback callback UNUSED, void *arg UNUSED) {
|
|
||||||
client->data_socket = client->passive_socket;
|
|
||||||
console_printf("Waiting for data connections...\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t prepare_data_connection(client_t *client, void *callback, void *arg, void *cleanup) {
|
|
||||||
int32_t result = write_reply(client, 150, "Transferring data.");
|
|
||||||
if (result >= 0) {
|
|
||||||
data_connection_handler handler = prepare_data_connection_active;
|
|
||||||
if (client->passive_socket >= 0)
|
|
||||||
handler = prepare_data_connection_passive;
|
|
||||||
result = handler(client, (data_connection_callback) callback, arg);
|
|
||||||
if (result < 0) {
|
|
||||||
result = write_reply(client, 520, "Closing data connection, error occurred during transfer.");
|
|
||||||
} else {
|
|
||||||
client->data_connection_connected = false;
|
|
||||||
client->data_callback = callback;
|
|
||||||
client->data_connection_callback_arg = arg;
|
|
||||||
client->data_connection_cleanup = cleanup;
|
|
||||||
client->data_connection_timer = gettime() + OSSecondsToTicks(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t send_nlst(int32_t data_socket, DIR_P *iter) {
|
|
||||||
int32_t result = 0;
|
|
||||||
char filename[MAXPATHLEN];
|
|
||||||
struct dirent *dirent = NULL;
|
|
||||||
while ((dirent = vrt_readdir(iter)) != 0) {
|
|
||||||
size_t end_index = strlen(dirent->d_name);
|
|
||||||
if (end_index + 2 >= MAXPATHLEN)
|
|
||||||
continue;
|
|
||||||
strcpy(filename, dirent->d_name);
|
|
||||||
filename[end_index] = CRLF[0];
|
|
||||||
filename[end_index + 1] = CRLF[1];
|
|
||||||
filename[end_index + 2] = '\0';
|
|
||||||
if ((result = send_exact(data_socket, filename, strlen(filename))) < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result < 0 ? result : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t send_list(int32_t data_socket, DIR_P *iter) {
|
|
||||||
struct stat st;
|
|
||||||
int32_t result = 0;
|
|
||||||
time_t mtime = 0;
|
|
||||||
uint64_t size = 0;
|
|
||||||
char filename[MAXPATHLEN];
|
|
||||||
char line[MAXPATHLEN + 56 + CRLF_LENGTH + 1];
|
|
||||||
struct dirent *dirent = NULL;
|
|
||||||
while ((dirent = vrt_readdir(iter)) != 0) {
|
|
||||||
|
|
||||||
snprintf(filename, sizeof(filename), "%s/%s", iter->path, dirent->d_name);
|
|
||||||
if (stat(filename, &st) == 0) {
|
|
||||||
mtime = st.st_mtime;
|
|
||||||
size = st.st_size;
|
|
||||||
} else {
|
|
||||||
mtime = time(0);
|
|
||||||
size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the date is past 2040-01-01 then use the current date instead.
|
|
||||||
if (mtime > 0x2208985200L) {
|
|
||||||
mtime = time(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
char timestamp[13];
|
|
||||||
strftime(timestamp, sizeof(timestamp), "%b %d %Y", localtime(&mtime));
|
|
||||||
snprintf(line, sizeof(line), "%c%s%s%s%s%s%s%s%s%s 1 0 0 %10llu %s %s\r\n", (dirent->d_type & DT_DIR) ? 'd' : '-',
|
|
||||||
st.st_mode & S_IRUSR ? "r" : "-",
|
|
||||||
st.st_mode & S_IWUSR ? "w" : "-",
|
|
||||||
st.st_mode & S_IXUSR ? "x" : "-",
|
|
||||||
st.st_mode & S_IRGRP ? "r" : "-",
|
|
||||||
st.st_mode & S_IWGRP ? "w" : "-",
|
|
||||||
st.st_mode & S_IXGRP ? "x" : "-",
|
|
||||||
st.st_mode & S_IROTH ? "r" : "-",
|
|
||||||
st.st_mode & S_IWOTH ? "w" : "-",
|
|
||||||
st.st_mode & S_IXOTH ? "x" : "-",
|
|
||||||
size, timestamp, dirent->d_name);
|
|
||||||
if ((result = send_exact(data_socket, line, strlen(line))) < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result < 0 ? result : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_NLST(client_t *client, char *path) {
|
|
||||||
if (!*path) {
|
|
||||||
path = ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
DIR_P *dir = vrt_opendir(client->cwd, path);
|
|
||||||
if (dir == NULL) {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t result = prepare_data_connection(client, send_nlst, dir, vrt_closedir);
|
|
||||||
if (result < 0)
|
|
||||||
vrt_closedir(dir);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_LIST(client_t *client, char *path) {
|
|
||||||
if (*path == '-') {
|
|
||||||
// handle buggy clients that use "LIST -aL" or similar, at the expense of breaking paths that begin with '-'
|
|
||||||
char flags[FTP_BUFFER_SIZE];
|
|
||||||
char rest[FTP_BUFFER_SIZE];
|
|
||||||
char *args[] = {flags, rest};
|
|
||||||
split(path, ' ', 1, args);
|
|
||||||
path = rest;
|
|
||||||
}
|
|
||||||
if (!*path) {
|
|
||||||
path = ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path && client->cwd) {
|
|
||||||
if (strcmp(path, ".") == 0 && strcmp(client->cwd, "/") == 0) {
|
|
||||||
UnmountVirtualPaths();
|
|
||||||
MountVirtualDevices();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DIR_P *dir = vrt_opendir(client->cwd, path);
|
|
||||||
if (dir == NULL) {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t result = prepare_data_connection(client, send_list, dir, vrt_closedir);
|
|
||||||
if (result < 0)
|
|
||||||
vrt_closedir(dir);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_RETR(client_t *client, char *path) {
|
|
||||||
FILE *f = vrt_fopen(client->cwd, path, "rb");
|
|
||||||
if (!f) {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
int fd = fileno(f);
|
|
||||||
if (client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) {
|
|
||||||
int32_t lseek_error = errno;
|
|
||||||
fclose(f);
|
|
||||||
client->restart_marker = 0;
|
|
||||||
return write_reply(client, 550, strerror(lseek_error));
|
|
||||||
}
|
|
||||||
client->restart_marker = 0;
|
|
||||||
|
|
||||||
int32_t result = prepare_data_connection(client, send_from_file, f, fclose);
|
|
||||||
if (result < 0)
|
|
||||||
fclose(f);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t stor_or_append(client_t *client, FILE *f) {
|
|
||||||
if (!f) {
|
|
||||||
return write_reply(client, 550, strerror(errno));
|
|
||||||
}
|
|
||||||
int32_t result = prepare_data_connection(client, recv_to_file, f, fclose);
|
|
||||||
if (result < 0)
|
|
||||||
fclose(f);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_STOR(client_t *client, char *path) {
|
|
||||||
char *openMode = "wb";
|
|
||||||
if (client->restart_marker) {
|
|
||||||
openMode = "r+";
|
|
||||||
}
|
|
||||||
FILE *f = vrt_fopen(client->cwd, path, openMode);
|
|
||||||
int fd;
|
|
||||||
if (f) {
|
|
||||||
fd = fileno(f);
|
|
||||||
}
|
|
||||||
if (f && client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) {
|
|
||||||
int32_t lseek_error = errno;
|
|
||||||
fclose(f);
|
|
||||||
client->restart_marker = 0;
|
|
||||||
return write_reply(client, 550, strerror(lseek_error));
|
|
||||||
}
|
|
||||||
client->restart_marker = 0;
|
|
||||||
|
|
||||||
return stor_or_append(client, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_APPE(client_t *client, char *path) {
|
|
||||||
return stor_or_append(client, vrt_fopen(client->cwd, path, "ab"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_REST(client_t *client, char *offset_str) {
|
|
||||||
off_t offset;
|
|
||||||
if (sscanf(offset_str, "%lli", &offset) < 1 || offset < 0) {
|
|
||||||
return write_reply(client, 501, "Syntax error in parameters.");
|
|
||||||
}
|
|
||||||
client->restart_marker = offset;
|
|
||||||
char msg[FTP_BUFFER_SIZE];
|
|
||||||
sprintf(msg, "Restart position accepted (%lli).", offset);
|
|
||||||
return write_reply(client, 350, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_LOADER(client_t *client, char *rest UNUSED) {
|
|
||||||
int32_t result = write_reply(client, 200, "Exiting to loader.");
|
|
||||||
//set_reset_flag();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_CLEAR(client_t *client, char *rest UNUSED) {
|
|
||||||
int32_t result = write_reply(client, 200, "Cleared.");
|
|
||||||
uint32_t i;
|
|
||||||
for (i = 0; i < 18; i++)
|
|
||||||
console_printf("\n");
|
|
||||||
//console_printf("\x1b[2;0H");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is implemented as a no-op to prevent some FTP clients
|
|
||||||
from displaying skip/abort/retry type prompts.
|
|
||||||
*/
|
|
||||||
static int32_t ftp_SITE_CHMOD(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 250, "SITE CHMOD command ok.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_PASSWD(client_t *client, char *new_password) {
|
|
||||||
set_ftp_password(new_password);
|
|
||||||
return write_reply(client, 200, "Password changed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_NOPASSWD(client_t *client, char *rest UNUSED) {
|
|
||||||
set_ftp_password(NULL);
|
|
||||||
return write_reply(client, 200, "Authentication disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_EJECT(client_t *client, char *rest UNUSED) {
|
|
||||||
//if (dvd_eject()) return write_reply(client, 550, "Unable to eject DVD.");
|
|
||||||
return write_reply(client, 200, "DVD ejected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_MOUNT(client_t *client, char *path UNUSED) {
|
|
||||||
//if (!mount_virtual(path)) return write_reply(client, 550, "Unable to mount.");
|
|
||||||
return write_reply(client, 250, "Mounted.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_UNMOUNT(client_t *client, char *path UNUSED) {
|
|
||||||
//if (!unmount_virtual(path)) return write_reply(client, 550, "Unable to unmount.");
|
|
||||||
return write_reply(client, 250, "Unmounted.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_UNKNOWN(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 501, "Unknown SITE command.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SITE_LOAD(client_t *client, char *path UNUSED) {
|
|
||||||
// FILE *f = vrt_fopen(client->cwd, path, "rb");
|
|
||||||
// if (!f) return write_reply(client, 550, strerror(errno));
|
|
||||||
// char *real_path = to_real_path(client->cwd, path);
|
|
||||||
// if (!real_path) goto end;
|
|
||||||
// load_from_file(f, real_path);
|
|
||||||
// free(real_path);
|
|
||||||
// end:
|
|
||||||
// fclose(f);
|
|
||||||
return write_reply(client, 500, "Unable to load.");
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef int32_t (*ftp_command_handler)(client_t *client, char *args);
|
|
||||||
|
|
||||||
static int32_t dispatch_to_handler(client_t *client, char *cmd_line, const char **commands, const ftp_command_handler *handlers) {
|
|
||||||
char cmd[FTP_BUFFER_SIZE], rest[FTP_BUFFER_SIZE];
|
|
||||||
char *args[] = {cmd, rest};
|
|
||||||
split(cmd_line, ' ', 1, args);
|
|
||||||
int32_t i;
|
|
||||||
for (i = 0; commands[i]; i++) {
|
|
||||||
if (!strcasecmp(commands[i], cmd))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return handlers[i](client, rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *site_commands[] = {"LOADER", "CLEAR", "CHMOD", "PASSWD", "NOPASSWD", "EJECT", "MOUNT", "UNMOUNT", "LOAD", NULL};
|
|
||||||
static const ftp_command_handler site_handlers[] = {ftp_SITE_LOADER, ftp_SITE_CLEAR, ftp_SITE_CHMOD, ftp_SITE_PASSWD, ftp_SITE_NOPASSWD, ftp_SITE_EJECT, ftp_SITE_MOUNT, ftp_SITE_UNMOUNT,
|
|
||||||
ftp_SITE_LOAD, ftp_SITE_UNKNOWN};
|
|
||||||
|
|
||||||
static int32_t ftp_SITE(client_t *client, char *cmd_line) {
|
|
||||||
return dispatch_to_handler(client, cmd_line, site_commands, site_handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_NOOP(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 200, "NOOP command successful.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_SUPERFLUOUS(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 202, "Command not implemented, superfluous at this site.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_NEEDAUTH(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 530, "Please login with USER and PASS.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t ftp_UNKNOWN(client_t *client, char *rest UNUSED) {
|
|
||||||
return write_reply(client, 502, "Command not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *unauthenticated_commands[] = {"USER", "PASS", "QUIT", "REIN", "FEAT", "OPTS", "NOOP", NULL};
|
|
||||||
static const ftp_command_handler unauthenticated_handlers[] = {ftp_USER, ftp_PASS, ftp_QUIT, ftp_REIN, ftp_FEAT, ftp_OPTS, ftp_NOOP, ftp_NEEDAUTH};
|
|
||||||
|
|
||||||
static const char *authenticated_commands[] = {
|
|
||||||
"USER", "PASS", "LIST", "PWD", "CWD", "CDUP",
|
|
||||||
"SIZE", "PASV", "PORT", "TYPE", "SYST", "MODE",
|
|
||||||
"RETR", "STOR", "APPE", "REST", "DELE", "MKD",
|
|
||||||
"RMD", "RNFR", "RNTO", "NLST", "QUIT", "REIN",
|
|
||||||
"SITE", "FEAT", "OPTS", "NOOP", "ALLO", NULL};
|
|
||||||
static const ftp_command_handler authenticated_handlers[] = {
|
|
||||||
ftp_USER, ftp_PASS, ftp_LIST, ftp_PWD, ftp_CWD, ftp_CDUP,
|
|
||||||
ftp_SIZE, ftp_PASV, ftp_PORT, ftp_TYPE, ftp_SYST, ftp_MODE,
|
|
||||||
ftp_RETR, ftp_STOR, ftp_APPE, ftp_REST, ftp_DELE, ftp_MKD,
|
|
||||||
ftp_DELE, ftp_RNFR, ftp_RNTO, ftp_NLST, ftp_QUIT, ftp_REIN,
|
|
||||||
ftp_SITE, ftp_FEAT, ftp_OPTS, ftp_NOOP, ftp_SUPERFLUOUS,
|
|
||||||
ftp_UNKNOWN};
|
|
||||||
|
|
||||||
/*
|
|
||||||
returns negative to signal an error that requires closing the connection
|
|
||||||
*/
|
|
||||||
static int32_t process_command(client_t *client, char *cmd_line) {
|
|
||||||
if (strlen(cmd_line) == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
console_printf("Got command: %s\n", cmd_line);
|
|
||||||
|
|
||||||
const char **commands = unauthenticated_commands;
|
|
||||||
const ftp_command_handler *handlers = unauthenticated_handlers;
|
|
||||||
|
|
||||||
if (client->authenticated) {
|
|
||||||
commands = authenticated_commands;
|
|
||||||
handlers = authenticated_handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatch_to_handler(client, cmd_line, commands, handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanup_data_resources(client_t *client) {
|
|
||||||
if (client->data_socket >= 0 && client->data_socket != client->passive_socket) {
|
|
||||||
network_close_blocking(client->data_socket);
|
|
||||||
}
|
|
||||||
client->data_socket = -1;
|
|
||||||
client->data_connection_connected = false;
|
|
||||||
client->data_callback = NULL;
|
|
||||||
if (client->data_connection_cleanup) {
|
|
||||||
client->data_connection_cleanup(client->data_connection_callback_arg);
|
|
||||||
}
|
|
||||||
client->data_connection_callback_arg = NULL;
|
|
||||||
client->data_connection_cleanup = NULL;
|
|
||||||
client->data_connection_timer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanup_client(client_t *client) {
|
|
||||||
network_close_blocking(client->socket);
|
|
||||||
cleanup_data_resources(client);
|
|
||||||
close_passive_socket(client);
|
|
||||||
int client_index;
|
|
||||||
for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
|
|
||||||
if (clients[client_index] == client) {
|
|
||||||
clients[client_index] = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(client);
|
|
||||||
client = NULL;
|
|
||||||
num_clients--;
|
|
||||||
console_printf("Client disconnected.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup_ftp() {
|
|
||||||
int client_index;
|
|
||||||
for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
|
|
||||||
client_t *client = clients[client_index];
|
|
||||||
if (client) {
|
|
||||||
write_reply(client, 421, "Service not available, closing control connection.");
|
|
||||||
cleanup_client(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool process_accept_events(int32_t server) {
|
|
||||||
int32_t peer;
|
|
||||||
struct sockaddr_in client_address;
|
|
||||||
socklen_t addrlen = sizeof(client_address);
|
|
||||||
while ((peer = network_accept(server, (struct sockaddr *) &client_address, &addrlen)) != -EAGAIN) {
|
|
||||||
if (peer < 0) {
|
|
||||||
console_printf("Error accepting connection: [%i] %s\n", -peer, strerror(-peer));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
console_printf("Accepted connection from %s!\n", inet_ntoa(client_address.sin_addr));
|
|
||||||
|
|
||||||
if (num_clients == MAX_CLIENTS) {
|
|
||||||
console_printf("Maximum of %u clients reached, not accepting client.\n", MAX_CLIENTS);
|
|
||||||
network_close(peer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_t *client = malloc(sizeof(client_t));
|
|
||||||
if (!client) {
|
|
||||||
console_printf("Could not allocate memory for client state, not accepting client.\n");
|
|
||||||
network_close(peer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
client->socket = peer;
|
|
||||||
client->representation_type = 'A';
|
|
||||||
client->passive_socket = -1;
|
|
||||||
client->data_socket = -1;
|
|
||||||
strcpy(client->cwd, "/");
|
|
||||||
*client->pending_rename = '\0';
|
|
||||||
client->restart_marker = 0;
|
|
||||||
client->authenticated = false;
|
|
||||||
client->offset = 0;
|
|
||||||
client->data_connection_connected = false;
|
|
||||||
client->data_callback = NULL;
|
|
||||||
client->data_connection_callback_arg = NULL;
|
|
||||||
client->data_connection_cleanup = NULL;
|
|
||||||
client->data_connection_timer = 0;
|
|
||||||
memcpy(&client->address, &client_address, sizeof(client_address));
|
|
||||||
int client_index;
|
|
||||||
if (write_reply(client, 220, "ftpii") < 0) {
|
|
||||||
console_printf("Error writing greeting.\n");
|
|
||||||
network_close_blocking(peer);
|
|
||||||
free(client);
|
|
||||||
client = NULL;
|
|
||||||
} else {
|
|
||||||
for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
|
|
||||||
if (!clients[client_index]) {
|
|
||||||
clients[client_index] = client;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
num_clients++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_data_events(client_t *client) {
|
|
||||||
int32_t result;
|
|
||||||
if (!client->data_connection_connected) {
|
|
||||||
if (client->passive_socket >= 0) {
|
|
||||||
struct sockaddr_in data_peer_address;
|
|
||||||
socklen_t addrlen = sizeof(data_peer_address);
|
|
||||||
result = network_accept(client->passive_socket, (struct sockaddr *) &data_peer_address, &addrlen);
|
|
||||||
if (result >= 0) {
|
|
||||||
client->data_socket = result;
|
|
||||||
client->data_connection_connected = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((result = network_connect(client->data_socket, (struct sockaddr *) &client->address, sizeof(client->address))) < 0) {
|
|
||||||
if (result == -EINPROGRESS || result == -EALREADY)
|
|
||||||
result = -EAGAIN;
|
|
||||||
if ((result != -EAGAIN) && (result != -EISCONN)) {
|
|
||||||
console_printf("Unable to connect to client: [%i] %s\n", -result, strerror(-result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result >= 0 || result == -EISCONN) {
|
|
||||||
client->data_connection_connected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client->data_connection_connected) {
|
|
||||||
result = 1;
|
|
||||||
console_printf("Connected to client! Transferring data...\n");
|
|
||||||
} else if (gettime() > client->data_connection_timer) {
|
|
||||||
result = -2;
|
|
||||||
console_printf("Timed out waiting for data connection.\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = client->data_callback(client->data_socket, client->data_connection_callback_arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result <= 0 && result != -EAGAIN) {
|
|
||||||
cleanup_data_resources(client);
|
|
||||||
if (result < 0) {
|
|
||||||
result = write_reply(client, 520, "Closing data connection, error occurred during transfer.");
|
|
||||||
} else {
|
|
||||||
result = write_reply(client, 226, "Closing data connection, transfer successful.");
|
|
||||||
}
|
|
||||||
if (result < 0) {
|
|
||||||
cleanup_client(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_control_events(client_t *client) {
|
|
||||||
int32_t bytes_read;
|
|
||||||
while (client->offset < (FTP_BUFFER_SIZE - 1)) {
|
|
||||||
if (client->data_callback) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char *offset_buf = client->buf + client->offset;
|
|
||||||
if ((bytes_read = network_read(client->socket, offset_buf, FTP_BUFFER_SIZE - 1 - client->offset)) < 0) {
|
|
||||||
if (bytes_read != -EAGAIN) {
|
|
||||||
console_printf("Read error %i occurred, closing client.\n", bytes_read);
|
|
||||||
goto recv_loop_end;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if (bytes_read == 0) {
|
|
||||||
goto recv_loop_end; // EOF from client
|
|
||||||
}
|
|
||||||
client->offset += bytes_read;
|
|
||||||
client->buf[client->offset] = '\0';
|
|
||||||
|
|
||||||
if (strchr(offset_buf, '\0') != (client->buf + client->offset)) {
|
|
||||||
console_printf("Received a null byte from client, closing connection ;-)\n"); // i have decided this isn't allowed =P
|
|
||||||
goto recv_loop_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *next;
|
|
||||||
char *end;
|
|
||||||
for (next = client->buf; (end = strstr(next, CRLF)) && !client->data_callback; next = end + CRLF_LENGTH) {
|
|
||||||
*end = '\0';
|
|
||||||
if (strchr(next, '\n')) {
|
|
||||||
console_printf("Received a line-feed from client without preceding carriage return, closing connection ;-)\n"); // i have decided this isn't allowed =P
|
|
||||||
goto recv_loop_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*next) {
|
|
||||||
int32_t result;
|
|
||||||
if ((result = process_command(client, next)) < 0) {
|
|
||||||
if (result != -EQUIT) {
|
|
||||||
console_printf("Closing connection due to error while processing command: %s\n", next);
|
|
||||||
}
|
|
||||||
goto recv_loop_end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next != client->buf) { // some lines were processed
|
|
||||||
client->offset = strlen(next);
|
|
||||||
char tmp_buf[client->offset];
|
|
||||||
memcpy(tmp_buf, next, client->offset);
|
|
||||||
memcpy(client->buf, tmp_buf, client->offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console_printf("Received line longer than %u bytes, closing client.\n", FTP_BUFFER_SIZE - 1);
|
|
||||||
|
|
||||||
recv_loop_end:
|
|
||||||
cleanup_client(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool process_ftp_events(int32_t server) {
|
|
||||||
bool network_down = !process_accept_events(server);
|
|
||||||
int client_index;
|
|
||||||
bool hasActiveClients = false;
|
|
||||||
for (client_index = 0; client_index < MAX_CLIENTS; client_index++) {
|
|
||||||
client_t *client = clients[client_index];
|
|
||||||
if (client) {
|
|
||||||
hasActiveClients = true;
|
|
||||||
if (client->data_callback) {
|
|
||||||
process_data_events(client);
|
|
||||||
} else {
|
|
||||||
process_control_events(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasActiveClients) {
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(100));
|
|
||||||
} else {
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(1));
|
|
||||||
}
|
|
||||||
return network_down;
|
|
||||||
}
|
|
47
src/ftp.h
47
src/ftp.h
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
ftpii -- an FTP server for the Wii
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
#ifndef _FTP_H_
|
|
||||||
#define _FTP_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
void accept_ftp_client(int32_t server);
|
|
||||||
|
|
||||||
void set_ftp_password(char *new_password);
|
|
||||||
|
|
||||||
bool process_ftp_events(int32_t server);
|
|
||||||
|
|
||||||
void cleanup_ftp();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _FTP_H_ */
|
|
230
src/main.cpp
230
src/main.cpp
@ -1,230 +0,0 @@
|
|||||||
#include "main.h"
|
|
||||||
#include "BackgroundThread.hpp"
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include "virtualpath.h"
|
|
||||||
#include <coreinit/cache.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <mocha/mocha.h>
|
|
||||||
#include <nn/ac.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <wups.h>
|
|
||||||
#include <wups/config/WUPSConfigItemBoolean.h>
|
|
||||||
#include <wups/config/WUPSConfigItemStub.h>
|
|
||||||
|
|
||||||
WUPS_PLUGIN_NAME("FTPiiU");
|
|
||||||
WUPS_PLUGIN_DESCRIPTION("FTP Server");
|
|
||||||
WUPS_PLUGIN_VERSION(VERSION_FULL);
|
|
||||||
WUPS_PLUGIN_AUTHOR("Maschell");
|
|
||||||
WUPS_PLUGIN_LICENSE("GPL");
|
|
||||||
|
|
||||||
WUPS_USE_WUT_DEVOPTAB();
|
|
||||||
WUPS_USE_STORAGE("ftpiiu"); // Unqiue id for the storage api
|
|
||||||
|
|
||||||
uint32_t hostIpAddress = 0;
|
|
||||||
|
|
||||||
BackgroundThread *thread = nullptr;
|
|
||||||
|
|
||||||
#define FTPIIU_ENABLED_STRING "enabled"
|
|
||||||
#define SYSTEM_FILES_ALLOWED_STRING "systemFilesAllowed"
|
|
||||||
|
|
||||||
bool gFTPServerEnabled = true;
|
|
||||||
bool gSystemFilesAllowed = false;
|
|
||||||
bool gMochaPathsWereMounted = false;
|
|
||||||
|
|
||||||
MochaUtilsStatus MountWrapper(const char *mount, const char *dev, const char *mountTo) {
|
|
||||||
auto res = Mocha_MountFS(mount, dev, mountTo);
|
|
||||||
if (res == MOCHA_RESULT_ALREADY_EXISTS) {
|
|
||||||
res = Mocha_MountFS(mount, nullptr, mountTo);
|
|
||||||
}
|
|
||||||
if (res == MOCHA_RESULT_SUCCESS) {
|
|
||||||
std::string mountPath = std::string(mount) + ":/";
|
|
||||||
VirtualMountDevice(mountPath.c_str());
|
|
||||||
DEBUG_FUNCTION_LINE_VERBOSE("Mounted %s", mountPath.c_str());
|
|
||||||
} else {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to mount %s: %s [%d]", mount, Mocha_GetStatusStr(res), res);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
void startServer();
|
|
||||||
void stopServer();
|
|
||||||
|
|
||||||
/* Entry point */
|
|
||||||
ON_APPLICATION_START() {
|
|
||||||
nn::ac::Initialize();
|
|
||||||
nn::ac::ConnectAsync();
|
|
||||||
hostIpAddress = 0;
|
|
||||||
nn::ac::GetAssignedAddress(&hostIpAddress);
|
|
||||||
initLogging();
|
|
||||||
|
|
||||||
if (gFTPServerEnabled) {
|
|
||||||
startServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INITIALIZE_PLUGIN() {
|
|
||||||
// Open storage to read values
|
|
||||||
WUPSStorageError storageRes = WUPS_OpenStorage();
|
|
||||||
if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
|
|
||||||
} else {
|
|
||||||
if ((storageRes = WUPS_GetBool(nullptr, FTPIIU_ENABLED_STRING, &gFTPServerEnabled)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
|
|
||||||
// Add the value to the storage if it's missing.
|
|
||||||
if (WUPS_StoreBool(nullptr, FTPIIU_ENABLED_STRING, gFTPServerEnabled) != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to store bool");
|
|
||||||
}
|
|
||||||
} else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
|
|
||||||
}
|
|
||||||
if ((storageRes = WUPS_GetBool(nullptr, SYSTEM_FILES_ALLOWED_STRING, &gSystemFilesAllowed)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
|
|
||||||
// Add the value to the storage if it's missing.
|
|
||||||
if (WUPS_StoreBool(nullptr, SYSTEM_FILES_ALLOWED_STRING, gSystemFilesAllowed) != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to store bool");
|
|
||||||
}
|
|
||||||
} else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close storage
|
|
||||||
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to close storage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thread = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void startServer() {
|
|
||||||
if (!thread) {
|
|
||||||
//!*******************************************************************
|
|
||||||
//! Initialize FS *
|
|
||||||
//!*******************************************************************
|
|
||||||
if (gSystemFilesAllowed) {
|
|
||||||
MochaUtilsStatus res;
|
|
||||||
if ((res = Mocha_InitLibrary()) == MOCHA_RESULT_SUCCESS) {
|
|
||||||
MountWrapper("slccmpt01", "/dev/slccmpt01", "/vol/storage_slccmpt01");
|
|
||||||
MountWrapper("storage_odd_tickets", nullptr, "/vol/storage_odd01");
|
|
||||||
MountWrapper("storage_odd_updates", nullptr, "/vol/storage_odd02");
|
|
||||||
MountWrapper("storage_odd_content", nullptr, "/vol/storage_odd03");
|
|
||||||
MountWrapper("storage_odd_content2", nullptr, "/vol/storage_odd04");
|
|
||||||
MountWrapper("storage_slc", "/dev/slc01", "/vol/storage_slc01");
|
|
||||||
Mocha_MountFS("storage_mlc", nullptr, "/vol/storage_mlc01");
|
|
||||||
Mocha_MountFS("storage_usb", nullptr, "/vol/storage_usb01");
|
|
||||||
gMochaPathsWereMounted = true;
|
|
||||||
} else {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to init libmocha: %s [%d]", Mocha_GetStatusStr(res), res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MountVirtualDevices();
|
|
||||||
|
|
||||||
thread = BackgroundThread::getInstance();
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopServer() {
|
|
||||||
BackgroundThread::destroyInstance();
|
|
||||||
if (gMochaPathsWereMounted) {
|
|
||||||
Mocha_UnmountFS("slccmpt01");
|
|
||||||
Mocha_UnmountFS("storage_odd_tickets");
|
|
||||||
Mocha_UnmountFS("storage_odd_updates");
|
|
||||||
Mocha_UnmountFS("storage_odd_content");
|
|
||||||
Mocha_UnmountFS("storage_odd_content2");
|
|
||||||
Mocha_UnmountFS("storage_slc");
|
|
||||||
Mocha_UnmountFS("storage_mlc");
|
|
||||||
Mocha_UnmountFS("storage_usb");
|
|
||||||
gMochaPathsWereMounted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_FUNCTION_LINE("Unmount virtual paths");
|
|
||||||
UnmountVirtualPaths();
|
|
||||||
|
|
||||||
thread = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void gFTPServerRunningChanged(ConfigItemBoolean *item, bool newValue) {
|
|
||||||
DEBUG_FUNCTION_LINE("New value in gFTPServerEnabled: %d", newValue);
|
|
||||||
gFTPServerEnabled = newValue;
|
|
||||||
if (!gFTPServerEnabled) {
|
|
||||||
stopServer();
|
|
||||||
} else {
|
|
||||||
startServer();
|
|
||||||
}
|
|
||||||
// If the value has changed, we store it in the storage.
|
|
||||||
auto res = WUPS_StoreInt(nullptr, FTPIIU_ENABLED_STRING, gFTPServerEnabled);
|
|
||||||
if (res != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to store gFTPServerEnabled: %s (%d)", WUPS_GetStorageStatusStr(res), res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void gSystemFilesAllowedChanged(ConfigItemBoolean *item, bool newValue) {
|
|
||||||
DEBUG_FUNCTION_LINE("New value in gFTPServerEnabled: %d", newValue);
|
|
||||||
if (thread != nullptr) { // If the server is already running we need to restart it.
|
|
||||||
stopServer();
|
|
||||||
gSystemFilesAllowed = newValue;
|
|
||||||
startServer();
|
|
||||||
} else {
|
|
||||||
gSystemFilesAllowed = newValue;
|
|
||||||
}
|
|
||||||
// If the value has changed, we store it in the storage.
|
|
||||||
auto res = WUPS_StoreInt(nullptr, SYSTEM_FILES_ALLOWED_STRING, gSystemFilesAllowed);
|
|
||||||
if (res != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to store gSystemFilesAllowed: %s (%d)", WUPS_GetStorageStatusStr(res), res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WUPS_GET_CONFIG() {
|
|
||||||
// We open the storage, so we can persist the configuration the user did.
|
|
||||||
if (WUPS_OpenStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to open storage");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
nn::ac::GetAssignedAddress(&hostIpAddress);
|
|
||||||
|
|
||||||
WUPSConfigHandle config;
|
|
||||||
WUPSConfig_CreateHandled(&config, "FTPiiU");
|
|
||||||
|
|
||||||
WUPSConfigCategoryHandle setting;
|
|
||||||
WUPSConfig_AddCategoryByNameHandled(config, "Settings", &setting);
|
|
||||||
|
|
||||||
WUPSConfigItemBoolean_AddToCategoryHandled(config, setting, FTPIIU_ENABLED_STRING, "Enable FTPiiU", gFTPServerEnabled, &gFTPServerRunningChanged);
|
|
||||||
WUPSConfigItemBoolean_AddToCategoryHandled(config, setting, SYSTEM_FILES_ALLOWED_STRING, "Allow access to system files", gSystemFilesAllowed, &gSystemFilesAllowedChanged);
|
|
||||||
|
|
||||||
WUPSConfigCategoryHandle info;
|
|
||||||
WUPSConfig_AddCategoryByNameHandled(config, "==========", &info);
|
|
||||||
WUPSConfigItemStub_AddToCategoryHandled(config, info, "info", "Press B to go Back");
|
|
||||||
|
|
||||||
WUPSConfigCategoryHandle info1;
|
|
||||||
char ipSettings[50];
|
|
||||||
if (hostIpAddress != 0) {
|
|
||||||
snprintf(ipSettings, 50, "IP of your console is %u.%u.%u.%u. Port %i",
|
|
||||||
(hostIpAddress >> 24) & 0xFF,
|
|
||||||
(hostIpAddress >> 16) & 0xFF,
|
|
||||||
(hostIpAddress >> 8) & 0xFF,
|
|
||||||
(hostIpAddress >> 0) & 0xFF,
|
|
||||||
PORT);
|
|
||||||
} else {
|
|
||||||
snprintf(ipSettings, 50, "The console is not connected to a network.");
|
|
||||||
}
|
|
||||||
WUPSConfig_AddCategoryByNameHandled(config, ipSettings, &info1);
|
|
||||||
WUPSConfigItemStub_AddToCategoryHandled(config, info1, "info1", "Press B to go Back");
|
|
||||||
|
|
||||||
WUPSConfigCategoryHandle info2;
|
|
||||||
char portSettings[50];
|
|
||||||
snprintf(portSettings, 50, "You can connect with empty credentials");
|
|
||||||
WUPSConfig_AddCategoryByNameHandled(config, portSettings, &info2);
|
|
||||||
WUPSConfigItemStub_AddToCategoryHandled(config, info2, "info2", "Press B to go Back");
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
WUPS_CONFIG_CLOSED() {
|
|
||||||
// Save all changes
|
|
||||||
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to close storage");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ON_APPLICATION_ENDS() {
|
|
||||||
stopServer();
|
|
||||||
deinitLogging();
|
|
||||||
}
|
|
29
src/main.h
29
src/main.h
@ -1,29 +0,0 @@
|
|||||||
#ifndef _MAIN_H_
|
|
||||||
#define _MAIN_H_
|
|
||||||
|
|
||||||
/* Main */
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "net.h"
|
|
||||||
#include "version.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define MAXPATHLEN 256
|
|
||||||
|
|
||||||
#define VERSION "v0.3.1"
|
|
||||||
#define VERSION_FULL VERSION VERSION_EXTRA
|
|
||||||
|
|
||||||
#define wiiu_geterrno() (errno)
|
|
||||||
|
|
||||||
extern bool gSystemFilesAllowed;
|
|
||||||
|
|
||||||
//! C wrapper for our C++ functions
|
|
||||||
int Menu_Main(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
323
src/net.c
323
src/net.c
@ -1,323 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
#include "main.h"
|
|
||||||
#include <coreinit/thread.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
|
||||||
|
|
||||||
#include "net.h"
|
|
||||||
|
|
||||||
#define DEFAULT_NET_BUFFER_SIZE (128 * 1024)
|
|
||||||
#define IO_BUFFER_SIZE (512 * 1024)
|
|
||||||
#define MIN_NET_BUFFER_SIZE (4 * 1024)
|
|
||||||
|
|
||||||
extern uint32_t hostIpAddress;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
void initialise_network() {
|
|
||||||
printf("Waiting for network to initialise...\n");
|
|
||||||
int32_t result = -1;
|
|
||||||
while (!check_reset_synchronous() && result < 0) {
|
|
||||||
net_deinit();
|
|
||||||
while (!check_reset_synchronous() && (result = net_init()) == -EAGAIN);
|
|
||||||
if (result < 0)
|
|
||||||
printf("net_init() failed: [%i] %s, retrying...\n", result, strerror(-result));
|
|
||||||
}
|
|
||||||
if (result >= 0) {
|
|
||||||
uint32_t ip = 0;
|
|
||||||
do {
|
|
||||||
ip = net_gethostip();
|
|
||||||
if (!ip)
|
|
||||||
printf("net_gethostip() failed, retrying...\n");
|
|
||||||
} while (!check_reset_synchronous() && !ip);
|
|
||||||
if (ip) {
|
|
||||||
struct in_addr addr;
|
|
||||||
addr.s_addr = ip;
|
|
||||||
printf("Network initialised. Wii IP address: %s\n", inet_ntoa(addr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int32_t network_socket(int32_t domain, int32_t type, int32_t protocol) {
|
|
||||||
int sock = socket(domain, type, protocol);
|
|
||||||
if (sock < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : sock;
|
|
||||||
}
|
|
||||||
if (type == SOCK_STREAM) {
|
|
||||||
int enable = 1;
|
|
||||||
// Activate WinScale
|
|
||||||
setsockopt(sock, SOL_SOCKET, SO_WINSCALE, &enable, sizeof(enable));
|
|
||||||
}
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_bind(int32_t s, struct sockaddr *name, int32_t namelen) {
|
|
||||||
int res = bind(s, name, namelen);
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_listen(int32_t s, uint32_t backlog) {
|
|
||||||
int res = listen(s, backlog);
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_accept(int32_t s, struct sockaddr *addr, socklen_t *addrlen) {
|
|
||||||
int res = accept(s, addr, addrlen);
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_connect(int32_t s, struct sockaddr *addr, int32_t addrlen) {
|
|
||||||
int res = connect(s, addr, addrlen);
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_read(int32_t s, void *mem, int32_t len) {
|
|
||||||
int res = recv(s, mem, len, 0);
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t network_gethostip() {
|
|
||||||
return hostIpAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_write(int32_t s, const void *mem, int32_t len) {
|
|
||||||
int32_t transfered = 0;
|
|
||||||
|
|
||||||
while (len) {
|
|
||||||
int ret = send(s, mem, len, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
transfered = (err < 0) ? err : ret;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mem += ret;
|
|
||||||
transfered += ret;
|
|
||||||
len -= ret;
|
|
||||||
}
|
|
||||||
return transfered;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_close(int32_t s) {
|
|
||||||
if (s < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
shutdown(s, SHUT_RDWR);
|
|
||||||
int res = close(s);
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
int err = -wiiu_geterrno();
|
|
||||||
return (err < 0) ? err : res;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t set_blocking(int32_t s, bool blocking) {
|
|
||||||
int32_t block = !blocking;
|
|
||||||
setsockopt(s, SOL_SOCKET, SO_NONBLOCK, &block, sizeof(block));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t network_close_blocking(int32_t s) {
|
|
||||||
set_blocking(s, true);
|
|
||||||
return network_close(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t create_server(uint16_t port) {
|
|
||||||
int32_t server = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
|
||||||
if (server < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_blocking(server, false);
|
|
||||||
uint32_t enable = 1;
|
|
||||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
|
||||||
|
|
||||||
struct sockaddr_in bindAddress;
|
|
||||||
memset(&bindAddress, 0, sizeof(bindAddress));
|
|
||||||
bindAddress.sin_family = AF_INET;
|
|
||||||
bindAddress.sin_port = htons(port);
|
|
||||||
bindAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
|
|
||||||
int32_t ret;
|
|
||||||
if ((ret = network_bind(server, (struct sockaddr *) &bindAddress, sizeof(bindAddress))) < 0) {
|
|
||||||
network_close(server);
|
|
||||||
//gxprintf("Error binding socket: [%i] %s\n", -ret, strerror(-ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
if ((ret = network_listen(server, 3)) < 0) {
|
|
||||||
network_close(server);
|
|
||||||
//gxprintf("Error listening on socket: [%i] %s\n", -ret, strerror(-ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef int32_t (*transferrer_type)(int32_t s, void *mem, int32_t len);
|
|
||||||
|
|
||||||
static int32_t transfer_exact(int32_t s, char *buf, int32_t length, transferrer_type transferrer) {
|
|
||||||
int32_t result = 0;
|
|
||||||
int32_t remaining = length;
|
|
||||||
int32_t bytes_transferred;
|
|
||||||
set_blocking(s, true);
|
|
||||||
uint32_t curNetBufferSize = DEFAULT_NET_BUFFER_SIZE;
|
|
||||||
|
|
||||||
while (remaining) {
|
|
||||||
try_again_with_smaller_buffer:
|
|
||||||
bytes_transferred = transferrer(s, buf, MIN(remaining, (int) DEFAULT_NET_BUFFER_SIZE));
|
|
||||||
if (bytes_transferred > 0) {
|
|
||||||
remaining -= bytes_transferred;
|
|
||||||
buf += bytes_transferred;
|
|
||||||
} else if (bytes_transferred < 0) {
|
|
||||||
if (bytes_transferred == -EINVAL && curNetBufferSize == DEFAULT_NET_BUFFER_SIZE) {
|
|
||||||
curNetBufferSize = MIN_NET_BUFFER_SIZE;
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(1));
|
|
||||||
goto try_again_with_smaller_buffer;
|
|
||||||
}
|
|
||||||
if (bytes_transferred == -EAGAIN) {
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result = bytes_transferred;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
result = -ENODATA;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set_blocking(s, false);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t send_exact(int32_t s, char *buf, int32_t length) {
|
|
||||||
return transfer_exact(s, buf, length, (transferrer_type) network_write);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t send_from_file(int32_t s, FILE *f) {
|
|
||||||
char *buf = (char *) memalign(0x40, IO_BUFFER_SIZE);
|
|
||||||
if (!buf) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t bytes_read;
|
|
||||||
int32_t result = 0;
|
|
||||||
|
|
||||||
int bufSize = DEFAULT_NET_BUFFER_SIZE;
|
|
||||||
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &bufSize, sizeof(bufSize));
|
|
||||||
|
|
||||||
bytes_read = fread(buf, 1, IO_BUFFER_SIZE, f);
|
|
||||||
if (bytes_read > 0) {
|
|
||||||
result = send_exact(s, buf, bytes_read);
|
|
||||||
if (result < 0)
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
if (bytes_read < IO_BUFFER_SIZE) {
|
|
||||||
result = -!feof(f);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
return -EAGAIN;
|
|
||||||
end:
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t recv_to_file(int32_t s, FILE *f) {
|
|
||||||
char *buf = (char *) memalign(0x40, DEFAULT_NET_BUFFER_SIZE);
|
|
||||||
if (!buf) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not perfect because it's not aligned, but with the way fclose is called
|
|
||||||
// using a custom buffer is annoying to clean up properly
|
|
||||||
setvbuf(f, NULL, _IOFBF, IO_BUFFER_SIZE);
|
|
||||||
|
|
||||||
int bufSize = DEFAULT_NET_BUFFER_SIZE;
|
|
||||||
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &bufSize, sizeof(bufSize));
|
|
||||||
|
|
||||||
uint32_t curNetBufferSize = DEFAULT_NET_BUFFER_SIZE;
|
|
||||||
|
|
||||||
int32_t bytes_read;
|
|
||||||
while (1) {
|
|
||||||
try_again_with_smaller_buffer:
|
|
||||||
bytes_read = network_read(s, buf, curNetBufferSize);
|
|
||||||
if (bytes_read < 0) {
|
|
||||||
if (bytes_read == -EINVAL && curNetBufferSize == DEFAULT_NET_BUFFER_SIZE) {
|
|
||||||
curNetBufferSize = MIN_NET_BUFFER_SIZE;
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(1));
|
|
||||||
goto try_again_with_smaller_buffer;
|
|
||||||
}
|
|
||||||
if (bytes_read == -EAGAIN) {
|
|
||||||
OSSleepTicks(OSMillisecondsToTicks(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
return bytes_read;
|
|
||||||
} else if (bytes_read == 0) {
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t bytes_written = fwrite(buf, 1, bytes_read, f);
|
|
||||||
if (bytes_written < bytes_read) {
|
|
||||||
free(buf);
|
|
||||||
buf = NULL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
77
src/net.h
77
src/net.h
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
#ifndef _NET_H_
|
|
||||||
#define _NET_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
void initialise_network();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int32_t network_socket(int32_t domain, int32_t type, int32_t protocol);
|
|
||||||
|
|
||||||
int32_t network_bind(int32_t s, struct sockaddr *name, int32_t namelen);
|
|
||||||
|
|
||||||
int32_t network_listen(int32_t s, uint32_t backlog);
|
|
||||||
|
|
||||||
int32_t network_accept(int32_t s, struct sockaddr *addr, socklen_t *addrlen);
|
|
||||||
|
|
||||||
int32_t network_connect(int32_t s, struct sockaddr *, int32_t);
|
|
||||||
|
|
||||||
int32_t network_read(int32_t s, void *mem, int32_t len);
|
|
||||||
|
|
||||||
int32_t network_close(int32_t s);
|
|
||||||
|
|
||||||
uint32_t network_gethostip();
|
|
||||||
|
|
||||||
int32_t set_blocking(int32_t s, bool blocking);
|
|
||||||
|
|
||||||
int32_t network_close_blocking(int32_t s);
|
|
||||||
|
|
||||||
int32_t create_server(uint16_t port);
|
|
||||||
|
|
||||||
int32_t send_exact(int32_t s, char *buf, int32_t length);
|
|
||||||
|
|
||||||
int32_t send_from_file(int32_t s, FILE *f);
|
|
||||||
|
|
||||||
int32_t recv_to_file(int32_t s, FILE *f);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _NET_H_ */
|
|
@ -1,24 +0,0 @@
|
|||||||
#include "BackgroundThreadWrapper.hpp"
|
|
||||||
#include <coreinit/cache.h>
|
|
||||||
|
|
||||||
BackgroundThreadWrapper::BackgroundThreadWrapper(int32_t priority) : CThread(CThread::eAttributeAffCore2, priority, 0x100000, nullptr, nullptr, "FTPiiU Server") {
|
|
||||||
}
|
|
||||||
|
|
||||||
BackgroundThreadWrapper::~BackgroundThreadWrapper() {
|
|
||||||
exitThread = 1;
|
|
||||||
stopThread();
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BackgroundThreadWrapper::executeThread() {
|
|
||||||
while (true) {
|
|
||||||
if (exitThread) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!whileLoop()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
threadEnded = true;
|
|
||||||
OSMemoryBarrier();
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CThread.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <wut_types.h>
|
|
||||||
|
|
||||||
class BackgroundThreadWrapper : public CThread {
|
|
||||||
public:
|
|
||||||
explicit BackgroundThreadWrapper(int32_t priority);
|
|
||||||
|
|
||||||
~BackgroundThreadWrapper() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
[[nodiscard]] BOOL shouldExit() const {
|
|
||||||
return (exitThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setThreadPriority(int32_t priority) override {
|
|
||||||
CThread::setThreadPriority(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopThread() {
|
|
||||||
exitThread = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasThreadStopped() {
|
|
||||||
return threadEnded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
volatile bool threadEnded = false;
|
|
||||||
volatile bool exitThread = false;
|
|
||||||
|
|
||||||
void executeThread() override;
|
|
||||||
|
|
||||||
virtual BOOL whileLoop() = 0;
|
|
||||||
};
|
|
@ -1,150 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* Copyright (C) 2015 Dimok
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
****************************************************************************/
|
|
||||||
#ifndef CTHREAD_H_
|
|
||||||
#define CTHREAD_H_
|
|
||||||
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include <coreinit/systeminfo.h>
|
|
||||||
#include <coreinit/thread.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <string>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
class CThread {
|
|
||||||
public:
|
|
||||||
typedef void (*Callback)(CThread *thread, void *arg);
|
|
||||||
|
|
||||||
//! constructor
|
|
||||||
explicit CThread(int32_t iAttr, int32_t iPriority = 16, int32_t iStackSize = 0x8000, CThread::Callback callback = nullptr, void *callbackArg = nullptr, const std::string &threadName = "")
|
|
||||||
: pThread(nullptr), pThreadStack(nullptr), pCallback(callback), pCallbackArg(callbackArg) {
|
|
||||||
//! save attribute assignment
|
|
||||||
iAttributes = iAttr;
|
|
||||||
//! allocate the thread
|
|
||||||
pThread = (OSThread *) memalign(8, sizeof(OSThread));
|
|
||||||
//! allocate the stack
|
|
||||||
pThreadStack = (uint8_t *) memalign(0x20, iStackSize);
|
|
||||||
//! create the thread
|
|
||||||
if (pThread && pThreadStack) {
|
|
||||||
OSCreateThread(pThread, &CThread::threadCallback, 1, (char *) this, pThreadStack + iStackSize, iStackSize, iPriority, iAttributes);
|
|
||||||
pThreadName = threadName;
|
|
||||||
OSSetThreadName(pThread, pThreadName.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! destructor
|
|
||||||
virtual ~CThread() {
|
|
||||||
shutdownThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
static CThread *create(CThread::Callback callback, void *callbackArg, int32_t iAttr = eAttributeNone, int32_t iPriority = 16, int32_t iStackSize = 0x8000) {
|
|
||||||
return (new CThread(iAttr, iPriority, iStackSize, callback, callbackArg));
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Get thread ID
|
|
||||||
[[nodiscard]] virtual void *getThread() const {
|
|
||||||
return pThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Thread entry function
|
|
||||||
virtual void executeThread() {
|
|
||||||
if (pCallback)
|
|
||||||
pCallback(this, pCallbackArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Suspend thread
|
|
||||||
virtual void suspendThread() {
|
|
||||||
if (isThreadSuspended()) return;
|
|
||||||
if (pThread) OSSuspendThread(pThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Resume thread
|
|
||||||
virtual void resumeThread() {
|
|
||||||
if (!isThreadSuspended()) return;
|
|
||||||
if (pThread) OSResumeThread(pThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Set thread priority
|
|
||||||
virtual void setThreadPriority(int prio) {
|
|
||||||
if (pThread) OSSetThreadPriority(pThread, prio);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Check if thread is suspended
|
|
||||||
[[nodiscard]] virtual BOOL isThreadSuspended() const {
|
|
||||||
if (pThread) return OSIsThreadSuspended(pThread);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Check if thread is terminated
|
|
||||||
[[nodiscard]] BOOL isThreadTerminated() const {
|
|
||||||
if (pThread) return OSIsThreadTerminated(pThread);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Check if thread is running
|
|
||||||
[[nodiscard]] virtual BOOL isThreadRunning() const {
|
|
||||||
return !isThreadSuspended() && !isThreadRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Shutdown thread
|
|
||||||
virtual void shutdownThread() {
|
|
||||||
//! wait for thread to finish
|
|
||||||
if (pThread && !(iAttributes & eAttributeDetach)) {
|
|
||||||
if (isThreadSuspended()) {
|
|
||||||
resumeThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
OSJoinThread(pThread, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! free the thread stack buffer
|
|
||||||
if (pThreadStack) {
|
|
||||||
free(pThreadStack);
|
|
||||||
}
|
|
||||||
if (pThread) {
|
|
||||||
free(pThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
pThread = nullptr;
|
|
||||||
pThreadStack = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Thread attributes
|
|
||||||
enum eCThreadAttributes {
|
|
||||||
eAttributeNone = 0x07,
|
|
||||||
eAttributeAffCore0 = 0x01,
|
|
||||||
eAttributeAffCore1 = 0x02,
|
|
||||||
eAttributeAffCore2 = 0x04,
|
|
||||||
eAttributeDetach = 0x08,
|
|
||||||
eAttributePinnedAff = 0x10
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int threadCallback(int argc, const char **argv) {
|
|
||||||
//! After call to start() continue with the internal function
|
|
||||||
((CThread *) argv)->executeThread();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int iAttributes;
|
|
||||||
OSThread *pThread;
|
|
||||||
uint8_t *pThreadStack;
|
|
||||||
Callback pCallback;
|
|
||||||
void *pCallbackArg;
|
|
||||||
std::string pThreadName;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,36 +0,0 @@
|
|||||||
#ifdef DEBUG
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <whb/log_cafe.h>
|
|
||||||
#include <whb/log_module.h>
|
|
||||||
#include <whb/log_udp.h>
|
|
||||||
|
|
||||||
uint32_t moduleLogInit = false;
|
|
||||||
uint32_t cafeLogInit = false;
|
|
||||||
uint32_t udpLogInit = false;
|
|
||||||
#endif // DEBUG
|
|
||||||
|
|
||||||
void initLogging() {
|
|
||||||
#ifdef DEBUG
|
|
||||||
if (!(moduleLogInit = WHBLogModuleInit())) {
|
|
||||||
cafeLogInit = WHBLogCafeInit();
|
|
||||||
udpLogInit = WHBLogUdpInit();
|
|
||||||
}
|
|
||||||
#endif // DEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
void deinitLogging() {
|
|
||||||
#ifdef DEBUG
|
|
||||||
if (moduleLogInit) {
|
|
||||||
WHBLogModuleDeinit();
|
|
||||||
moduleLogInit = false;
|
|
||||||
}
|
|
||||||
if (cafeLogInit) {
|
|
||||||
WHBLogCafeDeinit();
|
|
||||||
cafeLogInit = false;
|
|
||||||
}
|
|
||||||
if (udpLogInit) {
|
|
||||||
WHBLogUdpDeinit();
|
|
||||||
udpLogInit = false;
|
|
||||||
}
|
|
||||||
#endif // DEBUG
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <coreinit/debug.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <whb/log.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LOG_APP_TYPE "P"
|
|
||||||
#define LOG_APP_NAME "ftpiiu"
|
|
||||||
|
|
||||||
#define __FILENAME__ ({ \
|
|
||||||
const char *__filename = __FILE__; \
|
|
||||||
const char *__pos = strrchr(__filename, '/'); \
|
|
||||||
if (!__pos) __pos = strrchr(__filename, '\\'); \
|
|
||||||
__pos ? __pos + 1 : __filename; \
|
|
||||||
})
|
|
||||||
|
|
||||||
#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \
|
|
||||||
do { \
|
|
||||||
LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
|
|
||||||
#ifdef VERBOSE_DEBUG
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS)
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "", "", FMT, ##ARGS);
|
|
||||||
#else
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
|
|
||||||
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
|
|
||||||
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##WARN ## ", "\n", FMT, ##ARGS)
|
|
||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void initLogging();
|
|
||||||
|
|
||||||
void deinitLogging();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,38 +0,0 @@
|
|||||||
#include "utils/logger.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <whb/log.h>
|
|
||||||
|
|
||||||
// https://gist.github.com/ccbrown/9722406
|
|
||||||
void dumpHex(const void *data, size_t size) {
|
|
||||||
char ascii[17];
|
|
||||||
size_t i, j;
|
|
||||||
ascii[16] = '\0';
|
|
||||||
DEBUG_FUNCTION_LINE("0x%08X (0x0000): ", data);
|
|
||||||
for (i = 0; i < size; ++i) {
|
|
||||||
WHBLogWritef("%02X ", ((unsigned char *) data)[i]);
|
|
||||||
if (((unsigned char *) data)[i] >= ' ' && ((unsigned char *) data)[i] <= '~') {
|
|
||||||
ascii[i % 16] = ((unsigned char *) data)[i];
|
|
||||||
} else {
|
|
||||||
ascii[i % 16] = '.';
|
|
||||||
}
|
|
||||||
if ((i + 1) % 8 == 0 || i + 1 == size) {
|
|
||||||
WHBLogWritef(" ");
|
|
||||||
if ((i + 1) % 16 == 0) {
|
|
||||||
WHBLogPrintf("| %s ", ascii);
|
|
||||||
if (i + 1 < size) {
|
|
||||||
DEBUG_FUNCTION_LINE("0x%08X (0x%04X); ", data + i + 1, i + 1);
|
|
||||||
}
|
|
||||||
} else if (i + 1 == size) {
|
|
||||||
ascii[(i + 1) % 16] = '\0';
|
|
||||||
if ((i + 1) % 16 <= 8) {
|
|
||||||
WHBLogWritef(" ");
|
|
||||||
}
|
|
||||||
for (j = (i + 1) % 16; j < 16; ++j) {
|
|
||||||
WHBLogWritef(" ");
|
|
||||||
}
|
|
||||||
WHBLogPrintf("| %s ", ascii);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LIMIT(x, min, max) \
|
|
||||||
({ \
|
|
||||||
typeof(x) _x = x; \
|
|
||||||
typeof(min) _min = min; \
|
|
||||||
typeof(max) _max = max; \
|
|
||||||
(((_x) < (_min)) ? (_min) : ((_x) > (_max)) ? (_max) \
|
|
||||||
: (_x)); \
|
|
||||||
})
|
|
||||||
|
|
||||||
#define DegToRad(a) ((a) *0.01745329252f)
|
|
||||||
#define RadToDeg(a) ((a) *57.29577951f)
|
|
||||||
|
|
||||||
#define ALIGN4(x) (((x) + 3) & ~3)
|
|
||||||
#define ALIGN32(x) (((x) + 31) & ~31)
|
|
||||||
|
|
||||||
// those work only in powers of 2
|
|
||||||
#define ROUNDDOWN(val, align) ((val) & ~(align - 1))
|
|
||||||
#define ROUNDUP(val, align) ROUNDDOWN(((val) + (align - 1)), align)
|
|
||||||
|
|
||||||
|
|
||||||
#define le16(i) ((((uint16_t) ((i) &0xFF)) << 8) | ((uint16_t) (((i) &0xFF00) >> 8)))
|
|
||||||
#define le32(i) ((((uint32_t) le16((i) &0xFFFF)) << 16) | ((uint32_t) le16(((i) &0xFFFF0000) >> 16)))
|
|
||||||
#define le64(i) ((((uint64_t) le32((i) &0xFFFFFFFFLL)) << 32) | ((uint64_t) le32(((i) &0xFFFFFFFF00000000LL) >> 32)))
|
|
||||||
|
|
||||||
//Needs to have log_init() called beforehand.
|
|
||||||
void dumpHex(const void *data, size_t size);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,289 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* Copyright (C) 2008
|
|
||||||
* Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010
|
|
||||||
* by Dimok
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any
|
|
||||||
* damages arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any
|
|
||||||
* purpose, including commercial applications, and to alter it and
|
|
||||||
* redistribute it freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you
|
|
||||||
* must not claim that you wrote the original software. If you use
|
|
||||||
* this software in a product, an acknowledgment in the product
|
|
||||||
* documentation would be appreciated but is not required.
|
|
||||||
*
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and
|
|
||||||
* must not be misrepresented as being the original software.
|
|
||||||
*
|
|
||||||
* 3. This notice may not be removed or altered from any source
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* for WiiXplorer 2010
|
|
||||||
***************************************************************************/
|
|
||||||
#include "virtualpath.h"
|
|
||||||
#include "main.h"
|
|
||||||
#include "utils/logger.h"
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
uint8_t MAX_VIRTUAL_PARTITIONS = 0;
|
|
||||||
VIRTUAL_PARTITION *VIRTUAL_PARTITIONS = NULL;
|
|
||||||
|
|
||||||
uint8_t MAX_VIRTUAL_FS = 0;
|
|
||||||
VIRTUAL_PARTITION *VIRTUAL_FS = NULL;
|
|
||||||
|
|
||||||
uint8_t MAX_VIRTUAL_FS_VOL = 0;
|
|
||||||
VIRTUAL_PARTITION *VIRTUAL_FS_VOL = NULL;
|
|
||||||
|
|
||||||
void VirtualMountDevice(const char *path) {
|
|
||||||
if (!path)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
char name[255];
|
|
||||||
char alias[255];
|
|
||||||
char prefix[255];
|
|
||||||
bool namestop = false;
|
|
||||||
|
|
||||||
alias[0] = '/';
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (path[i] == ':')
|
|
||||||
namestop = true;
|
|
||||||
|
|
||||||
if (!namestop) {
|
|
||||||
name[i] = path[i];
|
|
||||||
name[i + 1] = '\0';
|
|
||||||
alias[i + 1] = path[i];
|
|
||||||
alias[i + 2] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix[i] = path[i];
|
|
||||||
prefix[i + 1] = '\0';
|
|
||||||
i++;
|
|
||||||
} while (path[i - 1] != '/');
|
|
||||||
AddVirtualPath(name, alias, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddVirtualPath(const char *name, const char *alias, const char *prefix) {
|
|
||||||
if (!VIRTUAL_PARTITIONS) {
|
|
||||||
VIRTUAL_PARTITIONS = (VIRTUAL_PARTITION *) malloc(sizeof(VIRTUAL_PARTITION));
|
|
||||||
if (!VIRTUAL_PARTITIONS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to allocate VIRTUAL_PARTITIONS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_PARTITION *tmp = realloc(VIRTUAL_PARTITIONS, sizeof(VIRTUAL_PARTITION) * (MAX_VIRTUAL_PARTITIONS + 1));
|
|
||||||
if (!tmp) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to reallocate VIRTUAL_PARTITIONS");
|
|
||||||
if (VIRTUAL_PARTITIONS) {
|
|
||||||
for (int i = 0; i < MAX_VIRTUAL_PARTITIONS; i++) {
|
|
||||||
if (VIRTUAL_PARTITIONS[i].name) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].name);
|
|
||||||
VIRTUAL_PARTITIONS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_PARTITIONS[i].alias) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].alias);
|
|
||||||
VIRTUAL_PARTITIONS[i].alias = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_PARTITIONS[i].prefix) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].prefix);
|
|
||||||
VIRTUAL_PARTITIONS[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(VIRTUAL_PARTITIONS);
|
|
||||||
}
|
|
||||||
VIRTUAL_PARTITIONS = NULL;
|
|
||||||
MAX_VIRTUAL_PARTITIONS = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_PARTITIONS = tmp;
|
|
||||||
|
|
||||||
VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].name = strdup(name);
|
|
||||||
VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].alias = strdup(alias);
|
|
||||||
VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].prefix = strdup(prefix);
|
|
||||||
VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].inserted = true;
|
|
||||||
|
|
||||||
MAX_VIRTUAL_PARTITIONS++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddVirtualFSPath(const char *name, const char *alias, const char *prefix) {
|
|
||||||
if (!VIRTUAL_FS) {
|
|
||||||
VIRTUAL_FS = (VIRTUAL_PARTITION *) malloc(sizeof(VIRTUAL_PARTITION));
|
|
||||||
if (!VIRTUAL_FS) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to allocate VIRTUAL_FS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_PARTITION *tmp = realloc(VIRTUAL_FS, sizeof(VIRTUAL_PARTITION) * (MAX_VIRTUAL_FS + 1));
|
|
||||||
if (!tmp) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to reallocate VIRTUAL_FS");
|
|
||||||
if (VIRTUAL_FS) {
|
|
||||||
for (int i = 0; i < MAX_VIRTUAL_FS; i++) {
|
|
||||||
if (VIRTUAL_FS[i].name) {
|
|
||||||
free(VIRTUAL_FS[i].name);
|
|
||||||
VIRTUAL_FS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS[i].alias) {
|
|
||||||
free(VIRTUAL_FS[i].alias);
|
|
||||||
VIRTUAL_FS[i].alias = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS[i].prefix) {
|
|
||||||
free(VIRTUAL_FS[i].prefix);
|
|
||||||
VIRTUAL_FS[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(VIRTUAL_FS);
|
|
||||||
}
|
|
||||||
VIRTUAL_FS = NULL;
|
|
||||||
MAX_VIRTUAL_FS = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_FS = tmp;
|
|
||||||
|
|
||||||
VIRTUAL_FS[MAX_VIRTUAL_FS].name = strdup(name);
|
|
||||||
VIRTUAL_FS[MAX_VIRTUAL_FS].alias = NULL;
|
|
||||||
VIRTUAL_FS[MAX_VIRTUAL_FS].prefix = NULL;
|
|
||||||
VIRTUAL_FS[MAX_VIRTUAL_FS].inserted = true;
|
|
||||||
|
|
||||||
MAX_VIRTUAL_FS++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddVirtualFSVOLPath(const char *name, const char *alias, const char *prefix) {
|
|
||||||
if (!VIRTUAL_FS_VOL) {
|
|
||||||
VIRTUAL_FS_VOL = (VIRTUAL_PARTITION *) malloc(sizeof(VIRTUAL_PARTITION));
|
|
||||||
if (!VIRTUAL_FS_VOL) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to allocate VIRTUAL_FS_VOL");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_PARTITION *tmp = realloc(VIRTUAL_FS_VOL, sizeof(VIRTUAL_PARTITION) * (MAX_VIRTUAL_FS_VOL + 1));
|
|
||||||
if (!tmp) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to reallocate VIRTUAL_FS_VOL");
|
|
||||||
if (VIRTUAL_FS_VOL) {
|
|
||||||
for (int i = 0; i < MAX_VIRTUAL_FS_VOL; i++) {
|
|
||||||
if (VIRTUAL_FS_VOL[i].name) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].name);
|
|
||||||
VIRTUAL_FS_VOL[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS_VOL[i].alias) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].alias);
|
|
||||||
VIRTUAL_FS_VOL[i].alias = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS_VOL[i].prefix) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].prefix);
|
|
||||||
VIRTUAL_FS_VOL[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(VIRTUAL_FS_VOL);
|
|
||||||
}
|
|
||||||
VIRTUAL_FS_VOL = NULL;
|
|
||||||
MAX_VIRTUAL_FS_VOL = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIRTUAL_FS_VOL = tmp;
|
|
||||||
|
|
||||||
VIRTUAL_FS_VOL[MAX_VIRTUAL_FS_VOL].name = strdup(name);
|
|
||||||
VIRTUAL_FS_VOL[MAX_VIRTUAL_FS_VOL].alias = NULL;
|
|
||||||
VIRTUAL_FS_VOL[MAX_VIRTUAL_FS_VOL].prefix = NULL;
|
|
||||||
VIRTUAL_FS_VOL[MAX_VIRTUAL_FS_VOL].inserted = true;
|
|
||||||
|
|
||||||
MAX_VIRTUAL_FS_VOL++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MountVirtualDevices() {
|
|
||||||
VirtualMountDevice("fs:/");
|
|
||||||
if (gSystemFilesAllowed) {
|
|
||||||
VirtualMountDevice("slccmpt01:/");
|
|
||||||
VirtualMountDevice("storage_odd_tickets:/");
|
|
||||||
VirtualMountDevice("storage_odd_updates:/");
|
|
||||||
VirtualMountDevice("storage_odd_content:/");
|
|
||||||
VirtualMountDevice("storage_odd_content2:/");
|
|
||||||
VirtualMountDevice("storage_slc:/");
|
|
||||||
VirtualMountDevice("storage_mlc:/");
|
|
||||||
VirtualMountDevice("storage_usb:/");
|
|
||||||
VirtualMountDevice("usb:/");
|
|
||||||
}
|
|
||||||
AddVirtualFSPath("vol", NULL, NULL);
|
|
||||||
AddVirtualFSVOLPath("external01", NULL, NULL);
|
|
||||||
AddVirtualFSVOLPath("content", NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UnmountVirtualPaths() {
|
|
||||||
uint32_t i = 0;
|
|
||||||
for (i = 0; i < MAX_VIRTUAL_PARTITIONS; i++) {
|
|
||||||
if (VIRTUAL_PARTITIONS[i].name) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].name);
|
|
||||||
VIRTUAL_PARTITIONS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_PARTITIONS[i].alias) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].alias);
|
|
||||||
VIRTUAL_PARTITIONS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_PARTITIONS[i].prefix) {
|
|
||||||
free(VIRTUAL_PARTITIONS[i].prefix);
|
|
||||||
VIRTUAL_PARTITIONS[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_VIRTUAL_FS_VOL; i++) {
|
|
||||||
if (VIRTUAL_FS_VOL[i].name) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].name);
|
|
||||||
VIRTUAL_FS_VOL[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS_VOL[i].alias) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].alias);
|
|
||||||
VIRTUAL_FS_VOL[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS_VOL[i].prefix) {
|
|
||||||
free(VIRTUAL_FS_VOL[i].prefix);
|
|
||||||
VIRTUAL_FS_VOL[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_VIRTUAL_FS; i++) {
|
|
||||||
if (VIRTUAL_FS[i].name) {
|
|
||||||
free(VIRTUAL_FS[i].name);
|
|
||||||
VIRTUAL_FS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS[i].alias) {
|
|
||||||
free(VIRTUAL_FS[i].alias);
|
|
||||||
VIRTUAL_FS[i].name = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS[i].prefix) {
|
|
||||||
free(VIRTUAL_FS[i].prefix);
|
|
||||||
VIRTUAL_FS[i].prefix = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VIRTUAL_PARTITIONS) {
|
|
||||||
free(VIRTUAL_PARTITIONS);
|
|
||||||
VIRTUAL_PARTITIONS = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS_VOL) {
|
|
||||||
free(VIRTUAL_FS_VOL);
|
|
||||||
VIRTUAL_FS_VOL = NULL;
|
|
||||||
}
|
|
||||||
if (VIRTUAL_FS) {
|
|
||||||
free(VIRTUAL_FS);
|
|
||||||
VIRTUAL_FS = NULL;
|
|
||||||
}
|
|
||||||
VIRTUAL_PARTITIONS = NULL;
|
|
||||||
VIRTUAL_FS_VOL = NULL;
|
|
||||||
VIRTUAL_FS = NULL;
|
|
||||||
MAX_VIRTUAL_PARTITIONS = 0;
|
|
||||||
MAX_VIRTUAL_FS = 0;
|
|
||||||
MAX_VIRTUAL_FS_VOL = 0;
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
* Copyright (C) 2010
|
|
||||||
* by Dimok
|
|
||||||
*
|
|
||||||
* Original VIRTUAL_PART Struct
|
|
||||||
* Copyright (C) 2008
|
|
||||||
* Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any
|
|
||||||
* damages arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any
|
|
||||||
* purpose, including commercial applications, and to alter it and
|
|
||||||
* redistribute it freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you
|
|
||||||
* must not claim that you wrote the original software. If you use
|
|
||||||
* this software in a product, an acknowledgment in the product
|
|
||||||
* documentation would be appreciated but is not required.
|
|
||||||
*
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and
|
|
||||||
* must not be misrepresented as being the original software.
|
|
||||||
*
|
|
||||||
* 3. This notice may not be removed or altered from any source
|
|
||||||
* distribution.
|
|
||||||
*
|
|
||||||
* for WiiXplorer 2010
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef _VIRTUALPATH_H_
|
|
||||||
#define _VIRTUALPATH_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *name;
|
|
||||||
char *alias;
|
|
||||||
char *prefix;
|
|
||||||
bool inserted;
|
|
||||||
} VIRTUAL_PARTITION;
|
|
||||||
|
|
||||||
extern VIRTUAL_PARTITION *VIRTUAL_PARTITIONS;
|
|
||||||
extern uint8_t MAX_VIRTUAL_PARTITIONS;
|
|
||||||
|
|
||||||
extern VIRTUAL_PARTITION *VIRTUAL_FS;
|
|
||||||
extern uint8_t MAX_VIRTUAL_FS;
|
|
||||||
|
|
||||||
extern VIRTUAL_PARTITION *VIRTUAL_FS_VOL;
|
|
||||||
extern uint8_t MAX_VIRTUAL_FS_VOL;
|
|
||||||
|
|
||||||
void VirtualMountDevice(const char *devicepath);
|
|
||||||
|
|
||||||
void AddVirtualPath(const char *name, const char *alias, const char *prefix);
|
|
||||||
|
|
||||||
void AddVirtualFSPath(const char *name, const char *alias, const char *prefix);
|
|
||||||
|
|
||||||
void AddVirtualFSVOLPath(const char *name, const char *alias, const char *prefix);
|
|
||||||
|
|
||||||
void MountVirtualDevices();
|
|
||||||
|
|
||||||
void UnmountVirtualPaths();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _VIRTUALPART_H_ */
|
|
422
src/vrt.c
422
src/vrt.c
@ -1,422 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
This work is derived from Daniel Ehlers' <danielehlers@mindeye.net> srg_vrt branch.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/dirent.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "virtualpath.h"
|
|
||||||
#include "vrt.h"
|
|
||||||
|
|
||||||
static char *virtual_abspath(char *virtual_cwd, char *virtual_path) {
|
|
||||||
char *path;
|
|
||||||
if (virtual_path[0] == '/') {
|
|
||||||
path = virtual_path;
|
|
||||||
} else {
|
|
||||||
size_t path_size = strlen(virtual_cwd) + strlen(virtual_path) + 1;
|
|
||||||
if (path_size > MAXPATHLEN || !(path = malloc(path_size))) return NULL;
|
|
||||||
strcpy(path, virtual_cwd);
|
|
||||||
strcat(path, virtual_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *normalised_path = malloc(strlen(path) + 1);
|
|
||||||
if (!normalised_path) goto end;
|
|
||||||
*normalised_path = '\0';
|
|
||||||
char *curr_dir = normalised_path;
|
|
||||||
|
|
||||||
uint32_t state = 0; // 0:start, 1:slash, 2:dot, 3:dotdot
|
|
||||||
char *token = path;
|
|
||||||
while (1) {
|
|
||||||
switch (state) {
|
|
||||||
case 0:
|
|
||||||
if (*token == '/') {
|
|
||||||
state = 1;
|
|
||||||
curr_dir = normalised_path + strlen(normalised_path);
|
|
||||||
strncat(normalised_path, token, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (*token == '.') state = 2;
|
|
||||||
else if (*token != '/')
|
|
||||||
state = 0;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (*token == '/' || !*token) {
|
|
||||||
state = 1;
|
|
||||||
*(curr_dir + 1) = '\0';
|
|
||||||
} else if (*token == '.')
|
|
||||||
state = 3;
|
|
||||||
else
|
|
||||||
state = 0;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (*token == '/' || !*token) {
|
|
||||||
state = 1;
|
|
||||||
*curr_dir = '\0';
|
|
||||||
char *prev_dir = strrchr(normalised_path, '/');
|
|
||||||
if (prev_dir) curr_dir = prev_dir;
|
|
||||||
else
|
|
||||||
*curr_dir = '/';
|
|
||||||
*(curr_dir + 1) = '\0';
|
|
||||||
} else
|
|
||||||
state = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!*token) break;
|
|
||||||
if (state == 0 || *token != '/') strncat(normalised_path, token, 1);
|
|
||||||
token++;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t end = strlen(normalised_path);
|
|
||||||
while (end > 1 && normalised_path[end - 1] == '/') {
|
|
||||||
normalised_path[--end] = '\x00';
|
|
||||||
}
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (path != virtual_path) {
|
|
||||||
free(path);
|
|
||||||
path = NULL;
|
|
||||||
}
|
|
||||||
return normalised_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Converts a client-visible path to a real absolute path
|
|
||||||
E.g. "/sd/foo" -> "sd:/foo"
|
|
||||||
"/sd" -> "sd:/"
|
|
||||||
"/sd/../usb" -> "usb:/"
|
|
||||||
The resulting path will fit in an array of size MAXPATHLEN
|
|
||||||
Returns NULL to indicate that the client-visible path is invalid
|
|
||||||
*/
|
|
||||||
char *to_real_path(char *virtual_cwd, char *virtual_path) {
|
|
||||||
errno = ENOENT;
|
|
||||||
if (strchr(virtual_path, ':')) {
|
|
||||||
return NULL; // colon is not allowed in virtual path, i've decided =P
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual_path = virtual_abspath(virtual_cwd, virtual_path);
|
|
||||||
if (!virtual_path) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *path = NULL;
|
|
||||||
char *rest = virtual_path;
|
|
||||||
|
|
||||||
if (!strcmp("/", virtual_path)) {
|
|
||||||
// indicate vfs-root with ""
|
|
||||||
path = malloc(1);
|
|
||||||
if (path) {
|
|
||||||
path[0] = 0;
|
|
||||||
}
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *prefix = NULL;
|
|
||||||
uint32_t i;
|
|
||||||
for (i = 0; i < MAX_VIRTUAL_PARTITIONS; i++) {
|
|
||||||
VIRTUAL_PARTITION *partition = VIRTUAL_PARTITIONS + i;
|
|
||||||
const char *alias = partition->alias;
|
|
||||||
size_t alias_len = strlen(alias);
|
|
||||||
if (!strcasecmp(alias, virtual_path) || (!strncasecmp(alias, virtual_path, alias_len) && virtual_path[alias_len] == '/')) {
|
|
||||||
prefix = partition->prefix;
|
|
||||||
rest += alias_len;
|
|
||||||
if (*rest == '/') rest++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!prefix) {
|
|
||||||
errno = ENODEV;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t real_path_size = strlen(prefix) + strlen(rest) + 1;
|
|
||||||
if (real_path_size > MAXPATHLEN) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
path = malloc(real_path_size);
|
|
||||||
if (!path) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
strcpy(path, prefix);
|
|
||||||
strcat(path, rest);
|
|
||||||
|
|
||||||
end:
|
|
||||||
free(virtual_path);
|
|
||||||
virtual_path = NULL;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int checkdir(char *path) {
|
|
||||||
if (path != NULL &&
|
|
||||||
(strcmp(path, "fs:/vol") == 0 ||
|
|
||||||
strcmp(path, "fs:/vol/") == 0 ||
|
|
||||||
strcmp(path, "fs:/") == 0)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DIR *dir = opendir(path);
|
|
||||||
if (dir) {
|
|
||||||
closedir(dir);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void *(*path_func)(char *path, ...);
|
|
||||||
|
|
||||||
static void *with_virtual_path(void *virtual_cwd, void *void_f, char *virtual_path, int32_t failed, ...) {
|
|
||||||
char *path = to_real_path(virtual_cwd, virtual_path);
|
|
||||||
if (!path || !*path) {
|
|
||||||
if (path && path != virtual_path) {
|
|
||||||
free(path);
|
|
||||||
path = NULL;
|
|
||||||
}
|
|
||||||
return (void *) failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
path_func f = (path_func) void_f;
|
|
||||||
va_list ap;
|
|
||||||
void *args[3];
|
|
||||||
unsigned int num_args = 0;
|
|
||||||
va_start(ap, failed);
|
|
||||||
do {
|
|
||||||
void *arg = va_arg(ap, void *);
|
|
||||||
if (!arg) break;
|
|
||||||
args[num_args++] = arg;
|
|
||||||
} while (1);
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
void *result;
|
|
||||||
switch (num_args) {
|
|
||||||
case 0:
|
|
||||||
result = f(path);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
result = f(path, args[0]);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
result = f(path, args[0], args[1]);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
result = f(path, args[0], args[1], args[2]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
result = (void *) failed;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(path);
|
|
||||||
path = NULL;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *vrt_fopen(char *cwd, char *path, char *mode) {
|
|
||||||
return with_virtual_path(cwd, fopen, path, 0, mode, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_stat(char *cwd, char *path, struct stat *st) {
|
|
||||||
char *real_path = to_real_path(cwd, path);
|
|
||||||
if (!real_path) {
|
|
||||||
return -1;
|
|
||||||
} else if (!*real_path || (strcmp(path, ".") == 0) || (strlen(cwd) == 1) || ((strlen(cwd) > 1) && (strcmp(path, "..") == 0))) {
|
|
||||||
st->st_mode = S_IFDIR;
|
|
||||||
st->st_size = 31337;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
free(real_path);
|
|
||||||
real_path = NULL;
|
|
||||||
return (int) with_virtual_path(cwd, stat, path, -1, st, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int vrt_checkdir(char *cwd, char *path) {
|
|
||||||
char *real_path = to_real_path(cwd, path);
|
|
||||||
if (!real_path) {
|
|
||||||
return -1;
|
|
||||||
} else if (!*real_path || (strcmp(path, ".") == 0) || (strlen(cwd) == 1) || ((strlen(cwd) > 1) && (strcmp(path, "..") == 0))) {
|
|
||||||
if (path != real_path) {
|
|
||||||
free(real_path);
|
|
||||||
real_path = NULL;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
free(real_path);
|
|
||||||
real_path = NULL;
|
|
||||||
return (int) with_virtual_path(cwd, checkdir, path, -1, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_chdir(char *cwd, char *path) {
|
|
||||||
|
|
||||||
if (vrt_checkdir(cwd, path)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
char *abspath = virtual_abspath(cwd, path);
|
|
||||||
if (!abspath) {
|
|
||||||
errno = ENOMEM;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
strcpy(cwd, abspath);
|
|
||||||
if (cwd[1]) {
|
|
||||||
strcat(cwd, "/");
|
|
||||||
}
|
|
||||||
free(abspath);
|
|
||||||
abspath = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_unlink(char *cwd, char *path) {
|
|
||||||
return (int) with_virtual_path(cwd, unlink, path, -1, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_mkdir(char *cwd, char *path, mode_t mode) {
|
|
||||||
return (int) with_virtual_path(cwd, mkdir, path, -1, mode, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_rename(char *cwd, char *from_path, char *to_path) {
|
|
||||||
char *real_to_path = to_real_path(cwd, to_path);
|
|
||||||
if (!real_to_path || !*real_to_path) return -1;
|
|
||||||
int result = (int) with_virtual_path(cwd, rename, from_path, -1, real_to_path, NULL);
|
|
||||||
free(real_to_path);
|
|
||||||
real_to_path = NULL;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
When in vfs-root this creates a fake DIR_ITER.
|
|
||||||
*/
|
|
||||||
DIR_P *vrt_opendir(char *cwd, char *path) {
|
|
||||||
char *real_path = to_real_path(cwd, path);
|
|
||||||
if (!real_path) { return NULL; }
|
|
||||||
|
|
||||||
DIR_P *iter = malloc(sizeof(DIR_P));
|
|
||||||
if (!iter) {
|
|
||||||
if (*real_path != 0) {
|
|
||||||
free(real_path);
|
|
||||||
real_path = NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter->virt_root = 0;
|
|
||||||
iter->virtual_fs = 0;
|
|
||||||
iter->virtual_fs_vol = 0;
|
|
||||||
iter->path = real_path;
|
|
||||||
|
|
||||||
if (*iter->path == 0 || (strncmp(iter->path, "fs:", 3) == 0 && strlen(iter->path) <= 4) || (strncmp(iter->path, "fs:/vol", 3) == 0 && strlen(iter->path) <= 8)) {
|
|
||||||
iter->dir = malloc(sizeof(DIR));
|
|
||||||
if (!iter->dir) {
|
|
||||||
// root path is not allocated
|
|
||||||
free(iter);
|
|
||||||
iter = NULL;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
memset(iter->dir, 0, sizeof(DIR));
|
|
||||||
|
|
||||||
if (strncmp(iter->path, "fs:/vol", 7) == 0) {
|
|
||||||
iter->virtual_fs_vol = 1; // we are at the virtual fs
|
|
||||||
} else if (strncmp(iter->path, "fs:", 3) == 0) {
|
|
||||||
iter->virtual_fs = 1; // we are at the virtual fs
|
|
||||||
} else {
|
|
||||||
iter->virt_root = 1; // we are at the virtual root
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
iter->dir = with_virtual_path(cwd, opendir, path, 0, NULL);
|
|
||||||
if (!iter->dir) {
|
|
||||||
free(iter->path);
|
|
||||||
iter->path = NULL;
|
|
||||||
free(iter);
|
|
||||||
iter = NULL;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Yields virtual aliases when pDir->virt_root
|
|
||||||
*/
|
|
||||||
struct dirent *vrt_readdir(DIR_P *pDir) {
|
|
||||||
if (!pDir || !pDir->dir) { return NULL; }
|
|
||||||
|
|
||||||
DIR *iter = pDir->dir;
|
|
||||||
if (pDir->virt_root || pDir->virtual_fs || pDir->virtual_fs_vol) {
|
|
||||||
int max = MAX_VIRTUAL_PARTITIONS;
|
|
||||||
VIRTUAL_PARTITION *PARTITION_PTR = VIRTUAL_PARTITIONS;
|
|
||||||
if (pDir->virtual_fs) {
|
|
||||||
max = MAX_VIRTUAL_FS;
|
|
||||||
PARTITION_PTR = VIRTUAL_FS;
|
|
||||||
} else if (pDir->virtual_fs_vol) {
|
|
||||||
max = MAX_VIRTUAL_FS_VOL;
|
|
||||||
PARTITION_PTR = VIRTUAL_FS_VOL;
|
|
||||||
}
|
|
||||||
for (; (uint32_t) iter->position < max; iter->position++) {
|
|
||||||
VIRTUAL_PARTITION *partition = PARTITION_PTR + (int) iter->position;
|
|
||||||
if (partition->inserted) {
|
|
||||||
iter->fileData.d_type = DT_DIR;
|
|
||||||
if (pDir->virtual_fs || pDir->virtual_fs_vol) {
|
|
||||||
strcpy(iter->fileData.d_name, partition->name);
|
|
||||||
} else {
|
|
||||||
strcpy(iter->fileData.d_name, partition->alias + 1);
|
|
||||||
}
|
|
||||||
iter->position++;
|
|
||||||
return &iter->fileData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return readdir(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
int vrt_closedir(DIR_P *iter) {
|
|
||||||
if (!iter) return -1;
|
|
||||||
|
|
||||||
if (iter->dir) {
|
|
||||||
if (iter->virt_root || iter->virtual_fs || iter->virtual_fs_vol) {
|
|
||||||
free(iter->dir);
|
|
||||||
iter->dir = NULL;
|
|
||||||
} else {
|
|
||||||
closedir(iter->dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iter->path) {
|
|
||||||
free(iter->path);
|
|
||||||
iter->path = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(iter);
|
|
||||||
iter = NULL;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
67
src/vrt.h
67
src/vrt.h
@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Copyright (C) 2008 Joseph Jordan <joe.ftpii@psychlaw.com.au>
|
|
||||||
This work is derived from Daniel Ehlers' <danielehlers@mindeye.net> srg_vrt branch.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied warranty.
|
|
||||||
In no event will the authors be held liable for any damages arising from
|
|
||||||
the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1.The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software in a
|
|
||||||
product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2.Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3.This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
*/
|
|
||||||
#ifndef _VRT_H_
|
|
||||||
#define _VRT_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/dirent.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
DIR *dir;
|
|
||||||
char *path;
|
|
||||||
uint8_t virt_root;
|
|
||||||
uint8_t virtual_fs;
|
|
||||||
uint8_t virtual_fs_vol;
|
|
||||||
} DIR_P;
|
|
||||||
|
|
||||||
char *to_real_path(char *virtual_cwd, char *virtual_path);
|
|
||||||
|
|
||||||
FILE *vrt_fopen(char *cwd, char *path, char *mode);
|
|
||||||
|
|
||||||
int vrt_stat(char *cwd, char *path, struct stat *st);
|
|
||||||
|
|
||||||
int vrt_chdir(char *cwd, char *path);
|
|
||||||
|
|
||||||
int vrt_unlink(char *cwd, char *path);
|
|
||||||
|
|
||||||
int vrt_mkdir(char *cwd, char *path, mode_t mode);
|
|
||||||
|
|
||||||
int vrt_rename(char *cwd, char *from_path, char *to_path);
|
|
||||||
|
|
||||||
DIR_P *vrt_opendir(char *cwd, char *path);
|
|
||||||
|
|
||||||
struct dirent *vrt_readdir(DIR_P *iter);
|
|
||||||
|
|
||||||
int vrt_closedir(DIR_P *iter);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* _VRT_H_ */
|
|
Loading…
Reference in New Issue
Block a user