Initial revision
This commit is contained in:
commit
4f5dfc85db
|
@ -0,0 +1,340 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,236 @@
|
|||
Installation Instructions
|
||||
*************************
|
||||
|
||||
Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free
|
||||
Software Foundation, Inc.
|
||||
|
||||
This file is free documentation; the Free Software Foundation gives
|
||||
unlimited permission to copy, distribute and modify it.
|
||||
|
||||
Basic Installation
|
||||
==================
|
||||
|
||||
These are generic installation instructions.
|
||||
|
||||
The `configure' shell script attempts to guess correct values for
|
||||
various system-dependent variables used during compilation. It uses
|
||||
those values to create a `Makefile' in each directory of the package.
|
||||
It may also create one or more `.h' files containing system-dependent
|
||||
definitions. Finally, it creates a shell script `config.status' that
|
||||
you can run in the future to recreate the current configuration, and a
|
||||
file `config.log' containing compiler output (useful mainly for
|
||||
debugging `configure').
|
||||
|
||||
It can also use an optional file (typically called `config.cache'
|
||||
and enabled with `--cache-file=config.cache' or simply `-C') that saves
|
||||
the results of its tests to speed up reconfiguring. (Caching is
|
||||
disabled by default to prevent problems with accidental use of stale
|
||||
cache files.)
|
||||
|
||||
If you need to do unusual things to compile the package, please try
|
||||
to figure out how `configure' could check whether to do them, and mail
|
||||
diffs or instructions to the address given in the `README' so they can
|
||||
be considered for the next release. If you are using the cache, and at
|
||||
some point `config.cache' contains results you don't want to keep, you
|
||||
may remove or edit it.
|
||||
|
||||
The file `configure.ac' (or `configure.in') is used to create
|
||||
`configure' by a program called `autoconf'. You only need
|
||||
`configure.ac' if you want to change it or regenerate `configure' using
|
||||
a newer version of `autoconf'.
|
||||
|
||||
The simplest way to compile this package is:
|
||||
|
||||
1. `cd' to the directory containing the package's source code and type
|
||||
`./configure' to configure the package for your system. If you're
|
||||
using `csh' on an old version of System V, you might need to type
|
||||
`sh ./configure' instead to prevent `csh' from trying to execute
|
||||
`configure' itself.
|
||||
|
||||
Running `configure' takes awhile. While running, it prints some
|
||||
messages telling which features it is checking for.
|
||||
|
||||
2. Type `make' to compile the package.
|
||||
|
||||
3. Optionally, type `make check' to run any self-tests that come with
|
||||
the package.
|
||||
|
||||
4. Type `make install' to install the programs and any data files and
|
||||
documentation.
|
||||
|
||||
5. You can remove the program binaries and object files from the
|
||||
source code directory by typing `make clean'. To also remove the
|
||||
files that `configure' created (so you can compile the package for
|
||||
a different kind of computer), type `make distclean'. There is
|
||||
also a `make maintainer-clean' target, but that is intended mainly
|
||||
for the package's developers. If you use it, you may have to get
|
||||
all sorts of other programs in order to regenerate files that came
|
||||
with the distribution.
|
||||
|
||||
Compilers and Options
|
||||
=====================
|
||||
|
||||
Some systems require unusual options for compilation or linking that the
|
||||
`configure' script does not know about. Run `./configure --help' for
|
||||
details on some of the pertinent environment variables.
|
||||
|
||||
You can give `configure' initial values for configuration parameters
|
||||
by setting variables in the command line or in the environment. Here
|
||||
is an example:
|
||||
|
||||
./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
|
||||
|
||||
*Note Defining Variables::, for more details.
|
||||
|
||||
Compiling For Multiple Architectures
|
||||
====================================
|
||||
|
||||
You can compile the package for more than one kind of computer at the
|
||||
same time, by placing the object files for each architecture in their
|
||||
own directory. To do this, you must use a version of `make' that
|
||||
supports the `VPATH' variable, such as GNU `make'. `cd' to the
|
||||
directory where you want the object files and executables to go and run
|
||||
the `configure' script. `configure' automatically checks for the
|
||||
source code in the directory that `configure' is in and in `..'.
|
||||
|
||||
If you have to use a `make' that does not support the `VPATH'
|
||||
variable, you have to compile the package for one architecture at a
|
||||
time in the source code directory. After you have installed the
|
||||
package for one architecture, use `make distclean' before reconfiguring
|
||||
for another architecture.
|
||||
|
||||
Installation Names
|
||||
==================
|
||||
|
||||
By default, `make install' installs the package's commands under
|
||||
`/usr/local/bin', include files under `/usr/local/include', etc. You
|
||||
can specify an installation prefix other than `/usr/local' by giving
|
||||
`configure' the option `--prefix=PREFIX'.
|
||||
|
||||
You can specify separate installation prefixes for
|
||||
architecture-specific files and architecture-independent files. If you
|
||||
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
|
||||
PREFIX as the prefix for installing programs and libraries.
|
||||
Documentation and other data files still use the regular prefix.
|
||||
|
||||
In addition, if you use an unusual directory layout you can give
|
||||
options like `--bindir=DIR' to specify different values for particular
|
||||
kinds of files. Run `configure --help' for a list of the directories
|
||||
you can set and what kinds of files go in them.
|
||||
|
||||
If the package supports it, you can cause programs to be installed
|
||||
with an extra prefix or suffix on their names by giving `configure' the
|
||||
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
|
||||
|
||||
Optional Features
|
||||
=================
|
||||
|
||||
Some packages pay attention to `--enable-FEATURE' options to
|
||||
`configure', where FEATURE indicates an optional part of the package.
|
||||
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
|
||||
is something like `gnu-as' or `x' (for the X Window System). The
|
||||
`README' should mention any `--enable-' and `--with-' options that the
|
||||
package recognizes.
|
||||
|
||||
For packages that use the X Window System, `configure' can usually
|
||||
find the X include and library files automatically, but if it doesn't,
|
||||
you can use the `configure' options `--x-includes=DIR' and
|
||||
`--x-libraries=DIR' to specify their locations.
|
||||
|
||||
Specifying the System Type
|
||||
==========================
|
||||
|
||||
There may be some features `configure' cannot figure out automatically,
|
||||
but needs to determine by the type of machine the package will run on.
|
||||
Usually, assuming the package is built to be run on the _same_
|
||||
architectures, `configure' can figure that out, but if it prints a
|
||||
message saying it cannot guess the machine type, give it the
|
||||
`--build=TYPE' option. TYPE can either be a short name for the system
|
||||
type, such as `sun4', or a canonical name which has the form:
|
||||
|
||||
CPU-COMPANY-SYSTEM
|
||||
|
||||
where SYSTEM can have one of these forms:
|
||||
|
||||
OS KERNEL-OS
|
||||
|
||||
See the file `config.sub' for the possible values of each field. If
|
||||
`config.sub' isn't included in this package, then this package doesn't
|
||||
need to know the machine type.
|
||||
|
||||
If you are _building_ compiler tools for cross-compiling, you should
|
||||
use the option `--target=TYPE' to select the type of system they will
|
||||
produce code for.
|
||||
|
||||
If you want to _use_ a cross compiler, that generates code for a
|
||||
platform different from the build platform, you should specify the
|
||||
"host" platform (i.e., that on which the generated programs will
|
||||
eventually be run) with `--host=TYPE'.
|
||||
|
||||
Sharing Defaults
|
||||
================
|
||||
|
||||
If you want to set default values for `configure' scripts to share, you
|
||||
can create a site shell script called `config.site' that gives default
|
||||
values for variables like `CC', `cache_file', and `prefix'.
|
||||
`configure' looks for `PREFIX/share/config.site' if it exists, then
|
||||
`PREFIX/etc/config.site' if it exists. Or, you can set the
|
||||
`CONFIG_SITE' environment variable to the location of the site script.
|
||||
A warning: not all `configure' scripts look for a site script.
|
||||
|
||||
Defining Variables
|
||||
==================
|
||||
|
||||
Variables not defined in a site shell script can be set in the
|
||||
environment passed to `configure'. However, some packages may run
|
||||
configure again during the build, and the customized values of these
|
||||
variables may be lost. In order to avoid this problem, you should set
|
||||
them in the `configure' command line, using `VAR=value'. For example:
|
||||
|
||||
./configure CC=/usr/local2/bin/gcc
|
||||
|
||||
causes the specified `gcc' to be used as the C compiler (unless it is
|
||||
overridden in the site shell script). Here is a another example:
|
||||
|
||||
/bin/bash ./configure CONFIG_SHELL=/bin/bash
|
||||
|
||||
Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent
|
||||
configuration-related scripts to be executed by `/bin/bash'.
|
||||
|
||||
`configure' Invocation
|
||||
======================
|
||||
|
||||
`configure' recognizes the following options to control how it operates.
|
||||
|
||||
`--help'
|
||||
`-h'
|
||||
Print a summary of the options to `configure', and exit.
|
||||
|
||||
`--version'
|
||||
`-V'
|
||||
Print the version of Autoconf used to generate the `configure'
|
||||
script, and exit.
|
||||
|
||||
`--cache-file=FILE'
|
||||
Enable the cache: use and save the results of the tests in FILE,
|
||||
traditionally `config.cache'. FILE defaults to `/dev/null' to
|
||||
disable caching.
|
||||
|
||||
`--config-cache'
|
||||
`-C'
|
||||
Alias for `--cache-file=config.cache'.
|
||||
|
||||
`--quiet'
|
||||
`--silent'
|
||||
`-q'
|
||||
Do not print messages saying which checks are being made. To
|
||||
suppress all normal output, redirect it to `/dev/null' (any error
|
||||
messages will still be shown).
|
||||
|
||||
`--srcdir=DIR'
|
||||
Look for the package's source code in directory DIR. Usually
|
||||
`configure' can determine that directory automatically.
|
||||
|
||||
`configure' also accepts some other, not widely useful, options. Run
|
||||
`configure --help' for more details.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
## Process this file with automake to produce Makefile.in
|
||||
|
||||
bin_PROGRAMS = curlftpfs
|
||||
|
||||
if FUSE_OPT_COMPAT
|
||||
compat_sources = compat/fuse_opt.c compat/fuse_opt.h
|
||||
endif
|
||||
|
||||
curlftpfs_SOURCES = ftpfs.c cache.c cache.h $(compat_sources)
|
||||
curlftpfs_CPPFLAGS = -DFUSE_USE_VERSION=25
|
|
@ -0,0 +1,541 @@
|
|||
/*
|
||||
Caching file system proxy
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <glib.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define DEFAULT_CACHE_TIMEOUT 300
|
||||
#define MAX_CACHE_SIZE 10000
|
||||
#define MIN_CACHE_CLEAN_INTERVAL 5
|
||||
#define CACHE_CLEAN_INTERVAL 60
|
||||
|
||||
struct cache {
|
||||
int on;
|
||||
unsigned stat_timeout;
|
||||
unsigned dir_timeout;
|
||||
unsigned link_timeout;
|
||||
struct fuse_cache_operations *next_oper;
|
||||
GHashTable *table;
|
||||
pthread_mutex_t lock;
|
||||
time_t last_cleaned;
|
||||
};
|
||||
|
||||
static struct cache cache;
|
||||
|
||||
struct node {
|
||||
struct stat stat;
|
||||
time_t stat_valid;
|
||||
char **dir;
|
||||
time_t dir_valid;
|
||||
char *link;
|
||||
time_t link_valid;
|
||||
time_t valid;
|
||||
};
|
||||
|
||||
struct fuse_cache_dirhandle {
|
||||
const char *path;
|
||||
fuse_dirh_t h;
|
||||
fuse_dirfil_t filler;
|
||||
GPtrArray *dir;
|
||||
};
|
||||
|
||||
static void free_node(gpointer node_)
|
||||
{
|
||||
struct node *node = (struct node *) node_;
|
||||
g_strfreev(node->dir);
|
||||
g_free(node);
|
||||
}
|
||||
|
||||
static int cache_clean_entry(void *key_, struct node *node, time_t *now)
|
||||
{
|
||||
(void) key_;
|
||||
if (*now > node->valid)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void cache_clean(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
if (now > cache.last_cleaned + MIN_CACHE_CLEAN_INTERVAL &&
|
||||
(g_hash_table_size(cache.table) > MAX_CACHE_SIZE ||
|
||||
now > cache.last_cleaned + CACHE_CLEAN_INTERVAL)) {
|
||||
g_hash_table_foreach_remove(cache.table,
|
||||
(GHRFunc) cache_clean_entry, &now);
|
||||
cache.last_cleaned = now;
|
||||
}
|
||||
}
|
||||
|
||||
static struct node *cache_lookup(const char *path)
|
||||
{
|
||||
return (struct node *) g_hash_table_lookup(cache.table, path);
|
||||
}
|
||||
|
||||
static void cache_purge(const char *path)
|
||||
{
|
||||
g_hash_table_remove(cache.table, path);
|
||||
}
|
||||
|
||||
static void cache_purge_parent(const char *path)
|
||||
{
|
||||
const char *s = strrchr(path, '/');
|
||||
if (s) {
|
||||
if (s == path)
|
||||
g_hash_table_remove(cache.table, "/");
|
||||
else {
|
||||
char *parent = g_strndup(path, s - path);
|
||||
cache_purge(parent);
|
||||
g_free(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cache_invalidate(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
cache_purge(path);
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static void cache_invalidate_dir(const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
cache_purge(path);
|
||||
cache_purge_parent(path);
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static void cache_do_rename(const char *from, const char *to)
|
||||
{
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
cache_purge(from);
|
||||
cache_purge(to);
|
||||
cache_purge_parent(from);
|
||||
cache_purge_parent(to);
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static struct node *cache_get(const char *path)
|
||||
{
|
||||
struct node *node = cache_lookup(path);
|
||||
if (node == NULL) {
|
||||
char *pathcopy = g_strdup(path);
|
||||
node = g_new0(struct node, 1);
|
||||
g_hash_table_insert(cache.table, pathcopy, node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void cache_add_attr(const char *path, const struct stat *stbuf)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
node->stat = *stbuf;
|
||||
node->stat_valid = time(NULL) + cache.stat_timeout;
|
||||
if (node->stat_valid > node->valid)
|
||||
node->valid = node->stat_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
void cache_add_dir(const char *path, char **dir)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
g_strfreev(node->dir);
|
||||
node->dir = dir;
|
||||
node->dir_valid = time(NULL) + cache.dir_timeout;
|
||||
if (node->dir_valid > node->valid)
|
||||
node->valid = node->dir_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static size_t my_strnlen(const char *s, size_t maxsize)
|
||||
{
|
||||
const char *p;
|
||||
for (p = s; maxsize && *p; maxsize--, p++);
|
||||
return p - s;
|
||||
}
|
||||
|
||||
void cache_add_link(const char *path, const char *link, size_t size)
|
||||
{
|
||||
struct node *node;
|
||||
time_t now;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_get(path);
|
||||
now = time(NULL);
|
||||
g_free(node->link);
|
||||
node->link = g_strndup(link, my_strnlen(link, size-1));
|
||||
node->link_valid = time(NULL) + cache.link_timeout;
|
||||
if (node->link_valid > node->valid)
|
||||
node->valid = node->link_valid;
|
||||
cache_clean();
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
}
|
||||
|
||||
static int cache_get_attr(const char *path, struct stat *stbuf)
|
||||
{
|
||||
struct node *node;
|
||||
int err = -EAGAIN;
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_lookup(path);
|
||||
if (node != NULL) {
|
||||
time_t now = time(NULL);
|
||||
if (node->stat_valid - now >= 0) {
|
||||
*stbuf = node->stat;
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_getattr(const char *path, struct stat *stbuf)
|
||||
{
|
||||
int err = cache_get_attr(path, stbuf);
|
||||
if (err) {
|
||||
err = cache.next_oper->oper.getattr(path, stbuf);
|
||||
if (!err)
|
||||
cache_add_attr(path, stbuf);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_readlink(const char *path, char *buf, size_t size)
|
||||
{
|
||||
struct node *node;
|
||||
int err;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_lookup(path);
|
||||
if (node != NULL) {
|
||||
time_t now = time(NULL);
|
||||
if (node->link_valid - now >= 0) {
|
||||
strncpy(buf, node->link, size-1);
|
||||
buf[size-1] = '\0';
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
err = cache.next_oper->oper.readlink(path, buf, size);
|
||||
if (!err)
|
||||
cache_add_link(path, buf, size);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
|
||||
const struct stat *stbuf)
|
||||
{
|
||||
int err = ch->filler(ch->h, name, 0, 0);
|
||||
if (!err) {
|
||||
char *fullpath;
|
||||
g_ptr_array_add(ch->dir, g_strdup(name));
|
||||
fullpath = g_strdup_printf("%s/%s", !ch->path[1] ? "" : ch->path, name);
|
||||
cache_add_attr(fullpath, stbuf);
|
||||
g_free(fullpath);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
|
||||
{
|
||||
struct fuse_cache_dirhandle ch;
|
||||
int err;
|
||||
char **dir;
|
||||
struct node *node;
|
||||
|
||||
pthread_mutex_lock(&cache.lock);
|
||||
node = cache_lookup(path);
|
||||
if (node != NULL && node->dir != NULL) {
|
||||
time_t now = time(NULL);
|
||||
if (node->dir_valid - now >= 0) {
|
||||
for(dir = node->dir; *dir != NULL; dir++)
|
||||
filler(h, *dir, 0, 0);
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&cache.lock);
|
||||
|
||||
ch.path = path;
|
||||
ch.h = h;
|
||||
ch.filler = filler;
|
||||
ch.dir = g_ptr_array_new();
|
||||
err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill);
|
||||
g_ptr_array_add(ch.dir, NULL);
|
||||
dir = (char **) ch.dir->pdata;
|
||||
if (!err)
|
||||
cache_add_dir(path, dir);
|
||||
else
|
||||
g_strfreev(dir);
|
||||
g_ptr_array_free(ch.dir, FALSE);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name,
|
||||
const struct stat *stbuf)
|
||||
{
|
||||
(void) stbuf;
|
||||
return ch->filler(ch->h, name, 0, 0);
|
||||
}
|
||||
|
||||
static int cache_unity_getdir(const char *path, fuse_dirh_t h,
|
||||
fuse_dirfil_t filler)
|
||||
{
|
||||
struct fuse_cache_dirhandle ch;
|
||||
ch.h = h;
|
||||
ch.filler = filler;
|
||||
return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
|
||||
}
|
||||
|
||||
static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
|
||||
{
|
||||
int err = cache.next_oper->oper.mknod(path, mode, rdev);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_mkdir(const char *path, mode_t mode)
|
||||
{
|
||||
int err = cache.next_oper->oper.mkdir(path, mode);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_unlink(const char *path)
|
||||
{
|
||||
int err = cache.next_oper->oper.unlink(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_rmdir(const char *path)
|
||||
{
|
||||
int err = cache.next_oper->oper.rmdir(path);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_symlink(const char *from, const char *to)
|
||||
{
|
||||
int err = cache.next_oper->oper.symlink(from, to);
|
||||
if (!err)
|
||||
cache_invalidate_dir(to);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_rename(const char *from, const char *to)
|
||||
{
|
||||
int err = cache.next_oper->oper.rename(from, to);
|
||||
if (!err)
|
||||
cache_do_rename(from, to);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_link(const char *from, const char *to)
|
||||
{
|
||||
int err = cache.next_oper->oper.link(from, to);
|
||||
if (!err) {
|
||||
cache_invalidate(from);
|
||||
cache_invalidate_dir(to);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chmod(const char *path, mode_t mode)
|
||||
{
|
||||
int err = cache.next_oper->oper.chmod(path, mode);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_chown(const char *path, uid_t uid, gid_t gid)
|
||||
{
|
||||
int err = cache.next_oper->oper.chown(path, uid, gid);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_truncate(const char *path, off_t size)
|
||||
{
|
||||
int err = cache.next_oper->oper.truncate(path, size);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_utime(const char *path, struct utimbuf *buf)
|
||||
{
|
||||
int err = cache.next_oper->oper.utime(path, buf);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_write(const char *path, const char *buf, size_t size,
|
||||
off_t offset, struct fuse_file_info *fi)
|
||||
{
|
||||
int res = cache.next_oper->oper.write(path, buf, size, offset, fi);
|
||||
if (res >= 0)
|
||||
cache_invalidate(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
#if FUSE_VERSION >= 25
|
||||
static int cache_create(const char *path, mode_t mode,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.create(path, mode, fi);
|
||||
if (!err)
|
||||
cache_invalidate_dir(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_ftruncate(const char *path, off_t size,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache.next_oper->oper.ftruncate(path, size, fi);
|
||||
if (!err)
|
||||
cache_invalidate(path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int cache_fgetattr(const char *path, struct stat *stbuf,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
int err = cache_get_attr(path, stbuf);
|
||||
if (err) {
|
||||
err = cache.next_oper->oper.fgetattr(path, stbuf, fi);
|
||||
if (!err)
|
||||
cache_add_attr(path, stbuf);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void cache_unity_fill(struct fuse_cache_operations *oper,
|
||||
struct fuse_operations *cache_oper)
|
||||
{
|
||||
#if FUSE_VERSION >= 23
|
||||
cache_oper->init = oper->oper.init;
|
||||
#endif
|
||||
cache_oper->getattr = oper->oper.getattr;
|
||||
cache_oper->readlink = oper->oper.readlink;
|
||||
cache_oper->getdir = cache_unity_getdir;
|
||||
cache_oper->mknod = oper->oper.mknod;
|
||||
cache_oper->mkdir = oper->oper.mkdir;
|
||||
cache_oper->symlink = oper->oper.symlink;
|
||||
cache_oper->unlink = oper->oper.unlink;
|
||||
cache_oper->rmdir = oper->oper.rmdir;
|
||||
cache_oper->rename = oper->oper.rename;
|
||||
cache_oper->link = oper->oper.link;
|
||||
cache_oper->chmod = oper->oper.chmod;
|
||||
cache_oper->chown = oper->oper.chown;
|
||||
cache_oper->truncate = oper->oper.truncate;
|
||||
cache_oper->utime = oper->oper.utime;
|
||||
cache_oper->open = oper->oper.open;
|
||||
cache_oper->read = oper->oper.read;
|
||||
cache_oper->write = oper->oper.write;
|
||||
cache_oper->flush = oper->oper.flush;
|
||||
cache_oper->release = oper->oper.release;
|
||||
cache_oper->fsync = oper->oper.fsync;
|
||||
cache_oper->statfs = oper->oper.statfs;
|
||||
cache_oper->setxattr = oper->oper.setxattr;
|
||||
cache_oper->getxattr = oper->oper.getxattr;
|
||||
cache_oper->listxattr = oper->oper.listxattr;
|
||||
cache_oper->removexattr = oper->oper.removexattr;
|
||||
#if FUSE_VERSION >= 25
|
||||
cache_oper->create = oper->oper.create;
|
||||
cache_oper->ftruncate = oper->oper.ftruncate;
|
||||
cache_oper->fgetattr = oper->oper.fgetattr;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
|
||||
{
|
||||
static struct fuse_operations cache_oper;
|
||||
cache.next_oper = oper;
|
||||
|
||||
cache_unity_fill(oper, &cache_oper);
|
||||
if (cache.on) {
|
||||
cache_oper.getattr = oper->oper.getattr ? cache_getattr : NULL;
|
||||
cache_oper.readlink = oper->oper.readlink ? cache_readlink : NULL;
|
||||
cache_oper.getdir = oper->cache_getdir ? cache_getdir : NULL;
|
||||
cache_oper.mknod = oper->oper.mknod ? cache_mknod : NULL;
|
||||
cache_oper.mkdir = oper->oper.mkdir ? cache_mkdir : NULL;
|
||||
cache_oper.symlink = oper->oper.symlink ? cache_symlink : NULL;
|
||||
cache_oper.unlink = oper->oper.unlink ? cache_unlink : NULL;
|
||||
cache_oper.rmdir = oper->oper.rmdir ? cache_rmdir : NULL;
|
||||
cache_oper.rename = oper->oper.rename ? cache_rename : NULL;
|
||||
cache_oper.link = oper->oper.link ? cache_link : NULL;
|
||||
cache_oper.chmod = oper->oper.chmod ? cache_chmod : NULL;
|
||||
cache_oper.chown = oper->oper.chown ? cache_chown : NULL;
|
||||
cache_oper.truncate = oper->oper.truncate ? cache_truncate : NULL;
|
||||
cache_oper.utime = oper->oper.utime ? cache_utime : NULL;
|
||||
cache_oper.write = oper->oper.write ? cache_write : NULL;
|
||||
#if FUSE_VERSION >= 25
|
||||
cache_oper.create = oper->oper.create ? cache_create : NULL;
|
||||
cache_oper.ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL;
|
||||
cache_oper.fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL;
|
||||
#endif
|
||||
pthread_mutex_init(&cache.lock, NULL);
|
||||
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||
free_node);
|
||||
if (cache.table == NULL) {
|
||||
fprintf(stderr, "failed to create cache\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return &cache_oper;
|
||||
}
|
||||
|
||||
static const struct fuse_opt cache_opts[] = {
|
||||
{ "cache=yes", offsetof(struct cache, on), 1 },
|
||||
{ "cache=no", offsetof(struct cache, on), 0 },
|
||||
{ "cache_timeout=%u", offsetof(struct cache, stat_timeout), 0 },
|
||||
{ "cache_timeout=%u", offsetof(struct cache, dir_timeout), 0 },
|
||||
{ "cache_timeout=%u", offsetof(struct cache, link_timeout), 0 },
|
||||
{ "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout), 0 },
|
||||
{ "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout), 0 },
|
||||
{ "cache_link_timeout=%u", offsetof(struct cache, link_timeout), 0 },
|
||||
FUSE_OPT_END
|
||||
};
|
||||
|
||||
int cache_parse_options(struct fuse_args *args)
|
||||
{
|
||||
cache.stat_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
cache.dir_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
cache.link_timeout = DEFAULT_CACHE_TIMEOUT;
|
||||
cache.on = 1;
|
||||
|
||||
return fuse_opt_parse(args, &cache, cache_opts, NULL);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Caching file system proxy
|
||||
Copyright (C) 2004 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include <fuse.h>
|
||||
#include <fuse_opt.h>
|
||||
|
||||
#ifndef FUSE_VERSION
|
||||
#define FUSE_VERSION (FUSE_MAJOR_VERSION * 10 + FUSE_MINOR_VERSION)
|
||||
#endif
|
||||
|
||||
typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t;
|
||||
typedef int (*fuse_cache_dirfil_t) (fuse_cache_dirh_t h, const char *name,
|
||||
const struct stat *stbuf);
|
||||
|
||||
struct fuse_cache_operations {
|
||||
struct fuse_operations oper;
|
||||
int (*cache_getdir) (const char *, fuse_cache_dirh_t, fuse_cache_dirfil_t);
|
||||
};
|
||||
|
||||
struct fuse_operations *cache_init(struct fuse_cache_operations *oper);
|
||||
int cache_parse_options(struct fuse_args *args);
|
||||
void cache_add_attr(const char *path, const struct stat *stbuf);
|
||||
void cache_add_dir(const char *path, char **dir);
|
||||
void cache_add_link(const char *path, const char *link, size_t size);
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPL.
|
||||
See the file COPYING.LIB
|
||||
*/
|
||||
|
||||
#include "fuse_opt.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
struct fuse_opt_context {
|
||||
void *data;
|
||||
const struct fuse_opt *opt;
|
||||
fuse_opt_proc_t proc;
|
||||
int argctr;
|
||||
int argc;
|
||||
char **argv;
|
||||
struct fuse_args outargs;
|
||||
char *opts;
|
||||
int nonopt;
|
||||
};
|
||||
|
||||
void fuse_opt_free_args(struct fuse_args *args)
|
||||
{
|
||||
if (args && args->argv && args->allocated) {
|
||||
int i;
|
||||
for (i = 0; i < args->argc; i++)
|
||||
free(args->argv[i]);
|
||||
free(args->argv);
|
||||
args->argv = NULL;
|
||||
args->allocated = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int alloc_failed(void)
|
||||
{
|
||||
fprintf(stderr, "fuse: memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
|
||||
{
|
||||
char **newargv;
|
||||
char *newarg;
|
||||
|
||||
assert(!args->argv || args->allocated);
|
||||
|
||||
newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
|
||||
newarg = newargv ? strdup(arg) : NULL;
|
||||
if (!newargv || !newarg)
|
||||
return alloc_failed();
|
||||
|
||||
args->argv = newargv;
|
||||
args->allocated = 1;
|
||||
args->argv[args->argc++] = newarg;
|
||||
args->argv[args->argc] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int next_arg(struct fuse_opt_context *ctx, const char *opt)
|
||||
{
|
||||
if (ctx->argctr + 1 >= ctx->argc) {
|
||||
fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
|
||||
return -1;
|
||||
}
|
||||
ctx->argctr++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_arg(struct fuse_opt_context *ctx, const char *arg)
|
||||
{
|
||||
return fuse_opt_add_arg(&ctx->outargs, arg);
|
||||
}
|
||||
|
||||
int fuse_opt_add_opt(char **opts, const char *opt)
|
||||
{
|
||||
char *newopts;
|
||||
if (!*opts)
|
||||
newopts = strdup(opt);
|
||||
else {
|
||||
unsigned oldlen = strlen(*opts);
|
||||
newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1);
|
||||
if (newopts) {
|
||||
newopts[oldlen] = ',';
|
||||
strcpy(newopts + oldlen + 1, opt);
|
||||
}
|
||||
}
|
||||
if (!newopts)
|
||||
return alloc_failed();
|
||||
|
||||
*opts = newopts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_opt(struct fuse_opt_context *ctx, const char *opt)
|
||||
{
|
||||
return fuse_opt_add_opt(&ctx->opts, opt);
|
||||
}
|
||||
|
||||
static int insert_arg(struct fuse_opt_context *ctx, int pos, const char *arg)
|
||||
{
|
||||
assert(pos <= ctx->outargs.argc);
|
||||
if (add_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
|
||||
if (pos != ctx->outargs.argc - 1) {
|
||||
char *newarg = ctx->outargs.argv[ctx->outargs.argc - 1];
|
||||
memmove(&ctx->outargs.argv[pos + 1], &ctx->outargs.argv[pos],
|
||||
sizeof(char *) * (ctx->outargs.argc - pos - 1));
|
||||
ctx->outargs.argv[pos] = newarg;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
|
||||
int iso)
|
||||
{
|
||||
if (ctx->proc) {
|
||||
int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
|
||||
if (res == -1 || !res)
|
||||
return res;
|
||||
}
|
||||
if (iso)
|
||||
return add_opt(ctx, arg);
|
||||
else
|
||||
return add_arg(ctx, arg);
|
||||
}
|
||||
|
||||
static int match_template(const char *t, const char *arg, unsigned *sepp)
|
||||
{
|
||||
int arglen = strlen(arg);
|
||||
const char *sep = strchr(t, '=');
|
||||
sep = sep ? sep : strchr(t, ' ');
|
||||
if (sep && (!sep[1] || sep[1] == '%')) {
|
||||
int tlen = sep - t;
|
||||
if (sep[0] == '=')
|
||||
tlen ++;
|
||||
if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
|
||||
*sepp = sep - t;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (strcmp(t, arg) == 0) {
|
||||
*sepp = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
|
||||
const char *arg, unsigned *sepp)
|
||||
{
|
||||
for (; opt && opt->template_opt; opt++)
|
||||
if (match_template(opt->template_opt, arg, sepp))
|
||||
return opt;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
|
||||
{
|
||||
unsigned dummy;
|
||||
return find_opt(opts, opt, &dummy) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int process_opt_param(void *var, const char *format, const char *param,
|
||||
const char *arg)
|
||||
{
|
||||
assert(format[0] == '%');
|
||||
if (format[1] == 's') {
|
||||
char *copy = strdup(param);
|
||||
if (!copy)
|
||||
return alloc_failed();
|
||||
|
||||
*(char **) var = copy;
|
||||
} else {
|
||||
if (sscanf(param, format, var) != 1) {
|
||||
fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_opt(struct fuse_opt_context *ctx,
|
||||
const struct fuse_opt *opt, unsigned sep,
|
||||
const char *arg, int iso)
|
||||
{
|
||||
if (opt->offset == -1U) {
|
||||
if (call_proc(ctx, arg, opt->value, iso) == -1)
|
||||
return -1;
|
||||
} else {
|
||||
void *var = ctx->data + opt->offset;
|
||||
if (sep && opt->template_opt[sep + 1]) {
|
||||
const char *param = arg + sep;
|
||||
if (opt->template_opt[sep] == '=')
|
||||
param ++;
|
||||
if (process_opt_param(var, opt->template_opt + sep + 1,
|
||||
param, arg) == -1)
|
||||
return -1;
|
||||
} else
|
||||
*(int *)var = opt->value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_opt_sep_arg(struct fuse_opt_context *ctx,
|
||||
const struct fuse_opt *opt, unsigned sep,
|
||||
const char *arg, int iso)
|
||||
{
|
||||
int res;
|
||||
char *newarg;
|
||||
char *param;
|
||||
|
||||
if (next_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
|
||||
param = ctx->argv[ctx->argctr];
|
||||
newarg = malloc(sep + strlen(param) + 1);
|
||||
if (!newarg)
|
||||
return alloc_failed();
|
||||
|
||||
memcpy(newarg, arg, sep);
|
||||
strcpy(newarg + sep, param);
|
||||
res = process_opt(ctx, opt, sep, newarg, iso);
|
||||
free(newarg);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
|
||||
{
|
||||
unsigned sep;
|
||||
const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
|
||||
if (opt) {
|
||||
for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
|
||||
int res;
|
||||
if (sep && opt->template_opt[sep] == ' ' && !arg[sep])
|
||||
res = process_opt_sep_arg(ctx, opt, sep, arg, iso);
|
||||
else
|
||||
res = process_opt(ctx, opt, sep, arg, iso);
|
||||
if (res == -1)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
} else
|
||||
return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
|
||||
}
|
||||
|
||||
static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
|
||||
{
|
||||
char *sep;
|
||||
|
||||
do {
|
||||
int res;
|
||||
sep = strchr(opts, ',');
|
||||
if (sep)
|
||||
*sep = '\0';
|
||||
res = process_gopt(ctx, opts, 1);
|
||||
if (res == -1)
|
||||
return -1;
|
||||
opts = sep + 1;
|
||||
} while (sep);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
|
||||
{
|
||||
int res;
|
||||
char *copy;
|
||||
const char *sep = strchr(opts, ',');
|
||||
if (!sep)
|
||||
return process_gopt(ctx, opts, 1);
|
||||
|
||||
copy = strdup(opts);
|
||||
if (!copy) {
|
||||
fprintf(stderr, "fuse: memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
res = process_real_option_group(ctx, copy);
|
||||
free(copy);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int process_one(struct fuse_opt_context *ctx, const char *arg)
|
||||
{
|
||||
if (ctx->nonopt || arg[0] != '-')
|
||||
return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
|
||||
else if (arg[1] == 'o') {
|
||||
if (arg[2])
|
||||
return process_option_group(ctx, arg + 2);
|
||||
else {
|
||||
if (next_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
|
||||
return process_option_group(ctx, ctx->argv[ctx->argctr]);
|
||||
}
|
||||
} else if (arg[1] == '-' && !arg[2]) {
|
||||
if (add_arg(ctx, arg) == -1)
|
||||
return -1;
|
||||
ctx->nonopt = ctx->outargs.argc;
|
||||
return 0;
|
||||
} else
|
||||
return process_gopt(ctx, arg, 0);
|
||||
}
|
||||
|
||||
static int opt_parse(struct fuse_opt_context *ctx)
|
||||
{
|
||||
if (ctx->argc) {
|
||||
if (add_arg(ctx, ctx->argv[0]) == -1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
|
||||
if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
|
||||
return -1;
|
||||
|
||||
if (ctx->opts) {
|
||||
if (insert_arg(ctx, 1, "-o") == -1 ||
|
||||
insert_arg(ctx, 2, ctx->opts) == -1)
|
||||
return -1;
|
||||
}
|
||||
if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc)
|
||||
ctx->outargs.argv[--ctx->outargs.argc] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_opt_parse(struct fuse_args *args, void *data,
|
||||
const struct fuse_opt opts[], fuse_opt_proc_t proc)
|
||||
{
|
||||
int res;
|
||||
struct fuse_opt_context ctx = {
|
||||
.data = data,
|
||||
.opt = opts,
|
||||
.proc = proc,
|
||||
};
|
||||
|
||||
if (!args || !args->argv || !args->argc)
|
||||
return 0;
|
||||
|
||||
ctx.argc = args->argc;
|
||||
ctx.argv = args->argv;
|
||||
|
||||
res = opt_parse(&ctx);
|
||||
if (res != -1) {
|
||||
struct fuse_args tmp = *args;
|
||||
*args = ctx.outargs;
|
||||
ctx.outargs = tmp;
|
||||
}
|
||||
free(ctx.opts);
|
||||
fuse_opt_free_args(&ctx.outargs);
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#ifndef _FUSE_OPT_H_
|
||||
#define _FUSE_OPT_H_
|
||||
|
||||
/* This file defines the option parsing interface of FUSE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Option description
|
||||
*
|
||||
* This structure describes a single option, and and action associated
|
||||
* with it, in case it matches.
|
||||
*
|
||||
* More than one such match may occur, in which case the action for
|
||||
* each match is executed.
|
||||
*
|
||||
* There are three possible actions in case of a match:
|
||||
*
|
||||
* i) An integer (int or unsigned) variable determined by 'offset' is
|
||||
* set to 'value'
|
||||
*
|
||||
* ii) The processing function is called, with 'value' as the key
|
||||
*
|
||||
* iii) An integer (any) or string (char *) variable determined by
|
||||
* 'offset' is set to the value of an option parameter
|
||||
*
|
||||
* 'offset' should normally be either set to
|
||||
*
|
||||
* - 'offsetof(struct foo, member)' actions i) and iii)
|
||||
*
|
||||
* - -1 action ii)
|
||||
*
|
||||
* The 'offsetof()' macro is defined in the <stddef.h> header.
|
||||
*
|
||||
* The template determines which options match, and also have an
|
||||
* effect on the action. Normally the action is either i) or ii), but
|
||||
* if a format is present in the template, then action iii) is
|
||||
* performed.
|
||||
*
|
||||
* The types of templates are:
|
||||
*
|
||||
* 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only
|
||||
* themselves. Invalid values are "--" and anything beginning
|
||||
* with "-o"
|
||||
*
|
||||
* 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or
|
||||
* the relevant option in a comma separated option list
|
||||
*
|
||||
* 3) "bar=", "--foo=", etc. These are variations of 1) and 2)
|
||||
* which have a parameter
|
||||
*
|
||||
* 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform
|
||||
* action iii).
|
||||
*
|
||||
* 5) "-x ", etc. Matches either "-xparam" or "-x param" as
|
||||
* two separate arguments
|
||||
*
|
||||
* 6) "-x %s", etc. Combination of 4) and 5)
|
||||
*
|
||||
* If the format is "%s", memory is allocated for the string unlike
|
||||
* with scanf().
|
||||
*/
|
||||
struct fuse_opt {
|
||||
/** Matching template and optional parameter formatting */
|
||||
const char *template_opt;
|
||||
|
||||
/**
|
||||
* Offset of variable within 'data' parameter of fuse_opt_parse()
|
||||
* or -1
|
||||
*/
|
||||
unsigned long offset;
|
||||
|
||||
/**
|
||||
* Value to set the variable to, or to be passed as 'key' to the
|
||||
* processing function. Ignored if template a format
|
||||
*/
|
||||
int value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Key option. In case of a match, the processing function will be
|
||||
* called with the specified key.
|
||||
*/
|
||||
#define FUSE_OPT_KEY(template_opt, key) { template_opt, -1U, key }
|
||||
|
||||
/**
|
||||
* Last option. An array of 'struct fuse_opt' must end with a NULL
|
||||
* template value
|
||||
*/
|
||||
#define FUSE_OPT_END { .template_opt = NULL }
|
||||
|
||||
/**
|
||||
* Argument list
|
||||
*/
|
||||
struct fuse_args {
|
||||
/** Argument count */
|
||||
int argc;
|
||||
|
||||
/** Argument vector. NULL terminated */
|
||||
char **argv;
|
||||
|
||||
/** Is 'argv' allocated? */
|
||||
int allocated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializer for 'struct fuse_args'
|
||||
*/
|
||||
#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
|
||||
|
||||
/**
|
||||
* Key value passed to the processing function if an option did not
|
||||
* match any templated
|
||||
*/
|
||||
#define FUSE_OPT_KEY_OPT -1
|
||||
|
||||
/**
|
||||
* Key value passed to the processing function for all non-options
|
||||
*
|
||||
* Non-options are the arguments beginning with a charater other than
|
||||
* '-' or all arguments after the special '--' option
|
||||
*/
|
||||
#define FUSE_OPT_KEY_NONOPT -2
|
||||
|
||||
/**
|
||||
* Processing function
|
||||
*
|
||||
* This function is called if
|
||||
* - option did not match any 'struct fuse_opt'
|
||||
* - argument is a non-option
|
||||
* - option did match and offset was set to -1
|
||||
*
|
||||
* The 'arg' parameter will always contain the whole argument or
|
||||
* option including the parameter if exists. A two-argument option
|
||||
* ("-x foo") is always converted to single arguemnt option of the
|
||||
* form "-xfoo" before this function is called.
|
||||
*
|
||||
* Options of the form '-ofoo' are passed to this function without the
|
||||
* '-o' prefix.
|
||||
*
|
||||
* The return value of this function determines whether this argument
|
||||
* is to be inserted into the output argument vector, or discarded.
|
||||
*
|
||||
* @param data is the user data passed to the fuse_opt_parse() function
|
||||
* @param arg is the whole argument or option
|
||||
* @param key determines why the processing function was called
|
||||
* @param outargs the current output argument list
|
||||
* @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept
|
||||
*/
|
||||
typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
|
||||
struct fuse_args *outargs);
|
||||
|
||||
/**
|
||||
* Option parsing function
|
||||
*
|
||||
* If 'args' was returned from a previous call to fuse_opt_parse() or
|
||||
* it was constructed from
|
||||
*
|
||||
* A NULL 'args' is equivalent to an empty argument vector
|
||||
*
|
||||
* A NULL 'opts' is equivalent to an 'opts' array containing a single
|
||||
* end marker
|
||||
*
|
||||
* A NULL 'proc' is equivalent to a processing function always
|
||||
* returning '1'
|
||||
*
|
||||
* @param args is the input and output argument list
|
||||
* @param data is the user data
|
||||
* @param opts is the option description array
|
||||
* @param proc is the processing function
|
||||
* @return -1 on error, 0 on success
|
||||
*/
|
||||
int fuse_opt_parse(struct fuse_args *args, void *data,
|
||||
const struct fuse_opt opts[], fuse_opt_proc_t proc);
|
||||
|
||||
/**
|
||||
* Add an option to a comma separated option list
|
||||
*
|
||||
* @param opts is a pointer to an option list, may point to a NULL value
|
||||
* @param opt is the option to add
|
||||
* @return -1 on allocation error, 0 on success
|
||||
*/
|
||||
int fuse_opt_add_opt(char **opts, const char *opt);
|
||||
|
||||
/**
|
||||
* Add an argument to a NULL terminated argument vector
|
||||
*
|
||||
* @param args is the structure containing the current argument list
|
||||
* @param arg is the new argument to add
|
||||
* @return -1 on allocation error, 0 on success
|
||||
*/
|
||||
int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
|
||||
|
||||
/**
|
||||
* Free the contents of argument list
|
||||
*
|
||||
* The structure itself is not freed
|
||||
*
|
||||
* @param args is the structure containing the argument list
|
||||
*/
|
||||
void fuse_opt_free_args(struct fuse_args *args);
|
||||
|
||||
|
||||
/**
|
||||
* Check if an option matches
|
||||
*
|
||||
* @param opts is the option description array
|
||||
* @param opt is the option to match
|
||||
* @return 1 if a match is found, 0 if not
|
||||
*/
|
||||
int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _FUSE_OPT_H_ */
|
|
@ -0,0 +1,18 @@
|
|||
AC_INIT(ftpfs-fuse, 1.4)
|
||||
AM_INIT_AUTOMAKE
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
|
||||
AC_PROG_CC
|
||||
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PKG_CHECK_MODULES(FTPFS, [fuse >= 2.2 glib-2.0 libcurl >= 0.17])
|
||||
CFLAGS="$CFLAGS -Wall -W -D_REENTRANT $FTPFS_CFLAGS"
|
||||
LIBS="$FTPFS_LIBS"
|
||||
have_fuse_opt_parse=no
|
||||
AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes])
|
||||
if test "$have_fuse_opt_parse" = no; then
|
||||
CFLAGS="$CFLAGS -Icompat"
|
||||
fi
|
||||
AM_CONDITIONAL(FUSE_OPT_COMPAT, test "$have_fuse_opt_parse" = no)
|
||||
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
|
@ -0,0 +1,927 @@
|
|||
/*
|
||||
FTP file system
|
||||
Copyright (C) 2006 Robson Braga Araujo <robsonbraga@gmail.com>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <fuse.h>
|
||||
#include <fuse_opt.h>
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include "cache.h"
|
||||
|
||||
static char* MonthStrings[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
|
||||
struct ftpfs {
|
||||
char* host;
|
||||
char* mountpoint;
|
||||
CURL* connection;
|
||||
GHashTable *filetab;
|
||||
int verbose;
|
||||
int debug;
|
||||
int transform_symlinks;
|
||||
char symlink_prefix[PATH_MAX+1];
|
||||
size_t symlink_prefix_len;
|
||||
};
|
||||
|
||||
static struct ftpfs ftpfs;
|
||||
static char error_buf[CURL_ERROR_SIZE];
|
||||
|
||||
struct buffer {
|
||||
uint8_t* p;
|
||||
size_t len;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static char* get_dir_path(const char* path, int strip);
|
||||
static int parse_dir(struct buffer* buf, const char* dir,
|
||||
const char* name, struct stat* sbuf,
|
||||
char* linkbuf, int linklen,
|
||||
fuse_cache_dirh_t h, fuse_cache_dirfil_t filler);
|
||||
|
||||
#define DEBUG(args...) \
|
||||
do { if (ftpfs.debug) fprintf(stderr, args); } while(0)
|
||||
|
||||
static inline void buf_init(struct buffer* buf, size_t size)
|
||||
{
|
||||
if (size) {
|
||||
buf->p = (uint8_t*) malloc(size);
|
||||
if (!buf->p) {
|
||||
fprintf(stderr, "ftpfs: memory allocation failed\n");
|
||||
exit(1);
|
||||
}
|
||||
} else
|
||||
buf->p = NULL;
|
||||
buf->len = 0;
|
||||
buf->size = size;
|
||||
}
|
||||
|
||||
static inline void buf_free(struct buffer* buf)
|
||||
{
|
||||
free(buf->p);
|
||||
}
|
||||
|
||||
static inline void buf_finish(struct buffer *buf)
|
||||
{
|
||||
buf->len = buf->size;
|
||||
}
|
||||
|
||||
|
||||
static inline void buf_clear(struct buffer *buf)
|
||||
{
|
||||
buf_free(buf);
|
||||
buf_init(buf, 0);
|
||||
}
|
||||
|
||||
static void buf_resize(struct buffer *buf, size_t len)
|
||||
{
|
||||
buf->size = (buf->len + len + 63) & ~31;
|
||||
buf->p = (uint8_t *) realloc(buf->p, buf->size);
|
||||
if (!buf->p) {
|
||||
fprintf(stderr, "ftpfs: memory allocation failed\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void buf_check_add(struct buffer *buf, size_t len)
|
||||
{
|
||||
if (buf->len + len > buf->size)
|
||||
buf_resize(buf, len);
|
||||
}
|
||||
|
||||
#define _buf_add_mem(b, d, l) \
|
||||
buf_check_add(b, l); \
|
||||
memcpy(b->p + b->len, d, l); \
|
||||
b->len += l;
|
||||
|
||||
|
||||
static inline void buf_add_mem(struct buffer *buf, const void *data,
|
||||
size_t len)
|
||||
{
|
||||
_buf_add_mem(buf, data, len);
|
||||
}
|
||||
|
||||
static inline void buf_add_buf(struct buffer *buf, const struct buffer *bufa)
|
||||
{
|
||||
_buf_add_mem(buf, bufa->p, bufa->len);
|
||||
}
|
||||
|
||||
static inline void buf_add_uint8(struct buffer *buf, uint8_t val)
|
||||
{
|
||||
_buf_add_mem(buf, &val, 1);
|
||||
}
|
||||
|
||||
static inline void buf_add_uint32(struct buffer *buf, uint32_t val)
|
||||
{
|
||||
uint32_t nval = htonl(val);
|
||||
_buf_add_mem(buf, &nval, 4);
|
||||
}
|
||||
|
||||
static inline void buf_add_uint64(struct buffer *buf, uint64_t val)
|
||||
{
|
||||
buf_add_uint32(buf, val >> 32);
|
||||
buf_add_uint32(buf, val & 0xffffffff);
|
||||
}
|
||||
|
||||
static inline void buf_add_data(struct buffer *buf, const struct buffer *data)
|
||||
{
|
||||
buf_add_uint32(buf, data->len);
|
||||
buf_add_mem(buf, data->p, data->len);
|
||||
}
|
||||
|
||||
static inline void buf_add_string(struct buffer *buf, const char *str)
|
||||
{
|
||||
struct buffer data;
|
||||
data.p = (uint8_t *) str;
|
||||
data.len = strlen(str);
|
||||
buf_add_data(buf, &data);
|
||||
}
|
||||
|
||||
static int buf_check_get(struct buffer *buf, size_t len)
|
||||
{
|
||||
if (buf->len + len > buf->size) {
|
||||
fprintf(stderr, "buffer too short\n");
|
||||
return -1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int buf_get_mem(struct buffer *buf, void *data, size_t len)
|
||||
{
|
||||
if (buf_check_get(buf, len) == -1)
|
||||
return -1;
|
||||
memcpy(data, buf->p + buf->len, len);
|
||||
buf->len += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int buf_get_uint8(struct buffer *buf, uint8_t *val)
|
||||
{
|
||||
return buf_get_mem(buf, val, 1);
|
||||
}
|
||||
|
||||
static inline int buf_get_uint32(struct buffer *buf, uint32_t *val)
|
||||
{
|
||||
uint32_t nval;
|
||||
if (buf_get_mem(buf, &nval, 4) == -1)
|
||||
return -1;
|
||||
*val = ntohl(nval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int buf_get_uint64(struct buffer *buf, uint64_t *val)
|
||||
{
|
||||
uint32_t val1;
|
||||
uint32_t val2;
|
||||
if (buf_get_uint32(buf, &val1) == -1 || buf_get_uint32(buf, &val2) == -1)
|
||||
return -1;
|
||||
*val = ((uint64_t) val1 << 32) + val2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int buf_get_data(struct buffer *buf, struct buffer *data)
|
||||
{
|
||||
uint32_t len;
|
||||
if (buf_get_uint32(buf, &len) == -1 || len > buf->size - buf->len)
|
||||
return -1;
|
||||
buf_init(data, len + 1);
|
||||
data->size = len;
|
||||
if (buf_get_mem(buf, data->p, data->size) == -1) {
|
||||
buf_free(data);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int buf_get_string(struct buffer *buf, char **str)
|
||||
{
|
||||
struct buffer data;
|
||||
if (buf_get_data(buf, &data) == -1)
|
||||
return -1;
|
||||
data.p[data.size] = '\0';
|
||||
*str = (char *) data.p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ftpfs_file {
|
||||
struct buffer buf;
|
||||
int dirty;
|
||||
int copied;
|
||||
};
|
||||
|
||||
#define FTPFS_OPT(t, p, v) { t, offsetof(struct ftpfs, p), v }
|
||||
|
||||
static struct fuse_opt ftpfs_opts[] = {
|
||||
FTPFS_OPT("-v", verbose, 1),
|
||||
FTPFS_OPT("ftpfs_debug", debug, 1),
|
||||
FTPFS_OPT("transform_symlinks", transform_symlinks, 1),
|
||||
};
|
||||
|
||||
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *data) {
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*)data;
|
||||
if (fh == NULL) return 0;
|
||||
unsigned to_copy = size * nmemb;
|
||||
if (to_copy > fh->buf.len - fh->copied) {
|
||||
to_copy = fh->buf.len - fh->copied;
|
||||
}
|
||||
DEBUG("write_data: %d\n", to_copy);
|
||||
memcpy(ptr, fh->buf.p + fh->copied, to_copy);
|
||||
fh->copied += to_copy;
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
static size_t read_data(void *ptr, size_t size, size_t nmemb, void *data) {
|
||||
struct buffer* buf = (struct buffer*)data;
|
||||
if (buf == NULL) return 0;
|
||||
buf_add_mem(buf, ptr, size * nmemb);
|
||||
DEBUG("read_data: %d\n", size * nmemb);
|
||||
return size*nmemb;
|
||||
}
|
||||
|
||||
static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h,
|
||||
fuse_cache_dirfil_t filler) {
|
||||
int err;
|
||||
CURLcode curl_res;
|
||||
char* dir_path = get_dir_path(path, 0);
|
||||
|
||||
DEBUG("ftpfs_getdir: %s\n", dir_path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, dir_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
|
||||
curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
DEBUG("%s\n", error_buf);
|
||||
}
|
||||
buf_add_mem(&buf, "\0", 1);
|
||||
|
||||
err = parse_dir(&buf, dir_path + strlen(ftpfs.host) - 1, NULL, NULL, NULL, 0, h, filler);
|
||||
|
||||
free(dir_path);
|
||||
buf_free(&buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char* get_dir_path(const char* path, int strip) {
|
||||
char *ret;
|
||||
const char *lastdir;
|
||||
|
||||
++path;
|
||||
|
||||
if (strip) {
|
||||
lastdir = strrchr(path, '/');
|
||||
if (lastdir == NULL) lastdir = path;
|
||||
} else {
|
||||
lastdir = path + strlen(path);
|
||||
}
|
||||
|
||||
ret = g_strdup_printf("%s%.*s%s", ftpfs.host, lastdir - path, path,
|
||||
lastdir - path ? "/" : "");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_dir(struct buffer* buf, const char* dir,
|
||||
const char* name, struct stat* sbuf,
|
||||
char* linkbuf, int linklen,
|
||||
fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) {
|
||||
char *start = buf->p;
|
||||
char *end = buf->p;
|
||||
char found = 0;
|
||||
|
||||
if (sbuf) memset(sbuf, 0, sizeof(struct stat));
|
||||
|
||||
if (name && sbuf && name[0] == '\0') {
|
||||
sbuf->st_mode |= S_IFDIR;
|
||||
sbuf->st_mode |= 0755;
|
||||
sbuf->st_size = 1024;
|
||||
return 0;
|
||||
}
|
||||
|
||||
while ((end = strchr(start, '\n')) != NULL)
|
||||
{
|
||||
char* line;
|
||||
char* file;
|
||||
struct stat stat_buf;
|
||||
memset(&stat_buf, 0, sizeof(stat_buf));
|
||||
|
||||
if (end > start && *(end-1) == '\r') end--;
|
||||
|
||||
line = (char*)malloc(end - start + 1);
|
||||
strncpy(line, start, end - start);
|
||||
line[end - start] = '\0';
|
||||
|
||||
// Symbolic links
|
||||
char* link = strstr(line, " -> ");
|
||||
|
||||
if (link) {
|
||||
file = link;
|
||||
--file;
|
||||
while (!isspace(*file)) --file;
|
||||
++file;
|
||||
file = g_strndup(file, link - file);
|
||||
} else {
|
||||
file = strrchr(line, ' ');
|
||||
++file;
|
||||
file = g_strdup(file);
|
||||
}
|
||||
|
||||
char *full_path = g_strdup_printf("%s%s", dir, file);
|
||||
|
||||
if (link) {
|
||||
link += 4;
|
||||
char *reallink;
|
||||
if (link[0] == '/' && ftpfs.symlink_prefix_len) {
|
||||
reallink = g_strdup_printf("%s%s", ftpfs.symlink_prefix, link);
|
||||
} else {
|
||||
reallink = g_strdup(link);
|
||||
}
|
||||
int linksize = strlen(reallink);
|
||||
cache_add_link(full_path, reallink, linksize+1);
|
||||
DEBUG("cache_add_link: %s %s\n", full_path, reallink);
|
||||
if (linkbuf && linklen) {
|
||||
if (linksize > linklen) linksize = linklen - 1;
|
||||
strncpy(linkbuf, reallink, linksize);
|
||||
linkbuf[linksize] = '\0';
|
||||
}
|
||||
free(reallink);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
char *p;
|
||||
if (line[i] == 'd') {
|
||||
stat_buf.st_mode |= S_IFDIR;
|
||||
} else if (line[i] == 'l') {
|
||||
stat_buf.st_mode |= S_IFLNK;
|
||||
} else {
|
||||
stat_buf.st_mode |= S_IFREG;
|
||||
}
|
||||
for (i = 1; i < 10; ++i) {
|
||||
if (line[i] != '-') {
|
||||
stat_buf.st_mode |= 1 << (9 - i);
|
||||
}
|
||||
}
|
||||
|
||||
// Advance whitespace
|
||||
while (line[i] && isspace(line[i])) ++i;
|
||||
|
||||
stat_buf.st_nlink = strtol(line+i, &p, 10);
|
||||
i = p - line;
|
||||
|
||||
// Advance whitespace
|
||||
while (line[i] && isspace(line[i])) ++i;
|
||||
// Advance username
|
||||
while (line[i] && !isspace(line[i])) ++i;
|
||||
// Advance whitespace
|
||||
while (line[i] && isspace(line[i])) ++i;
|
||||
// Advance group
|
||||
while (line[i] && !isspace(line[i])) ++i;
|
||||
|
||||
stat_buf.st_size = strtol(line+i, &p, 10);
|
||||
i = p - line;
|
||||
++i;
|
||||
|
||||
// Date
|
||||
int month;
|
||||
for (month = 0; month < 12; ++month) {
|
||||
if (!strncmp(MonthStrings[month], line+i, 3)) break;
|
||||
}
|
||||
if (month < 12) {
|
||||
i += 3;
|
||||
int day = strtol(line+i, &p, 10);
|
||||
if (p != line+i) {
|
||||
i = p - line;
|
||||
int year_or_hour = strtol(line+i, &p, 10);
|
||||
struct tm current_time;
|
||||
time_t now = time(NULL);
|
||||
localtime_r(&now, ¤t_time);
|
||||
if (p != line+i) {
|
||||
i = p - line;
|
||||
struct tm parsed_time;
|
||||
memset(&parsed_time, 0, sizeof(parsed_time));
|
||||
if (*p == ':') {
|
||||
// Hour
|
||||
++i;
|
||||
int minute = strtol(line+i, &p, 10);
|
||||
parsed_time.tm_mday = day;
|
||||
parsed_time.tm_mon = month;
|
||||
parsed_time.tm_year = current_time.tm_year;
|
||||
parsed_time.tm_hour = year_or_hour;
|
||||
parsed_time.tm_min = minute;
|
||||
stat_buf.st_atime = mktime(&parsed_time);
|
||||
if (stat_buf.st_atime > now) {
|
||||
parsed_time.tm_year--;
|
||||
stat_buf.st_atime = mktime(&parsed_time);
|
||||
}
|
||||
stat_buf.st_mtime = stat_buf.st_atime;
|
||||
stat_buf.st_ctime = stat_buf.st_atime;
|
||||
} else {
|
||||
// Year
|
||||
parsed_time.tm_mday = day;
|
||||
parsed_time.tm_mon = month;
|
||||
parsed_time.tm_year = year_or_hour - 1900;
|
||||
stat_buf.st_atime = mktime(&parsed_time);
|
||||
stat_buf.st_mtime = stat_buf.st_atime;
|
||||
stat_buf.st_ctime = stat_buf.st_atime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (h && filler) {
|
||||
DEBUG("filler: %s\n", file);
|
||||
filler(h, file, &stat_buf);
|
||||
} else {
|
||||
DEBUG("cache_add_attr: %s\n", full_path);
|
||||
cache_add_attr(full_path, &stat_buf);
|
||||
}
|
||||
|
||||
if (name && !strcmp(name, file)) {
|
||||
if (sbuf) *sbuf = stat_buf;
|
||||
found = 1;
|
||||
}
|
||||
|
||||
start = *end == '\r' ? end + 2 : end + 1;
|
||||
free(full_path);
|
||||
free(line);
|
||||
free(file);
|
||||
}
|
||||
|
||||
if (found) return 0;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int ftpfs_getattr(const char* path, struct stat* sbuf) {
|
||||
int err;
|
||||
CURLcode curl_res;
|
||||
char* dir_path = get_dir_path(path, 1);
|
||||
|
||||
DEBUG("dir_path: %s %s\n", path, dir_path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, dir_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
|
||||
curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
DEBUG("%s\n", error_buf);
|
||||
}
|
||||
buf_add_mem(&buf, "\0", 1);
|
||||
|
||||
char* name = strrchr(path, '/');
|
||||
++name;
|
||||
err = parse_dir(&buf, dir_path + strlen(ftpfs.host) - 1, name, sbuf, NULL, 0, NULL, NULL);
|
||||
|
||||
free(dir_path);
|
||||
buf_free(&buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_open(const char* path, struct fuse_file_info* fi) {
|
||||
DEBUG("%d\n", fi->flags & O_ACCMODE);
|
||||
if ((fi->flags & O_ACCMODE) == O_RDONLY) {
|
||||
DEBUG("opening %s O_RDONLY\n", path);
|
||||
} else if ((fi->flags & O_ACCMODE) == O_WRONLY) {
|
||||
DEBUG("opening %s O_WRONLY\n", path);
|
||||
} else if ((fi->flags & O_ACCMODE) == O_RDWR) {
|
||||
DEBUG("opening %s O_RDWR\n", path);
|
||||
}
|
||||
|
||||
char *full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
||||
|
||||
DEBUG("full_path: %s\n", full_path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, full_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
|
||||
int err = 0;
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EACCES;
|
||||
buf_free(&buf);
|
||||
} else {
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*)
|
||||
malloc(sizeof(struct ftpfs_file));
|
||||
fh->buf = buf;
|
||||
fh->dirty = 0;
|
||||
fh->copied = 0;
|
||||
fi->fh = (unsigned long) fh;
|
||||
}
|
||||
|
||||
free(full_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_read(const char* path, char* rbuf, size_t size, off_t offset,
|
||||
struct fuse_file_info* fi) {
|
||||
(void) path;
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
||||
if (offset >= fh->buf.len) return 0;
|
||||
if (size > fh->buf.len - offset) {
|
||||
size = fh->buf.len - offset;
|
||||
}
|
||||
memcpy(rbuf, fh->buf.p + offset, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int ftpfs_mknod(const char* path, mode_t mode, dev_t rdev) {
|
||||
(void) rdev;
|
||||
|
||||
int err = 0;
|
||||
|
||||
if ((mode & S_IFMT) != S_IFREG)
|
||||
return -EPERM;
|
||||
|
||||
char *full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, full_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_INFILESIZE, 0);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_UPLOAD, 1);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_READFUNCTION, write_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_READDATA, NULL);
|
||||
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_UPLOAD, 0);
|
||||
|
||||
free(full_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_chmod(const char* path, mode_t mode) {
|
||||
(void) path;
|
||||
(void) mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) {
|
||||
(void) path;
|
||||
(void) uid;
|
||||
(void) gid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftpfs_truncate(const char* path, off_t offset) {
|
||||
DEBUG("ftpfs_truncate: %lld\n", offset);
|
||||
if (offset == 0) return ftpfs_mknod(path, S_IFREG, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftpfs_utime(const char* path, struct utimbuf* time) {
|
||||
(void) path;
|
||||
(void) time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftpfs_rmdir(const char* path) {
|
||||
int err = 0;
|
||||
struct curl_slist* header = NULL;
|
||||
char *cmd = g_strdup_printf("RMD %s", path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
|
||||
header = curl_slist_append(header, cmd);
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, header);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, NULL);
|
||||
|
||||
buf_free(&buf);
|
||||
curl_slist_free_all(header);
|
||||
free(cmd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_mkdir(const char* path, mode_t mode) {
|
||||
(void) mode;
|
||||
int err = 0;
|
||||
struct curl_slist* header = NULL;
|
||||
char *cmd = g_strdup_printf("MKD %s", path);
|
||||
char *full_path = g_strdup_printf("%s%s/", ftpfs.host, path + 1);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
|
||||
header = curl_slist_append(header, cmd);
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, header);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, full_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, NULL);
|
||||
|
||||
buf_free(&buf);
|
||||
curl_slist_free_all(header);
|
||||
free(cmd);
|
||||
free(full_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_unlink(const char* path) {
|
||||
int err = 0;
|
||||
struct curl_slist* header = NULL;
|
||||
char *cmd = g_strdup_printf("DELE %s", path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
|
||||
header = curl_slist_append(header, cmd);
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, header);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, NULL);
|
||||
|
||||
buf_free(&buf);
|
||||
curl_slist_free_all(header);
|
||||
free(cmd);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_write(const char *path, const char *wbuf, size_t size,
|
||||
off_t offset, struct fuse_file_info *fi) {
|
||||
(void) path;
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
||||
DEBUG("ftpfs_write: %d %lld\n", size, offset);
|
||||
if (offset + size > fh->buf.size) {
|
||||
buf_resize(&fh->buf, offset + size);
|
||||
}
|
||||
while (fh->buf.len < offset + size) {
|
||||
buf_add_mem(&fh->buf, "\0", 1);
|
||||
}
|
||||
memcpy(fh->buf.p + offset, wbuf, size);
|
||||
fh->dirty = 1;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int ftpfs_flush(const char *path, struct fuse_file_info *fi) {
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
||||
if (!fh->dirty) return 0;
|
||||
|
||||
int err = 0;
|
||||
DEBUG("ftpfs_flush: %d\n", fh->buf.len);
|
||||
char* full_path = g_strdup_printf("%s%s", ftpfs.host, path + 1);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, full_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_INFILESIZE, fh->buf.len);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_UPLOAD, 1);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_READFUNCTION, write_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_READDATA, fh);
|
||||
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
|
||||
fh->dirty = 0;
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_UPLOAD, 0);
|
||||
|
||||
free(full_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_fsync(const char *path, int isdatasync,
|
||||
struct fuse_file_info *fi) {
|
||||
(void) isdatasync;
|
||||
return ftpfs_flush(path, fi);
|
||||
}
|
||||
|
||||
static int ftpfs_release(const char* path, struct fuse_file_info* fi) {
|
||||
struct ftpfs_file* fh = (struct ftpfs_file*) (uintptr_t) fi->fh;
|
||||
ftpfs_flush(path, fi);
|
||||
buf_free(&fh->buf);
|
||||
free(fh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ftpfs_rename(const char* from, const char* to) {
|
||||
int err = 0;
|
||||
char *rnfr = g_strdup_printf("RNFR %s", from);
|
||||
char *rnto = g_strdup_printf("RNTO %s", to);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
struct curl_slist* header = NULL;
|
||||
header = curl_slist_append(header, rnfr);
|
||||
header = curl_slist_append(header, rnto);
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, header);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
CURLcode curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
err = -EPERM;
|
||||
}
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_QUOTE, NULL);
|
||||
|
||||
buf_free(&buf);
|
||||
curl_slist_free_all(header);
|
||||
free(rnfr);
|
||||
free(rnto);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) {
|
||||
int err;
|
||||
CURLcode curl_res;
|
||||
char* dir_path = get_dir_path(path, 1);
|
||||
|
||||
DEBUG("dir_path: %s %s\n", path, dir_path);
|
||||
struct buffer buf;
|
||||
buf_init(&buf, 0);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, dir_path);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_WRITEFUNCTION, read_data);
|
||||
|
||||
curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
DEBUG("%s\n", error_buf);
|
||||
}
|
||||
buf_add_mem(&buf, "\0", 1);
|
||||
|
||||
char* name = strrchr(path, '/');
|
||||
++name;
|
||||
err = parse_dir(&buf, dir_path + strlen(ftpfs.host) - 1, name, NULL, linkbuf, size, NULL, NULL);
|
||||
|
||||
free(dir_path);
|
||||
buf_free(&buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if FUSE_VERSION >= 25
|
||||
static int ftpfs_statfs(const char *path, struct statvfs *buf)
|
||||
{
|
||||
(void) path;
|
||||
|
||||
buf->f_namemax = 255;
|
||||
buf->f_bsize = ftpfs.blksize;
|
||||
buf->f_frsize = 512;
|
||||
buf->f_blocks = 999999999 * 2;
|
||||
buf->f_bfree = 999999999 * 2;
|
||||
buf->f_bavail = 999999999 * 2;
|
||||
buf->f_files = 999999999;
|
||||
buf->f_ffree = 999999999;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int ftpfs_statfs(const char *path, struct statfs *buf)
|
||||
{
|
||||
(void) path;
|
||||
|
||||
buf->f_namelen = 255;
|
||||
buf->f_bsize = 512;
|
||||
buf->f_blocks = 999999999 * 2;
|
||||
buf->f_bfree = 999999999 * 2;
|
||||
buf->f_bavail = 999999999 * 2;
|
||||
buf->f_files = 999999999;
|
||||
buf->f_ffree = 999999999;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int ftpfs_opt_proc(void* data, const char* arg, int key,
|
||||
struct fuse_args* outargs) {
|
||||
(void) data;
|
||||
(void) outargs;
|
||||
|
||||
switch (key) {
|
||||
case FUSE_OPT_KEY_OPT:
|
||||
return 1;
|
||||
case FUSE_OPT_KEY_NONOPT:
|
||||
if (!ftpfs.host) {
|
||||
ftpfs.host = g_strdup_printf("%s%s", arg,
|
||||
arg[strlen(arg)-1] == '/' ? "" : "/");
|
||||
return 0;
|
||||
} else if (!ftpfs.mountpoint)
|
||||
ftpfs.mountpoint = strdup(arg);
|
||||
return 1;
|
||||
default:
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static struct fuse_cache_operations ftpfs_oper = {
|
||||
.oper = {
|
||||
#ifdef SSHFS_USE_INIT
|
||||
// .init = ftpfs_init,
|
||||
#endif
|
||||
.getattr = ftpfs_getattr,
|
||||
.readlink = ftpfs_readlink,
|
||||
.mknod = ftpfs_mknod,
|
||||
.mkdir = ftpfs_mkdir,
|
||||
// .symlink = ftpfs_symlink,
|
||||
.unlink = ftpfs_unlink,
|
||||
.rmdir = ftpfs_rmdir,
|
||||
.rename = ftpfs_rename,
|
||||
.chmod = ftpfs_chmod,
|
||||
.chown = ftpfs_chown,
|
||||
.truncate = ftpfs_truncate,
|
||||
.utime = ftpfs_utime,
|
||||
.open = ftpfs_open,
|
||||
.flush = ftpfs_flush,
|
||||
.fsync = ftpfs_fsync,
|
||||
.release = ftpfs_release,
|
||||
.read = ftpfs_read,
|
||||
.write = ftpfs_write,
|
||||
.statfs = ftpfs_statfs,
|
||||
#if FUSE_VERSION >= 25
|
||||
// .create = ftpfs_create,
|
||||
// .ftruncate = ftpfs_ftruncate,
|
||||
// .fgetattr = ftpfs_fgetattr,
|
||||
#endif
|
||||
},
|
||||
.cache_getdir = ftpfs_getdir,
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int res;
|
||||
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
||||
CURLcode curl_res;
|
||||
|
||||
memset(&ftpfs, 0, sizeof(ftpfs));
|
||||
if (fuse_opt_parse(&args, &ftpfs, ftpfs_opts, ftpfs_opt_proc) == -1)
|
||||
exit(1);
|
||||
|
||||
if (!ftpfs.host) {
|
||||
fprintf(stderr, "missing host\n");
|
||||
fprintf(stderr, "see `%s -h' for usage\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ftpfs.connection = curl_easy_init();
|
||||
if (ftpfs.connection == NULL) {
|
||||
fprintf(stderr, "Error initializing libcurl\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_ERRORBUFFER, error_buf);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_URL, ftpfs.host);
|
||||
curl_easy_setopt(ftpfs.connection, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
||||
if (ftpfs.verbose) curl_easy_setopt(ftpfs.connection, CURLOPT_VERBOSE, 1);
|
||||
curl_res = curl_easy_perform(ftpfs.connection);
|
||||
if (curl_res != 0) {
|
||||
fprintf(stderr, "Error connecting to ftp: %s\n", error_buf);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
res = cache_parse_options(&args);
|
||||
if (res == -1)
|
||||
exit(1);
|
||||
|
||||
if (ftpfs.transform_symlinks && !ftpfs.mountpoint) {
|
||||
fprintf(stderr, "cannot transform symlinks: no mountpoint given\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!ftpfs.transform_symlinks)
|
||||
ftpfs.symlink_prefix_len = 0;
|
||||
else if (realpath(ftpfs.mountpoint, ftpfs.symlink_prefix) != NULL)
|
||||
ftpfs.symlink_prefix_len = strlen(ftpfs.symlink_prefix);
|
||||
else {
|
||||
perror("unable to normalize mount path");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
res = fuse_main(args.argc, args.argv, cache_init(&ftpfs_oper));
|
||||
|
||||
curl_easy_cleanup(ftpfs.connection);
|
||||
|
||||
return res;
|
||||
}
|
Loading…
Reference in New Issue