commit 4f5dfc85dbde4a2298b2e3bcfafa904128dee14e Author: braga Date: Thu Feb 23 19:11:56 2006 +0000 Initial revision diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..623b625 --- /dev/null +++ b/COPYING @@ -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. + + + Copyright (C) + + 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. + + , 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..23e5f25 --- /dev/null +++ b/INSTALL @@ -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. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2e60606 --- /dev/null +++ b/Makefile.am @@ -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 diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..74662db --- /dev/null +++ b/cache.c @@ -0,0 +1,541 @@ +/* + Caching file system proxy + Copyright (C) 2004 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "cache.h" +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/cache.h b/cache.h new file mode 100644 index 0000000..c523e0d --- /dev/null +++ b/cache.h @@ -0,0 +1,29 @@ +/* + Caching file system proxy + Copyright (C) 2004 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include +#include + +#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); diff --git a/compat/fuse_opt.c b/compat/fuse_opt.c new file mode 100644 index 0000000..4c6e12e --- /dev/null +++ b/compat/fuse_opt.c @@ -0,0 +1,359 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2006 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPL. + See the file COPYING.LIB +*/ + +#include "fuse_opt.h" + +#include +#include +#include +#include + +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; +} diff --git a/compat/fuse_opt.h b/compat/fuse_opt.h new file mode 100644 index 0000000..da0ed7b --- /dev/null +++ b/compat/fuse_opt.h @@ -0,0 +1,227 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2006 Miklos Szeredi + + 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 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_ */ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..b9140de --- /dev/null +++ b/configure.ac @@ -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 diff --git a/ftpfs.c b/ftpfs.c new file mode 100644 index 0000000..4dcbee5 --- /dev/null +++ b/ftpfs.c @@ -0,0 +1,927 @@ +/* + FTP file system + Copyright (C) 2006 Robson Braga Araujo + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}