summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2003-01-31 18:41:20 +0000
committerLudovic Courtès <ludo@gnu.org>2003-01-31 18:41:20 +0000
commit823053ec9f580c44aea57ca473a86452e7e332a7 (patch)
treeb26ac31982a993ed21e2021f32d0c9e21f1833b1
Initial commit of a libnetfs-based tarfs including a gzip/bzip2 store.
-rw-r--r--BUGS30
-rw-r--r--COPYING340
-rw-r--r--ChangeLog388
-rw-r--r--Makefile65
-rw-r--r--README67
-rw-r--r--TODO15
-rw-r--r--backend.h123
-rw-r--r--cache.c470
-rw-r--r--cache.h83
-rw-r--r--debug.c88
-rw-r--r--debug.h64
-rw-r--r--fs.c739
-rw-r--r--fs.h123
-rw-r--r--main.c76
-rw-r--r--names.c218
-rw-r--r--names.h33
-rw-r--r--netfs.c834
-rw-r--r--store-bzip2.c107
-rw-r--r--store-gzip.c366
-rw-r--r--tar.c501
-rw-r--r--tar.h185
-rw-r--r--tarfs.c1364
-rw-r--r--tarfs.h145
-rw-r--r--tarlist.c232
-rwxr-xr-xtestfs.sh104
-rw-r--r--zipstores.c1291
-rw-r--r--zipstores.h34
27 files changed, 8085 insertions, 0 deletions
diff --git a/BUGS b/BUGS
new file mode 100644
index 000000000..b7e993658
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,30 @@
+Known tarfs bugs
+=--------------=
+
+
+1. General
+
+* Doesn't pass the "ustar-all-quickest.tar" test file with long links.
+* io_map () not implemented (gcc uses it unfortunately).
+* netfs.c (netfs_get_dirents): too much memory may be allocated (mmap'd).
+
+
+2. Writable fs bugs
+
+* UIDs/GIDs are not properly handled (uses numbers instead of strings).
+* Deadlock when syncing the filesystem at tarfs.c:tarfs_sync_fs when
+ trying to acquire the lock of a node (couldn't reproduce it though).
+
+
+3. Zip stores bug
+
+* Missing feature: RPC encoding/decoding stuff not supported.
+
+ Note: The gzip and bzip2 stores sharing most of their code, there is a
+ high probability that any bug occuring with on backend should also
+ occur with the other. If you encounter a bug, please try using
+ both backends to see if that makes any difference. The code is
+ also very close to what's in cache.c.
+
+
+For more bugs, "grep '\(FIXME\|XXX\)' *.c" should do the trick.
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..60549be51
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) 19yy <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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) 19yy 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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 000000000..d0d7f4dae
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,388 @@
+2002-12-04
+
+ * tarlist.c (tar_put_item): Fixed a bug in placing "foo/bar".
+ * tar.c (tar_make_header): Add a trailing '/' to the name if it's
+ a directory.
+ * fs.c (fs_make_node): Duplicate NAME.
+ (fs_unlink_node): Don't free () anything.
+ (fs_free_node): New function.
+ (_make_node): Always set st_fstype to FSTYPE_TAR.
+ * tarfs.c (tarfs_unlink_node): Free NODE only once it has been
+ successfully unlinked.
+ * netfs.c (netfs_S_file_syncfs): New stub that doesn't lock
+ the node passed by the user.
+
+2002-12-03
+
+ * tarfs.c (tarfs_add_header): In case of "inconsistent tar archive",
+ call tarfs_create_node () to create the missing node.
+ * netfs.c (fs_unlink_node): Lock netfs_node_refcnt_lock before
+ accessing node->references.
+ (fs_unlink_node): Don't call netfs_nput ().
+
+2002-11-26
+
+ * tarfs.c (tarfs_sync_fs::tar_write): Check whether we need to
+ call open_store () before actually calling store_write ().
+ * netfs.c (netfs_attempt_mkdir): Don't lock & ref the newly created
+ directory; don't unlock DIR.
+ * cache.c (__cache_set_size): New function.
+ (__cache_synced): New function.
+ (cache_cache): Call __cache_set_size () if necessary.
+ (cache_write): Use __cache_set_size () and __cache_synced ().
+
+2002-11-19
+
+ * tarfs.c (tarfs_set_options): Initialize err.
+ * fs.c (fs_make_node): Return EEXIST if the node already exists.
+ (_make_node): Check whether M denotes a directory.
+ * netfs.c (netfs_attempt_chmod): Fixed typo.
+ (netfs_attempt_lookup): Lock node on success.
+
+2002-11-13
+
+ * fs.c (fs_link_node, fs_link_node_path): Update nn_translated so
+ that lookups of symlink gets redirected to their target
+ (ie. "cat link" shows link's target contents, *not* link's target
+ path).
+ * zipstores.c (ZIP (open)): Do not read gzip header if the file
+ is empty.
+ (ZIP (stream_read)): Do nothing if the file is empty.
+ (ZIP (read)): Initialize err to 0.
+ * store-gzip.c (gzip_write_header): New function.
+ * cache.c (cache_read): Before accessing blocks[block], make sure
+ BLOCK is not larger that BLOCKS_SIZE.
+ * zipstore.c (ZIP (read)): Likewise.
+ * tarfs.c (tarfs_init): Don't forget to call cache_init ().
+
+2002-11-09
+
+ * tarfs.c (tarfs_set_options): Close and reopen file when mode
+ has changed.
+ * cache.c (fetch_block): Use "orig_size" instead of the current node
+ size when calculating the amount of data to be read.
+ * zipstores.c (fetch_block): Likewise.
+ * cache.c (cache_write): Don't fetch_block() if beyond last available
+ block on disk.
+ (cache_set_size): Check whether SIZE is 0.
+ (cache_write): Fixed the computation of WRITE.
+ * zipstores.c (ZIP (write)): Likewise.
+
+2002-11-06
+
+ * cache.c (cache_set_size): Don't allocate blocks here.
+ (alloc_block): New function.
+ (cache_init): New function.
+ * tarfs.c (read_from_file): New function.
+ (__tar_file_lock): New variable used throughout the thing.
+ * netfs.c (netfs_S_io_map): New function.
+
+2002-11-05
+
+ * netfs.c (netfs_attempt_chmod): Don't remove the FMT bits.
+ * fs.c (fs_make_node): Make sure the FMT bits are set.
+
+2002-11-04
+
+ * tarfs.c (tarfs_create_node): Call cache_create ().
+ * cache.c (cache_write): Call cache_set_size () if the node has been
+ synced.
+ (cache_write): Allocate new blocks here.
+
+2002-10-24
+
+ * zipstores.c (ZIP (set_size)): Do not allocate new blocks here.
+ (ZIP (write)): Allocate new blocks here.
+ (struct ZIP (object)): zip_orig_blocks_size: New field.
+ (ZIP (traverse)): Renamed to "traverse".
+ * fs.c (_find_node): Look for '.' and '..'.
+ * tarfs.c (tarfs_add_header): Complain if a node already exists.
+ * cache.c: Changed cache_read/cache_write prototypes to make them
+ similar to those of store_read/store_write.
+
+2002-10-22
+
+ * fs.c (fs_dir_first_entry, fs_dir_next_entry): New functions.
+ * tarlist.c: New file.
+ * tarfs.c: Moved the tar list mgmt functions to tarlist.c.
+ * tarfs.h: Added tarlist.c prototypes.
+ * cache.c: Added locks.
+
+2002-10-21
+
+ * zipstores.c: Moved gzip-specific code to store-gzip.c (CRC, etc.).
+ * store-gzip.c (gzip_write_suffix): New function.
+ * testfs.sh: New file.
+
+2002-10-20
+
+ * zipstores.c (struct stream_state): Added a lock.
+ (struct ZIP (object)): Added a lock for the cache.
+ (ZIP (set_size)): Also set store->end and store->wrap_src and
+ runs[0].length.
+
+2002-10-19
+
+ * zipstores.c (ZIP (stream_write)): Increment write's file_offs
+ *only* after having actually written things.
+ * libstore.diff: Use size_t instead of off_t/store_offset_t for
+ the NEWSIZE parameter.
+
+2002-10-17
+
+ * zipstores.c (enum status): STATUS_IDLE: New value.
+ (ZIP (stream_*_init)): Close non-idle streams.
+ (ZIP (sync)): Rewrite file if size has changed.
+ (struct ZIP (store)): Renamed to ZIP (object).
+ (struct ZIP (object)): zip_orig_size: New field.
+
+2002-10-15
+
+ * zipstores.c (struct stream_state): New structure
+ (struct ZIP (store)): Added the `read' and `write' fields.
+ (stream_read): Killed a bug (assignment of *len).
+ (store_simple_write): New function.
+
+2002-10-13
+
+ * zipstores.c: Implemented a copy-on-write page cache, mostly copied
+ from cache.c, rather than trying to adapt cache.c and integrate it.
+ This is mostly because (1) cache.c already make asumptions that we
+ are reading from nodes and (2) zipstores.c may exist in the future
+ as a stand-alone file, outside of tarfs.
+ ZIP (stream_write): New function.
+ ZIP (sync): New function.
+ STORE_ZIP (class): Use ZIP (sync) () as a cleanup function.
+ fetch_block: New function.
+ * tarfs.c (tarfs_sync_fs): Call store_free () when it's done.
+
+2002-10-09
+
+ * tarfs.c (tarfs_set_options): Support for runtime option settings.
+ * store-gzip.c (gzip_verify_crc): New function.
+ * zipstores.c (ZIP (stream_read)): Added support for CRC check
+ (only used by the gzip store).
+
+2002-10-07
+
+ * Makefile (DEBUG_ZIP): New flag.
+
+2002-10-06
+
+ * zipstores.c: New file.
+ * zip-stores.h: Renamed to zipstores.h.
+ * store-gzip.c: Include zipstores.c.
+ * store-bzip2.c: New file.
+
+2002-10-04
+
+ * debug.c: New file.
+ * tarfs.c (tarfs_parse_opts): 'D': New option.
+
+2002-10-03
+
+ * debug.h: New file.
+ * store-gzip.c: Crashes at EOF.
+
+2002-10-01
+
+ * tarfs.c (tar_put_item): New function.
+ (tarfs_create_node): Moved some code to tar_put_item ().
+ (tarfs_link_node): Use tar_put_item ().
+
+2002-09-25
+
+ * zip-stores.h: New file.
+ * store-gzip.c: New files.
+
+2002-09-23
+
+ * tarfs.c (debug): New macro.
+
+2002-09-22
+
+ * tarfs.c: New --create and --volatile options.
+
+2002-09-17
+
+ * fs.c (fs_link_node_path): New function.
+ * fs.c: Include fs.h to make sure that it's consistent.
+ (fs_unlink_node): *Do* call netfs_nput ().
+ * backend.h (struct fs_backend): symlink_node, link_node, mkdev_node:
+ New fields.
+ * tarfs.c: tarfs_symlink_node, tarfs_mkdev_node, tarfs_link_node:
+ New functions.
+ * tarfs.c (tarfs_get_next_entry): Skip anonymous nodes.
+ (tarfs_skip_entries): Likewise.
+ (tarfs_set_cd): Likewise.
+ (tarfs_create_node): Allow anonymous (nameless) nodes.
+ (tarfs_lookup_node): Likewise.
+ * fs.c (_find_node): Do nothing for nameless nodes.
+
+2002-09-16
+
+ * netfs.c (netfs_attempt_create_file): Lock node and add a ref
+ to it on success.
+ * fs.c (fs_unlink_node): Don't call netfs_nput ().
+
+2002-09-14
+
+ * Makefile (CFLAGS): Added -D_FILE_OFFSET_BITS=64 so that it can
+ work well (otherwise, struct dirent are wrong and the first 4
+ bytes of filenames would just disappear...)
+ * tarfs.c (tarfs_sync_fs): Write a zero record at the end so that
+ tar parsing doesn't get messed up.
+ tar_write: New subfunction.
+
+2002-09-13 (Friday!)
+
+ * netfs.c: Moved off_t into loff_t to work with 0.3 interfaces.
+ * tarfs.c (netfs_server_name): New netfs variable.
+ (netfs_server_version): Likewise.
+ * netfs.c: Changed struct stat into io_statbuf_t and
+ struct statfs into fsys_statfsbuf_t.
+ * backend.h (change_stat): Likewise.
+ * tarfs.c (tarfs_change_stat): Likewise.
+ * netfs.c (_make_node): Likewise + set nn_translated to st_mode.
+ (fs_hard_link_node): Likewise.
+ * backend.h (init): Pass an iouser to init.
+ * main.c (main): Likewise.
+
+2002-09-09
+
+ * zio.h, zio-file.c, zio-gzip.c, zio-bzip2.c: New files.
+ * tarfs.c: Replaced store_* with zio_*.
+ * tar.c: Likewise.
+ * cache.c: Likewise.
+ * tarfs.c (tarfs_sync_fs): Truncate file size if necessary.
+ * tarfs.c (tarfs_sync_fs): Don't call cache_ahead() if we are
+ at least one block behind.
+
+2002-09-07
+
+ * fsutils.[ch]: Renamed to fs.[ch] (looks better).
+ * fs.[ch]: New files.
+ * tarfs_defs.h: Renamed to tarfs.h (also looks better).
+ * tarfs.h: New file.
+ * tarfs.h (struct tar_item): Removed field 'header' which is useless.
+ * fs.c: Set DEBUG_FS to enable fs.c's debugging output.
+ * tarfs.c (tarfs_get_args): Handle options.
+ * backend.h (struct netnode): Added the `hardlink' field.
+ * fs.c (fs_hard_link_node): Use the `hardlink' field of netnode.
+ * cache.c (cache_read): Likewise.
+ * tarfs.c (tarfs_write_node): Likewise.
+ * tarfs.c (tarfs_change_stat): Likewise.
+ * names.c (uid_to_uname, gid_to_uname): New functions from GNU tar.
+ * tarfs.c (tarfs_create_node): Return ENAMETOOLONG when relevant.
+ * fs.c (fs_hard_link_node): Increase TARGET's st_nlink.
+ * fs.c (fs_unlink_node): Check NODE's st_nlink.
+ * fs.c: get_path_(from|to)_root renamed to fs_get...
+ * netfs.c (netfs_attempt_syncfs): Only call go_away () which in turn
+ should call sync_fs ().
+ * fs.c (fs_unlink_node): Call netfs_nput ().
+
+2002-09-05
+
+ * cache.c (cache_set_size): Fixed typo in the block allocation.
+ * tarfs.c (cache_ahead): New function.
+ * tarfs.c (tarfs_create_node): New nodes are inserted in an optimal
+ way.
+ * current status: Syncing works when adding a file. :)
+
+2002-09-04
+
+ * tar.c (tar_make_header): New function (FIXME: Bad chksum).
+ * tarfs.c (tarfs_sync_fs): Cache *all* following nodes if necessary.
+
+2002-09-03
+
+ * tarfs.c (read_archive): Open it read-write when relevant.
+ * tarfs.c (tarfs_sync_fs): Ends up in an indefinite loop when
+ writing contents.
+
+2002-09-02
+
+ * tarfs.c (tar_make_item): Args changed.
+ * cache.c (cache_synced): New function to replace the contents_changed field.
+ * cache.c (cache_sync): New function.
+ * tarfs.c (tarfs_sync_fs): Almost complete (never tried ;).
+
+2002-09-01
+
+ * tarfs.c (tar_insert_item): Fixed some flaws.
+
+2002-08-31
+
+ * fsutils.c (fs_make_node): Fixed typo in newnode->prevp assignment.
+ * fsutils.c (fs_unlink_node): Returns error_t.
+
+2002-08-27
+
+ * cache.c (cache_read): Make sure that node is available on disk
+ before calling read_from_file ().
+ * cache.c (cache_write): Likewise.
+
+2002-08-26
+
+ * cache.c (cache_set_size): New function.
+ * fsutils.c (fs_unlink_node): New function.
+
+2002-08-24
+
+ * cache.c (cache_write): Writing to a file with copy-on-write works.
+ * fsutils.c (_make_node): Set st_blocksize to 1024, better than 1.
+ * fsutils.c: A bit more GNU Coding Std compliant. ;)
+ * backend.h (struct fs_backend): New callbacks.
+
+2002-08-23
+
+ * cache.c, cache.h: New files: node's cache management.
+ * cache.c (cache_read): Reading directly from the file (i.e.
+ store_read ()) works.
+
+2002-08-20
+
+ * netfs.c (OWNERSHIP): New macro to check whether a user owns a node.
+ * netfs.c (HIDE_FILES_NOT_OWNED): New compilation flag.
+ * tarfs_defs.h: New data structures to support writing:
+ struct tar_item, struct status and struct ccache;
+ struct tarfs_info also changed.
+ * backend.h (struct fs_backend): New fields to support writing.
+ * tarfs.c: New functions to manage a linked list of tar items,
+ new functions to support writing (do nothing so far).
+ * tar.c: fill_stat_from_header renamed to tar_header2stat.
+ -> this is an unstable version!
+
+2002-08-19
+
+ * netfs.c: Uses EROFS instead of EOPNOTSUPP
+ (netfs_attempt_create_file): Set *NP to NULL before returning
+ (netfs_attempt_lookup): When lookup'ing "." and "..", first make
+ sure that DIR is a directory (thus, it is not possible to cd to
+ a file).
+ * tarfs.c (tarfs_init): Retrieve permissions from the tar file and
+ set them to the root node.
+ * version.h: File removed since tarfs is not part of the Hurd tree...
+
+2002-08-12
+
+ * tarfs.c (tarfs_add_header): changed so that it can handle hard links
+ * fsutils.c (fs_hard_link_node): new function
+ * fsutils.c (fs_find_node_path): new function
+ * fsutils.c (fs_make_node_path): uses above-mentioned function
+ * tarfs.c: susbstituted error () to fprintf ()
+ * tarfs.c (tarfs_new_dirent): renamed to _new_dirent ()
+ * tarfs.c (_new_dirent): replaced vm_allocate () by mmap ()
+
+2002-08-03
+
+ * tar.c (skip_n_records): removed useless calls to get_next_record()
+ * tarfs.c (tarfs_get_args): appends filename to the arglist
+
+2002-08-02 Ludovic Courtès <ludo@type-z.org>
+
+ * tarfs.c: set st_fstype to FSTYPE_TAR
+ * tarfs.c: tarfs_add_header (): creates nodes even if one of its subdir
+ does not exist in the current tree (eg.: creates "/foo/bar" even
+ if "/foo" does not exist, just create "/foo" before)
+ * Makefile: added an install rule :)
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..311fd709e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,65 @@
+# Makefile for the Hurd Tarfs translator.
+# Copyright (C) 2002, 2003 Ludovic Courtès <ludo@type-z.org>
+
+# 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, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+CC = gcc
+INSTALL = install # cp
+CFLAGS = -D_GNU_SOURCE -Wall -g -D_FILE_OFFSET_BITS=64
+CFLAGS += -DDEBUG # tarfs.c debugging
+#CFLAGS += -DDEBUG_FS # fs.c debugging
+CFLAGS += -DDEBUG_ZIP # zip stores debugging
+
+# Uncomment the line below to get a fs that shows file *only* to their owner.
+#CFLAGS += -DHIDE_FILES_NOT_OWNED
+
+# Note: -lz has to be first otherwise inflate() will be the exec server's
+# inflate function
+LDFLAGS = -L~ -lz -L. -lnetfs -lfshelp -liohelp -lports \
+ -lihash -lshouldbeinlibc -lthreads -lstore -lbz2 #-lpthread
+CTAGS = ctags
+
+SRC = main.c netfs.c tarfs.c tarlist.c fs.c cache.c tar.c names.c \
+ store-bzip2.c store-gzip.c debug.c
+
+OBJ = $(SRC:%.c=%.o)
+
+TRANS = tarfs
+
+HURD = /hurd
+
+# Name of the test node
+TNODE = t
+
+all: $(TRANS) # start-trans
+
+$(TRANS): $(OBJ)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+tags: $(SRC)
+ $(CTAGS) $(SRC)
+
+start-trans: $(TRANS)
+ settrans -ac $(TNODE) ./$(TRANS)
+
+stop-trans:
+ settrans -fg $(TNODE)
+
+install: $(TRANS)
+ $(INSTALL) -m 555 $(TRANS) $(HURD)
+
+clean:
+ -rm -f $(TRANS) $(OBJ) $(TNODE) tags core
diff --git a/README b/README
new file mode 100644
index 000000000..728cc49b7
--- /dev/null
+++ b/README
@@ -0,0 +1,67 @@
+This is tarfs, a tar filesystem translator for the GNU Hurd.
+============================================================
+
+
+1. Introduction
+
+Tarfs is a GNU Hurd filesystem server for tar files. In other words, it
+can be used to "mount" tar files, and even zipped tar files. To install
+it, if you don't have a recent CVS snapshot (Dec. 2002), you first need
+to patch libstore in order to add the store_set_size () call which is
+used by tarfs:
+
+ $ cd /src/hurd
+ $ patch -p0 < /path/to/tarfs/libstore.diff
+ $ make -C libstore install
+
+Then you can use tarfs to mount (almost) any tar file:
+
+ $ settrans -ca a /hurd/tarfs -z myfile.tar.gz
+ $ settrans -ca b /hurd/tarfs -y myfile.tar.bz2
+ $ settrans -ca c /hurd/tarfs myfile.tar
+
+You can even use it to create new tar files:
+
+ $ settrans -ca new /hurd/tarfs -cz newfile.tar.gz
+ $ cp -r all my files new/
+ $ syncfs new
+
+This is not as fast as "tar czvf newfile.tar.gz all my files" but at least,
+it's more original. ;)
+
+tarfs borrows code from the mboxfs translator (fsutils.[ch], netfs.c,
+backend.h), from the Midnight Commander file manager v. 4.5.30 (tar.c) and
+from GNU Tar (names.c). The two latter files are under LGPL and the rest is
+under GPL (in the future, the code from GNU tar might be replaced by a
+"cleaner" custom version).
+
+
+2. Gzip and Bzip2 stores
+
+For tarfs to be able to transparently read from and write to zipped tar files,
+a gzip and a bzip2 store (i.e. a libstore module) have been written, using
+zlib and libbz2. Libstore currently only provides read-only zip stores. These
+two new stores support both reading and writing. However this is done in a
+special way: It uses a copy-on-write caching scheme and the data written to
+zip stores gets actually written (i.e. compressed and written) to the
+underlying file *only* when the store is freed (call to store_free ()). The
+issue was that both zlib and libbz2 provide either the ability of read *or* to
+write to a file, not both. Moreover, I couldn't think of any reliable way to
+provide read-write zip stores without caching.
+
+Note also that these zip stores are quite slow when being created: They
+actually first traverse (and uncompress) the whole file in order to get
+its size. This is because libstore needs to know the store size before
+anything can be done. When being traversed, the uncompressed stream does
+not get cached; caching is done only when writing to the store.
+
+
+3. Misc
+
+You can try out the HIDE_FILES_NOT_OWNED compilation flag which creates
+a tarfs that shows you only the files that belong to you (looking at your list
+of effective user ids). :)
+
+
+<ludo@type-z.org> http://people.type-z.org/ludo/hurd/
+<ludovic.courtes@utbm.fr>
diff --git a/TODO b/TODO
new file mode 100644
index 000000000..c1c826167
--- /dev/null
+++ b/TODO
@@ -0,0 +1,15 @@
+TODO List
+=-------=
+
+* Get a better tar.c (parsing engine) from GNU tar or write it from scratch.
+
+* Implement passive translator settings: this could typically be done using
+ the `link' field of the tar header to store the translator command line;
+ then we could use an unused byte of the header to say that there is a
+ translator set on this node. For symlinks, we would not show any passive
+ translator setting.
+
+* Implement netfs_S_io_map ().
+
+* Update the gzip store to make it more robust (for instance, find *real*
+ specifications of the header format).
diff --git a/backend.h b/backend.h
new file mode 100644
index 000000000..ab8f92f9c
--- /dev/null
+++ b/backend.h
@@ -0,0 +1,123 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * A general filesystem backend.
+ */
+
+#ifndef __FS_BACKEND__
+#define __FS_BACKEND__
+
+#include <hurd/netfs.h>
+#include <dirent.h>
+#include <argp.h>
+#include <assert.h>
+
+
+/* Generic (fs independent) netnode structure. */
+struct netnode
+{
+ char *name; /* node name */
+ char *symlink; /* link's target path (in the case of a symlink) */
+ struct node *hardlink;/* hard link's target or zero */
+ struct node *entries; /* directory entries (when applies) */
+ struct node *dir; /* parent directory */
+
+ void *info; /* fs defined data (node related info) */
+};
+
+/* Substitute for '/' and chars lower than 32 in node names.
+ If SUBST_LOWER isn't defined, then lower chars won't be filtered. */
+#define SUBST_SLASH '|'
+//#define SUBST_LOWER '.'
+
+
+/* Each filesystem backend should define a struct fs_backend variable with
+ the appropriate functions. */
+struct fs_backend
+{
+ /* Initialize filesystem (create root node *N, etc.) */
+ error_t (* init)(struct node **n, struct iouser *user);
+
+ /* Get filesystem's struct argp. */
+ void (* get_argp)(struct argp *s);
+
+ /* Get arguments (see netfs_append_args()). */
+ error_t (*get_args)(char **argz, unsigned *argz_len);
+
+ /* Set options (see netfs_set_options()). */
+ error_t (*set_options)(char *argz, size_t argz_len);
+
+
+ /*
+ * Directory scan functions (used in netfs_get_dirents ()).
+ */
+
+ /* Set current directory. */
+ int (* set_curr_dir)(struct node *dir);
+
+ /* Skip N entries in current directory, returns non-zero if
+ no more entries are available. */
+ int (* skip_entries)(int n);
+
+ /* Returns a newly-allocated entry in ENTRY. Returns non-zero when
+ no more entries are available. */
+ int (* get_next_entry)(struct dirent **entry);
+
+ /* Reading a node */
+ error_t (* lookup_node)(struct node **np, struct node* dir, const char* name);
+ error_t (* read_node) (struct node *np, off_t offset,
+ size_t *len, void* data);
+
+
+ /* Changing a node */
+ error_t (* write_node) (struct node *np, off_t offset,
+ size_t *len, void* data);
+
+ /* Change NP's stats. */
+ error_t (* change_stat)(struct node *np, const io_statbuf_t *new_stat);
+
+ /* Creates a node named NAME in DIR which is locked. */
+ error_t (* create_node) (struct node **new, struct node *dir,
+ char *name, mode_t m);
+
+ /* Unlinks NODE. NODE can be a directory in which case it is empty. */
+ error_t (* unlink_node) (struct node *node);
+
+ /* Tries to create a hard link named NAME in DIR to file NODE. */
+ error_t (* link_node) (struct node *dir, struct node *target,
+ char *name, int excl); /* Same as netfs semantics */
+
+ /* Makes NODE a symlink to TARGET. */
+ error_t (* symlink_node) (struct node *node, const char *target);
+
+ /* Tries to turn NODE into a device of type TYPE (either S_IFBLK
+ or S_IFCHR). */
+ error_t (* mkdev_node) (struct node *node, mode_t type, dev_t indexes);
+
+ /* Free all resources associated to NODE. */
+ void (* free_node) (struct node *node);
+
+ /* Synchronize filesystem. */
+ error_t (* sync_fs) (int wait);
+
+ /* Filesystem destructor */
+ error_t (* go_away) ();
+};
+
+#endif
diff --git a/cache.c b/cache.c
new file mode 100644
index 000000000..1e4e78458
--- /dev/null
+++ b/cache.c
@@ -0,0 +1,470 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * Nodes contents cache management.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <hurd/netfs.h>
+
+#include "tarfs.h"
+#include "cache.h"
+#include "debug.h"
+
+/* Locking/unlocking a node's cache */
+#define LOCK(Node) mutex_lock (&CACHE_INFO ((Node), lock))
+#define UNLOCK(Node) mutex_unlock (&CACHE_INFO ((Node), lock));
+
+/* Tar file callback (in tarfs.c). */
+static error_t (* read_file) (struct node *node,
+ off_t offset, size_t howmuch,
+ size_t *actually_read, void *data) = NULL;
+
+/* BLOCK_NUMBER gives the number in which Offset can be found
+ (equivalent to Offset/CACHE_BLOCK_SIZE). */
+#define BLOCK_NUMBER(Offset) \
+ ((Offset) >> CACHE_BLOCK_SIZE_LOG2)
+
+/* BLOCK_RELATIVE_OFFSET gives the relative offset inside a block
+ (equivalent to AbsoluteOffset%CACHE_BLOCK_SIZE). */
+#define BLOCK_RELATIVE_OFFSET(AbsoluteOffset) \
+ ((AbsoluteOffset) & (CACHE_BLOCK_SIZE - 1))
+
+
+/* Initializes the cache backend. READ is the method that will be called
+ when data needs to be read from a node. */
+void
+cache_init (error_t (* read) (struct node *node, off_t offset, size_t howmuch,
+ size_t *actually_read, void *data))
+{
+ read_file = read;
+}
+
+/* Create a cache for node NODE. */
+error_t
+cache_create (struct node *node)
+{
+ size_t size, blocks;
+
+ size = node->nn_stat.st_size;
+ blocks = size ? BLOCK_NUMBER (size - 1) + 1 : 1;
+
+ CACHE_INFO (node, blocks) = calloc (blocks, sizeof (char *));
+ if (!CACHE_INFO (node, blocks))
+ return ENOMEM;
+
+ CACHE_INFO (node, size) = blocks;
+ debug (("Node %s: Initial block vector size: %u", node->nn->name, blocks));
+
+ mutex_init (&CACHE_INFO (node, lock));
+
+ return 0;
+}
+
+/* Free NODE's cache. */
+error_t
+cache_free (struct node *node)
+{
+ size_t i, size;
+ char **p;
+
+ LOCK (node);
+ p = CACHE_INFO (node, blocks);
+ size = CACHE_INFO (node, size);
+ debug (("Node %s: Freeing blocks (size = %u)", node->nn->name, size));
+
+ if (p)
+ {
+ for (i=0; i < CACHE_INFO (node, size); i++)
+ if (p[i])
+ {
+ free (p[i]);
+ p[i] = NULL;
+ }
+
+ /* Finish it. */
+ free (p);
+ CACHE_INFO (node, blocks) = NULL;
+ CACHE_INFO (node, size) = 0;
+ }
+ else
+ assert (CACHE_INFO (node, size) == 0);
+
+ UNLOCK (node);
+ return 0;
+}
+
+/* Same as cache_synced () (assuming NODE's cache is locked). */
+static inline int
+__cache_synced (struct node *node)
+{
+ int i, ret = 1;
+ char **blocks;
+
+ blocks = CACHE_INFO (node, blocks);
+
+ for (i = 0; i < CACHE_INFO (node, size); i++)
+ if (blocks[i])
+ {
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+/* Returns non-zero if NODE is synchronized (ie. not cached). */
+int
+cache_synced (struct node *node)
+{
+ int ret;
+
+ LOCK (node);
+ ret = __cache_synced (node);
+ UNLOCK (node);
+
+ return ret;
+}
+
+
+/* A canonical way to allocate cache blocks (assumes that cache is locked
+ and that BLOCKS is at least BLOCK+1 long). */
+static inline error_t
+alloc_block (struct node *node, size_t block)
+{
+ char *b;
+ char **blocks = CACHE_INFO (node, blocks);
+
+ assert (CACHE_INFO(node, size) > block);
+ assert (!blocks[block]);
+
+ /* Allocate a new block */
+ //debug (("Node %s: Allocating block %u", node->nn->name, block));
+ b = calloc (CACHE_BLOCK_SIZE, sizeof (char));
+ if (!b)
+ return ENOMEM;
+
+ blocks[block] = b;
+
+ return 0;
+}
+
+/* Fetches block number BLOCK of NODE. This assumes that NODE's cache
+ is already locked. */
+static inline error_t
+fetch_block (struct node *node, int block)
+{
+ error_t err = 0;
+ char **blocks = CACHE_INFO(node, blocks); /* cache blocks array */
+
+ size_t read;
+ size_t size = NODE_INFO (node)->tar->orig_size;
+ size_t actually_read = 0;
+
+ assert (read_file);
+
+ /* Don't try to go beyond the boundaries. */
+ assert (block <= BLOCK_NUMBER (size - 1));
+
+ /* Allocate a new block. */
+ assert (!blocks[block]);
+ err = alloc_block (node, block);
+ if (err)
+ return err;
+
+ /* If this is the last block, then we may have less to read. */
+ if (block == BLOCK_NUMBER (size - 1))
+ read = size % CACHE_BLOCK_SIZE;
+ else
+ read = CACHE_BLOCK_SIZE;
+
+ err = read_file (node, (block << CACHE_BLOCK_SIZE_LOG2),
+ read, &actually_read,
+ blocks[block]);
+
+ if (err)
+ return err;
+
+ /* We should have read everything. */
+ assert (actually_read == read);
+
+ return err;
+}
+
+
+/* Read at most AMOUNT bytes from NODE at OFFSET into BUF.
+ Returns the amount of data actually read in LEN. */
+error_t
+cache_read (struct node *node, off_t offset, size_t amount,
+ void *buf, size_t *len)
+{
+ error_t err = 0;
+ void *datap = buf; /* current pointer */
+ off_t start = NODE_INFO(node)->tar->offset;
+ size_t size = node->nn_stat.st_size;
+ char **blocks;
+ size_t blocks_size;
+ size_t block = BLOCK_NUMBER (offset); /* 1st block to read. */
+
+ /* If NODE is a link then redirect the call. */
+ if (node->nn->hardlink)
+ return cache_read (node->nn->hardlink, offset, amount, buf, len);
+
+ /* Symlinks should be handled in tarfs_read_node () or so. */
+ assert (! node->nn->symlink);
+ assert (read_file);
+
+ /* Check file boundaries. */
+ if (offset >= size)
+ {
+ *len = 0;
+ return 0;
+ }
+
+ /* Lock the node */
+ LOCK (node);
+ blocks = CACHE_INFO(node, blocks);
+ blocks_size = CACHE_INFO(node, size);
+
+ /* Adjust SIZE and LEN to the maximum that can be read. */
+ size -= offset;
+ size = (size > amount) ? amount : size;
+ *len = size;
+
+ /* Set OFFSET to be the relative offset inside cache block num. BLOCK. */
+ offset = BLOCK_RELATIVE_OFFSET (offset);
+
+ while (size > 0)
+ {
+ size_t read = (size > CACHE_BLOCK_SIZE)
+ ? (CACHE_BLOCK_SIZE - offset)
+ : (size);
+
+ /* Read a block either from cache or directly. */
+ if ((block < blocks_size) && (blocks[block]))
+ memcpy (datap, &blocks[block][offset], read);
+ else
+ {
+ /* If NODE is available on disk, then fetch its contents. */
+ if (start != -1)
+ {
+ size_t actually_read;
+ err = read_file (node, (block << CACHE_BLOCK_SIZE_LOG2) + offset,
+ read, &actually_read, datap);
+ if (err)
+ break;
+
+ /* We should have read everything. */
+ assert (actually_read == read);
+ }
+ else
+ /* If NODE is not cached nor on disk, then zero the user's buffer. */
+ bzero (datap, read);
+ }
+
+ /* Go ahead with next block. */
+ block++;
+ size -= read;
+ offset = 0;
+ datap = datap + read;
+ }
+
+ UNLOCK (node);
+
+ return err;
+}
+
+/* Set the cache size (assuming NODE's cache is locked). */
+static inline error_t
+__cache_set_size (struct node *node, size_t size)
+{
+ error_t err = 0;
+ size_t *blocks_size;
+ char ***blocks;
+
+ /* New size of BLOCKS */
+ size_t newsize = size ? BLOCK_NUMBER (size - 1) + 1 : 0;
+
+ blocks_size = &(CACHE_INFO (node, size));
+ blocks = &(CACHE_INFO (node, blocks));
+
+ if (size > node->nn_stat.st_size)
+ {
+ /* Grow the cache. */
+ if (newsize > *blocks_size)
+ {
+ /* Enlarge the block vector */
+ char **newblocks;
+
+ newblocks = realloc (*blocks, newsize * sizeof (char *));
+ if (newblocks)
+ {
+ /* Zero the new blocks without actually allocating them */
+ bzero (&newblocks[*blocks_size],
+ (newsize - *blocks_size) * sizeof (char *));
+
+ *blocks = newblocks;
+ *blocks_size = newsize;
+
+ debug (("Node %s: grown to %u blocks", node->nn->name, newsize));
+ }
+ else
+ err = ENOMEM;
+ }
+ }
+ else
+ {
+ int i;
+
+ /* Free unused cache blocks */
+ for (i = newsize; i < *blocks_size; i++)
+ free ((*blocks)[i]);
+
+ /* Reduce cache vector */
+ *blocks = realloc (*blocks, newsize * sizeof (char *));
+ *blocks_size = newsize;
+ }
+
+ if (!err)
+ node->nn_stat.st_size = size;
+
+ return err;
+}
+
+/* Sets the size of NODE and reduce/grow its cache. */
+error_t
+cache_set_size (struct node *node, size_t size)
+{
+ error_t err;
+
+ LOCK (node);
+ err = __cache_set_size (node, size);
+ UNLOCK (node);
+
+ return err;
+}
+
+/* Writes at most LEN bytes from NODE at OFFSET into BUF.
+ Returns the amount of data actually written in AMOUNT. */
+error_t
+cache_write (struct node *node, off_t offset, void *data,
+ size_t len, size_t *amount)
+{
+ error_t err = 0;
+ size_t size = len;
+ void *datap = data; /* current pointer */
+ size_t block = BLOCK_NUMBER (offset); /* 1st block to read */
+ size_t last_block; /* Last block avail on disk */
+ char **blocks; /* cache blocks array */
+ int ondisk;
+
+ /* Links should be handled by tarfs_write_node ()). */
+ assert (!node->nn->hardlink);
+ assert (!node->nn->symlink);
+
+ LOCK (node);
+
+ {
+ /* Check whether we need to create/grow NODE's cache */
+ size_t newsize = offset + len;
+
+ if (__cache_synced (node) || (newsize > node->nn_stat.st_size))
+ err = __cache_set_size (node, newsize);
+ }
+
+ blocks = CACHE_INFO (node, blocks);
+ ondisk = (NODE_INFO (node)->tar->offset >= 0);
+ last_block = BLOCK_NUMBER (NODE_INFO (node)->tar->orig_size - 1);
+
+ /* Set OFFSET to be the relative offset inside cache block num. BLOCK. */
+ offset = BLOCK_RELATIVE_OFFSET (offset);
+
+ while ((!err) && (size > 0))
+ {
+ size_t write = (size + offset > CACHE_BLOCK_SIZE)
+ ? (CACHE_BLOCK_SIZE - offset)
+ : (size);
+
+ /* Allocate and fetch this block if not here yet (copy-on-write). */
+ if (!blocks[block])
+ {
+ if (ondisk && (block <= last_block))
+ /* Fetch this block */
+ err = fetch_block (node, block);
+ else
+ /* Allocate a new block */
+ err = alloc_block (node, block);
+
+ if (err)
+ break;
+ }
+
+ /* Copy the new data into cache. */
+ memcpy (&blocks[block][offset], datap, write);
+
+ /* Go ahead with next block. */
+ block++;
+ size -= write;
+ offset = 0;
+ datap = datap + write;
+ }
+
+ UNLOCK (node);
+
+ *amount = len - size;
+ assert (*amount <= len);
+
+ return err;
+}
+
+/* Cache AMOUNT bytes of NODE. */
+error_t
+cache_cache (struct node *node, size_t amount)
+{
+ error_t err = 0;
+ int block = BLOCK_NUMBER (amount - 1) + 1;
+ char **blocks;
+ int b;
+
+ assert (amount <= node->nn_stat.st_size);
+
+ LOCK (node);
+
+ if (block >= CACHE_INFO (node, size))
+ /* Allocate a large enough cache */
+ err = __cache_set_size (node, amount);
+
+ blocks = CACHE_INFO (node, blocks);
+
+ for (b = 0; b < block; b++)
+ if (!blocks[b])
+ {
+ err = fetch_block (node, b);
+ if (err)
+ break;
+ }
+
+ UNLOCK (node);
+
+ return err;
+}
diff --git a/cache.h b/cache.h
new file mode 100644
index 000000000..2ec926b76
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,83 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * Nodes contents cache management.
+ */
+
+#ifndef __CACHE_H__
+#define __CACHE_H__
+
+#include <stdlib.h>
+#include <error.h>
+#include <hurd/netfs.h>
+#include <hurd/store.h>
+
+/* Size of a cache block. */
+#define CACHE_BLOCK_SIZE_LOG2 10
+#define CACHE_BLOCK_SIZE (1 << CACHE_BLOCK_SIZE_LOG2)
+
+/* Cache data accessor */
+#define CACHE_INFO(Node, Field) \
+ (NODE_INFO(Node)->cache. Field)
+
+/* Nodes contents cache */
+struct cache
+{
+ /* Vector of cache blocks */
+ char **blocks;
+
+ /* Size of BLOCKS */
+ size_t size;
+
+ /* Lock of this cache */
+ struct mutex lock;
+};
+
+/* Initializes the cache backend. READ is the method that will be called
+ when data needs to be read from a node. */
+extern void cache_init (error_t (* read) (struct node *node, off_t offset,
+ size_t howmuch,
+ size_t *actually_read, void *data));
+
+/* Create a cache for node NODE. */
+extern error_t cache_create (struct node *node);
+
+/* Free NODE's cache. */
+extern error_t cache_free (struct node *node);
+
+/* Read at most AMOUNT bytes from NODE at OFFSET into BUF.
+ Returns the amount of data actually read in LEN. */
+extern error_t cache_read (struct node *node, off_t offset,
+ size_t amount, void *buf, size_t *len);
+
+/* Writes at most LEN bytes from NODE at OFFSET into BUF.
+ Returns the amount of data actually written in AMOUNT. */
+extern error_t cache_write (struct node *node, off_t offset,
+ void *data, size_t len, size_t *amount);
+
+/* Sets the size of NODE and reduce/grow its cache. */
+extern error_t cache_set_size (struct node *node, size_t size);
+
+/* Cache AMOUNT bytes of NODE. */
+extern error_t cache_cache (struct node *node, size_t amount);
+
+/* Returns non-zero if NODE is synchronized (ie. not cached). */
+extern int cache_synced (struct node *node);
+
+#endif /* cache.h */
diff --git a/debug.c b/debug.c
new file mode 100644
index 000000000..f406b4308
--- /dev/null
+++ b/debug.c
@@ -0,0 +1,88 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * General debugging output tools.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <error.h>
+#include <string.h>
+#include <cthreads.h>
+
+
+static struct mutex debug_lock;
+static char *debug_function = NULL;
+static FILE *debug_file = NULL;
+
+/* Sets the debugging output file. */
+void
+debug_set_file (const char *name)
+{
+ if (!strcmp (name, "-"))
+ debug_file = stderr;
+ else
+ {
+ debug_file = fopen (name, "w+");
+ if (!debug_file)
+ error (0, errno, name);
+ }
+}
+
+
+void
+__debug_start (const char *function)
+{
+ if (!debug_file)
+ return;
+
+ mutex_lock (&debug_lock);
+ debug_function = strdup (function);
+}
+
+void
+__debug (const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!debug_file)
+ return;
+
+ fprintf (debug_file, "%s: ", debug_function);
+
+ va_start (ap, fmt);
+ vfprintf (debug_file, fmt, ap);
+ va_end (ap);
+
+ fprintf (debug_file, "\n");
+}
+
+void
+__debug_end ()
+{
+ if (!debug_file)
+ return;
+
+ free (debug_function);
+ debug_function = NULL;
+ fflush (debug_file);
+ mutex_unlock (&debug_lock);
+}
diff --git a/debug.h b/debug.h
new file mode 100644
index 000000000..c65a46478
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,64 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * General debugging output tools.
+ */
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+
+/* Format string for off_t objects. */
+
+#ifdef _FILE_OFFSET_BITS
+# if _FILE_OFFSET_BITS == 64
+# define OFF_FMT "%lli"
+# else /* assume 32 bit */
+# define OFF_FMT "%li"
+# endif
+#else /* assume 32 bit */
+# define OFF_FMT "%li"
+#endif
+
+
+#ifdef DEBUG
+
+/* Sets the debugging output file. */
+extern void debug_set_file (const char *name);
+
+extern void __debug_start (const char *function);
+extern void __debug (const char *fmt, ...);
+extern void __debug_end ();
+
+#define debug(Args) \
+ __debug_start (__FUNCTION__), __debug Args, __debug_end ();
+
+#else
+
+static inline void
+debug_set_file (const char *name) { }
+
+static inline void
+__debug (const char *fmt, ...) { }
+
+#define debug(Args) __debug Args;
+
+#endif /* DEBUG */
+
+#endif
diff --git a/fs.c b/fs.c
new file mode 100644
index 000000000..84d7905b2
--- /dev/null
+++ b/fs.c
@@ -0,0 +1,739 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * General filesystem node management facilities.
+ */
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <maptime.h>
+#include <fcntl.h>
+#include "backend.h"
+#include "fs.h" /* to make sure that the header is up-to-date */
+#include "debug.h"
+
+
+/* General info */
+static pid_t pid;
+static uid_t uid;
+static gid_t gid;
+static volatile struct mapped_time_value *curr_time;
+
+/* Initialization. */
+int
+fs_init ()
+{
+ error_t err;
+
+ /* General stuff. */
+ pid = getpid ();
+ uid = getuid ();
+ gid = getgid ();
+ err = maptime_map (0, 0, &curr_time);
+
+ return err;
+}
+
+/* Returns the first entry of directory DIR (i.e. the first entry which was
+ added to DIR). */
+error_t
+fs_dir_first_entry (struct node *dir, struct node **first)
+{
+ if ((!dir->nn->entries) ||
+ (!S_ISDIR (dir->nn_stat.st_mode)))
+ return ENOTDIR;
+
+ /* This has to be consistent with _make_node () */
+ *first = dir->nn->entries;
+
+ if (!*first)
+ return ENOENT;
+
+ return 0;
+}
+
+/* Return DIR's last entry. */
+error_t
+fs_dir_last_entry (struct node *dir, struct node **last)
+{
+ struct node *node = dir->nn->entries;
+
+ if ((!dir->nn->entries) ||
+ (!S_ISDIR (dir->nn_stat.st_mode)))
+ return ENOTDIR;
+
+ if (!node)
+ {
+ *last = NULL;
+ return ENOENT;
+ }
+
+ for ( ; node->next; node = node->next);
+
+ *last = node;
+
+ return 0;
+}
+
+
+/* Filters a node name, that is, remove '/' and chars lower than 32.
+ Returns NAME is no change has been made, or a pointer to a newly
+ malloced buffer otherwise. */
+char*
+filter_node_name (char* name)
+{
+ char* newname;
+ int modified = 0;
+ char *s, *ns;
+
+ if (!name)
+ return NULL;
+ if (! (newname = calloc (strlen (name) + 1, sizeof (char))))
+ return name;
+
+ for (s = name, ns = newname;
+ *s != '\0';
+ s++, ns++)
+ {
+ if (*s == '/')
+ *ns = SUBST_SLASH, modified = 1;
+#ifdef SUBST_LOWER
+ else if (*s < 32)
+ *ns = SUBST_LOWER, modified = 1;
+#endif
+ else
+ *ns = *s;
+ }
+ *ns = *s;
+
+ if (!modified)
+ {
+ free (newname);
+ newname = name;
+ }
+
+ return newname;
+}
+
+/* Returns either NULL or a pointer to a node if found. */
+static inline struct node*
+_find_node (struct node *dir, char *name)
+{
+ struct node *node = NULL;
+
+ if (name)
+ {
+ /* Looking for '.' or '..'? */
+ if (name[0] == '.')
+ {
+ switch (name[1])
+ {
+ case '\0':
+ node = dir;
+ break;
+ case '.':
+ if (name[2] == '\0')
+ {
+ node = dir->nn->dir;
+ break;
+ }
+ }
+ }
+
+ if (!node)
+ {
+ /* Look for a "regular" node */
+ for (node = dir->nn->entries;
+ node != NULL;
+ node = node->next)
+ {
+ if (node->nn->name)
+ if (! strcmp (node->nn->name, name))
+ break;
+ }
+ }
+ }
+
+ return node;
+}
+
+struct node *
+fs_find_node (struct node *dir, char *name)
+{
+ return _find_node (dir, name);
+}
+
+/* Inserts a new node in directory DIR, with name NAME and mode M. If not NULL,
+ *N points to the newly created node.
+ NAME is *not* duplicated! */
+static inline error_t
+_make_node (struct node **n, struct node *dir, char* name, mode_t m)
+{
+ static ino_t id = 1;
+ io_statbuf_t st;
+ struct netnode *nn;
+ struct node* newnode = NULL;
+
+ /* Alloctes a new netnode */
+ nn = (struct netnode*) calloc (1, sizeof (struct netnode));
+ if (!nn)
+ return ENOMEM;
+ newnode = netfs_make_node (nn);
+ if (!newnode)
+ return ENOMEM;
+
+ /* General stat */
+ st.st_fstype = FSTYPE_TAR;
+ st.st_fsid = pid;
+ st.st_dev = st.st_rdev = pid; /* unique device id */
+ st.st_uid = st.st_author = uid;
+ st.st_gid = gid;
+ st.st_mode = m;
+ st.st_ino = id++; /* unique inode number for each fs node */
+ st.st_nlink = 1; /* number of subdir plus two, one otherwise. */
+ st.st_size = 0;
+ st.st_blksize = 1024; /* optimal block size for reading */
+ st.st_blocks = 1; /* XXX */
+ st.st_gen = 0;
+
+ if (S_ISDIR (m))
+ /* Set st_nlink to the number of subdirs plus 2 */
+ st.st_nlink = 2;
+
+ newnode->nn->name = filter_node_name (name);
+ newnode->nn->entries = NULL; /* ptr to the first entry of this node */
+ newnode->nn_stat = st;
+ newnode->nn_translated = m;
+
+ newnode->next = NULL;
+ newnode->prevp = NULL;
+
+ if (dir)
+ {
+ struct node *p;
+
+ /* Add a reference to DIR */
+ netfs_nref (dir);
+
+ /* Insert the new node *at the end* of the linked list of DIR entries. */
+ if (dir->nn->entries)
+ {
+ for (p = dir->nn->entries;
+ p->next;
+ p = p->next);
+ newnode->prevp = &p->next;
+ p->next = newnode;
+ }
+ else
+ {
+ newnode->prevp = &dir->nn->entries;
+ dir->nn->entries = newnode;
+ }
+
+#if 0
+ /* Insert the new node *at the beginning* of the linked list
+ of DIR entries. */
+ newnode->next = dir->nn->entries;
+ newnode->prevp = &dir->nn->entries;
+ dir->nn->entries = newnode;
+ if (newnode->next)
+ newnode->next->prevp = &newnode->next;
+#endif
+
+ newnode->nn->dir = dir;
+
+ /* Make sure that DIR is a directory. */
+ dir->nn_stat.st_mode |= S_IFDIR;
+
+ if (S_ISDIR (m))
+ /* Update DIR's hardlinks count */
+ dir->nn_stat.st_nlink++;
+ }
+
+ fshelp_touch (&newnode->nn_stat,
+ TOUCH_ATIME | TOUCH_CTIME | TOUCH_MTIME,
+ curr_time);
+
+ *n = newnode;
+
+ return 0;
+}
+
+/* Creates a new node in directory DIR, with name NAME (actually
+ a copy of NAME) and mode M. If not NULL, *N points to the newly
+ created node.
+ Checks whether there already exists such a node. */
+error_t
+fs_make_node (struct node **n, struct node *dir,
+ char* name, mode_t m)
+{
+ struct node* newnode = NULL;
+ error_t err = 0;
+
+ /* DIR == NULL means that we are creating NETFS_ROOT_NODE. */
+ if (dir)
+ newnode = _find_node (dir, name);
+
+ /* Creates a new one if not found. */
+ if (!newnode)
+ {
+ /* Make sure the filetype bits are set */
+ m = (m & S_IFMT) ? m : (m | S_IFREG);
+ name = name ? strdup (name) : NULL;
+ err = _make_node (&newnode, dir, name, m);
+ }
+ else
+ err = EEXIST;
+
+ /* Return a pointer to the newly created node. */
+ if (n)
+ *n = newnode;
+
+ return err;
+}
+
+/* Looks for a node located at PATH, starting at directory N.
+ When looking for "/foo/bar":
+ - if "/foo/bar" exists, a reference to it is returned in N and
+ the remaining parameters are set to NULL;
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo";
+ - if "/foo" exists but "/foo/bar" doesn't, then RETRY is NULL but NOTFOUND
+ is equal to "bar". */
+error_t
+fs_find_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path)
+{
+ struct node *node = NULL;
+ char *str, *pathstr;
+ char *name = NULL;
+
+ pathstr = str = strdup (path);
+
+ /* Lookup nodes. */
+ if (! *n)
+ *n = netfs_root_node;
+ node = *n;
+ name = strtok_r (pathstr, "/", &str);
+
+ while (node && name)
+ {
+ /* Lookup base node. */
+ node = _find_node (*n, name);
+ if (node)
+ {
+ name = strtok_r (NULL, "/", &str);
+ *n = node;
+ }
+ }
+
+ /* Did we parse the whole string? */
+ if (*str == '\0')
+ {
+ if (!node)
+ {
+ /* Yes, but we didn't find the very last node. */
+ assert (name != NULL);
+ assert (strlen (name) != 0);
+
+ *notfound = strdup (name);
+ *retry_name = NULL;
+ }
+ else
+ /* Yes, and we did find it. */
+ *notfound = *retry_name = NULL;
+ }
+ else
+ {
+ /* No, we stopped before the end of the string. */
+ assert (name != NULL);
+ assert (strlen (name) != 0);
+ *notfound = strdup (name);
+ *retry_name = strdup (str);
+ }
+
+ free (pathstr);
+
+ return 0;
+}
+
+/* Tries to create a node located at PATH, starting at directory N.
+ When creating "/foo/bar":
+ - if "/foo" exists and is a directory, "/foo/bar" is created and
+ a reference to it is returned; RETRY_NAME is NULL; N points to "/foo/bar".
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo". */
+error_t
+fs_make_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path, const mode_t m)
+{
+ struct node *updir = *n;
+
+ fs_find_node_path (&updir, retry_name, notfound, path);
+
+ /* If all parent dirs have been found, then create the new node. */
+ if (!*retry_name)
+ {
+ assert (*notfound != NULL);
+#ifdef DEBUG_FS
+ fprintf (stderr, "%s: Creating %s\n", __FUNCTION__, *notfound);
+#endif
+ fs_make_node (n, updir, *notfound, m);
+ /* Do *not* free *NOTFOUND. */
+
+ free (*notfound);
+ *notfound = NULL;
+ }
+
+ return 0;
+}
+
+/* Used to add a sub-directory to DIR. If SUBDIRNAME already exists in DIR,
+ returns the number of entries in it; otherwise creates it and returns
+ zero. NEWDIR points to DIR/SUBDIRNAME.
+ It also checks whether SUBDIRNAME already exists. */
+unsigned long
+fs_make_subdir (struct node **newdir, struct node *dir, char *subdirname)
+{
+ unsigned long nodenum = 0;
+ struct node *n, *p;
+
+ /* Look for an existing dir */
+ n = _find_node (dir, subdirname);
+
+ if (!n)
+ /* Create a new sub-directory. */
+ fs_make_node (&n, dir, subdirname, S_IFDIR|0555);
+ else
+ /* Compute the node number. */
+ for (p = n->nn->entries; p; p = p->next)
+ if (!S_ISDIR (p->nn_stat.st_mode))
+ nodenum++;
+
+ *newdir = n;
+ return nodenum;
+}
+
+
+/* Returns the path of a given node (relatively to the given root node).
+ This is a very funny function (see macro below). ;-) */
+char*
+fs_get_path_from_root (struct node *root, struct node *node)
+{
+#define REVERSE_COPY(dst, src) \
+ { int i; \
+ for (i=0; i < strlen ((src)); i++) \
+ (dst)[i] = src[strlen ((src)) - 1 - i]; \
+ (dst)[strlen ((src))] = '\0'; \
+ }
+
+ struct node *n;
+ size_t len = 256;
+ char *path;
+ char *ptr;
+
+ path = (char*)calloc(len, sizeof(char));
+ ptr = path;
+
+ for (n = node;
+ (n != root) && (n != NULL);
+ n = n->nn->dir)
+ {
+ /* Reallocate if necessary. */
+ if (strlen (path) + strlen (n->nn->name) + 1 + 1 > len)
+ {
+ char* new;
+ len *= 2;
+ new = realloc (path, len);
+ ptr = new + (ptr - path);
+ path = new;
+ }
+ REVERSE_COPY (ptr, n->nn->name);
+ ptr[strlen (n->nn->name)] = '/';
+ ptr += strlen (n->nn->name) + 1;
+ }
+
+ /* Remove trailing slash. */
+ if (strlen (path))
+ path[strlen (path) - 1] = '\0';
+
+ /* Reverse-copy the final result. */
+ ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
+ REVERSE_COPY (ptr, path);
+ free (path);
+
+ return ptr;
+}
+
+/* Returns the relavive path to the given root node. */
+char*
+fs_get_path_to_root (struct node *root, struct node *node)
+{
+ struct node *n;
+ size_t len = 256;
+ char *path;
+ char *ptr;
+
+ /* Go to the parent dir if NODE is not a directory. */
+ if (! (node->nn_stat.st_mode & S_IFDIR))
+ node = node->nn->dir;
+
+ path = (char*)calloc(len, sizeof(char));
+ ptr = path;
+
+ for (n = node;
+ (n != root) && (n != NULL);
+ n = n->nn->dir)
+ {
+ /* Reallocate if necessary. */
+ if (strlen (path) + 3 + 1 > len)
+ {
+ char* new;
+ len *= 2;
+ new = realloc (path, len);
+ ptr = new + (ptr - path);
+ path = new;
+ }
+ strncpy (ptr, "../", 3);
+ ptr += 3;
+ }
+
+ /* Remove last slash. */
+ assert (strlen (path) > 0);
+ path[strlen (path) - 1] = '\0';
+
+ /* Reverse-copy the final result. */
+ ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
+ strcpy (ptr, path);
+ free (path);
+
+ return ptr;
+}
+
+/* Gets the first common directory. */
+struct node*
+get_common_root (struct node *node1, struct node *node2)
+{
+#define MAX_PATH_DEPTH 256
+ struct node *n1 = node1, *n2 = node2;
+ struct node *path1[MAX_PATH_DEPTH];
+ struct node *path2[MAX_PATH_DEPTH];
+ int i1 = 0, i2 = 0;
+
+ if (n1 == n2)
+ return n1;
+
+ /* Save pathes to NETFS_ROOT_NODE in a stack. */
+ do
+ {
+ assert (i1 < MAX_PATH_DEPTH);
+ path1[i1++] = n1 = n1->nn->dir;
+ }
+ while (n1 != netfs_root_node);
+
+ do
+ {
+ assert (i2 < MAX_PATH_DEPTH);
+ path2[i2++] = n2 = n2->nn->dir;
+ }
+ while (n2 != netfs_root_node);
+
+ /* Get to the last common node. */
+ while (path1[--i1] == path2[--i2]);
+
+ return path1[++i1];
+}
+
+/* Makes NODE a symlink to TARGET, relatively to root directory ROOTDIR. */
+error_t
+fs_link_node (struct node *node, struct node *target)
+{
+ char *toroot, *tolink, *link;
+ struct node *rootdir;
+
+ /* Make it look like a symlink. */
+ node->nn_stat.st_mode |= S_IFLNK;
+ node->nn_translated |= S_IFLNK;
+
+ rootdir = get_common_root (node, target);
+ toroot = fs_get_path_to_root (rootdir, node);
+ tolink = fs_get_path_from_root (rootdir, target);
+ link = calloc (strlen (toroot) + 1 + strlen (tolink) + 1, sizeof (char));
+ sprintf (link, "%s/%s", toroot, tolink);
+
+ node->nn->symlink = link;
+ node->nn_stat.st_size = strlen (link);
+
+ return 0;
+}
+
+/* Turn NODE into a symbolic link to TARGET. */
+error_t
+fs_link_node_path (struct node *node, const char *target)
+{
+ /* Make it look like a symlink. */
+ node->nn_stat.st_mode |= S_IFLNK;
+ node->nn_translated |= S_IFLNK;
+
+ assert (node->nn);
+ node->nn->symlink = strdup (target);
+ node->nn_stat.st_size = strlen (target);
+
+ return 0;
+}
+
+/* Creates a new node NODE, in directory DIR, with name NAME and mode
+ M, hard linked to TARGET. */
+error_t
+fs_hard_link_node (struct node **node, struct node *dir, char* name,
+ const mode_t m, struct node *target)
+{
+ struct netnode *nn;
+ struct node* newnode = NULL;
+
+ /* Alloctes a new netnode */
+ nn = (struct netnode*) calloc (1, sizeof (struct netnode));
+ if (!nn)
+ return ENOMEM;
+ newnode = netfs_make_node (nn);
+ if (!newnode)
+ return ENOMEM;
+
+ /* Increase TARGET's hard links count. */
+ target->nn_stat.st_nlink++;
+ netfs_nref (target);
+
+ /* Copies netnode from TARGET (optional since only TARGET should be
+ accessed). */
+ newnode->nn_stat = target->nn_stat;
+ newnode->nn_stat.st_mode = m; /* FIXME: Should keep the upper bits. */
+ newnode->nn_translated = m;
+ newnode->nn_stat.st_nlink--; /* XXX: One less hard link? */
+ *newnode->nn = *target->nn;
+ newnode->nn->name = name;
+ newnode->next = NULL;
+ newnode->prevp = NULL;
+
+ /* Mark NEWNODE as a hard link to TARGET. */
+ newnode->nn->hardlink = target;
+
+ if (dir)
+ {
+ struct node *p;
+ netfs_nref (dir);
+
+ /* Insert the new node *at the end* of the linked list of DIR entries. */
+ if (dir->nn->entries)
+ {
+ for (p = dir->nn->entries;
+ p->next;
+ p = p->next);
+ newnode->prevp = &p;
+ p->next = newnode;
+ }
+ else
+ dir->nn->entries = newnode;
+
+ newnode->nn->dir = dir;
+
+ /* Make sure that DIR is a directory. */
+ dir->nn_stat.st_mode |= S_IFDIR;
+ }
+
+ fshelp_touch (&newnode->nn_stat,
+ TOUCH_ATIME|TOUCH_CTIME|TOUCH_MTIME, curr_time);
+
+ if (node)
+ *node = newnode;
+
+ return 0;
+}
+
+/* Unlink NODE *without* freeing its resources. */
+error_t
+fs_unlink_node (struct node *node)
+{
+ struct node *dir = node->nn->dir;
+ struct node *next = node->next;
+
+ if (node->nn->entries)
+ return ENOTEMPTY;
+
+ /* Check the number of hard links to NODE. */
+ if (S_ISDIR (node->nn_stat.st_mode))
+ {
+ if (node->nn_stat.st_nlink > 2)
+ return EBUSY;
+ }
+ else
+ {
+ if (node->nn_stat.st_nlink > 1)
+ return EBUSY;
+ }
+
+ /* PREVP should never be zero. */
+ assert (node->prevp);
+
+ /* Unlink NODE */
+ if (*node->prevp)
+ *node->prevp = next;
+
+ if (next)
+ next->prevp = node->prevp;
+
+ /* Decrease the reference count to the hardlink targets */
+ if (node->nn->hardlink)
+ {
+ node->nn->hardlink->nn_stat.st_nlink--;
+ netfs_nput (node->nn->hardlink);
+ }
+
+ /* Same for directories ('..' links to DIR) */
+ if (dir && S_ISDIR (node->nn_stat.st_mode))
+ dir->nn_stat.st_nlink--;
+
+ /* Finally, drop a reference to the node itself, which may result
+ in calling netfs_node_norefs (). */
+ netfs_nput (node);
+
+ /* Drop a reference from DIR */
+ netfs_nput (dir);
+
+ return 0;
+}
+
+/* Frees all memory associated to NODE (which is assumed to be already
+ unlinked) except its 'nn->info' field. */
+void
+fs_free_node (struct node *node)
+{
+ struct netnode *nn = node->nn;
+
+ assert (nn);
+
+ if (nn->name)
+ free (nn->name);
+ if (nn->symlink)
+ free (nn->symlink);
+
+ free (nn);
+ node->nn = NULL;
+}
diff --git a/fs.h b/fs.h
new file mode 100644
index 000000000..1f448e5f4
--- /dev/null
+++ b/fs.h
@@ -0,0 +1,123 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * General filesystem node management facilities.
+ */
+
+#ifndef __FS_H__
+#define __FS_H__
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+#include <fcntl.h>
+#include "backend.h"
+
+/* Initialization. */
+extern int fs_init ();
+
+/* The point of the following two accessors is to have something generic.
+ For instance, the first entry could be either DIR->NN->ENTRIES or
+ it could be the last element of the list starting at DIR->NN->ENTRIES. */
+
+/* Returns the first entry of directory DIR (i.e. the first entry which was
+ added to DIR). */
+extern error_t fs_dir_first_entry (struct node *dir, struct node **first);
+
+/* Returns the directory entry next to NODE (i.e. the entry which was added
+ right after NODE). This has to be consistent with _make_node ()/ */
+#define fs_dir_next_entry(Node) ((Node)->next)
+
+/* Return DIR's last entry. */
+extern error_t fs_dir_last_entry (struct node *dir, struct node **last);
+
+/* Returns either NULL or a pointer to a node if found. */
+extern struct node*
+fs_find_node (struct node *dir, char *name);
+
+/* Looks for a node located at PATH, starting at directory N.
+ When looking for "/foo/bar":
+ - if "/foo/bar" exists, a reference to it is returned in N and
+ the remaining parameters are set to NULL;
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo".
+ - if "/foo" exists but "/foo/bar" doesn't, then RETRY is NULL but NOTFOUND
+ is equal to "bar". */
+extern error_t
+fs_find_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path);
+
+/* Creates a new node in directory DIR, with name NAME (actually
+ a copy of NAME) and mode M. If not NULL, *N points to the newly
+ created node.
+ Checks whether there already exists such a node. */
+extern error_t fs_make_node (struct node **n, struct node *dir,
+ char* name, mode_t m);
+
+/* Tries to create a node located at PATH, starting at directory N.
+ When creating "/foo/bar":
+ - if "/foo" exists and is a directory, "/foo/bar" is created and
+ a reference to it is returned; RETRY_NAME is NULL; N points to "/foo/bar".
+ - if "/foo" doesn't exist, a reference to "/" is returned and RETRY_NAME
+ is set to "foo/bar" and NOTFOUND is set to "foo"; N points to "/foo". */
+extern error_t
+fs_make_node_path (struct node **n, char **retry_name, char **notfound,
+ const char *path, const mode_t m);
+
+/* Used to add a sub-directory to DIR. If SUBDIRNAME already exists in DIR,
+ returns the number of entries in it; otherwise creates it and returns
+ zero. NEWDIR points to DIR/SUBDIRNAME.
+ It also checks whether SUBDIRNAME already exists.
+ SUBDIRNAME is *not* duplicated! */
+extern unsigned long fs_make_subdir (struct node **newdir,
+ struct node *dir, char *subdirname);
+
+/* Makes NODE a symlink to TARGET, relatively to root directory ROOTDIR. */
+extern error_t fs_link_node (struct node *node, struct node *target);
+
+/* Turn NODE into a symbolic link to TARGET. */
+extern error_t fs_link_node_path (struct node *node, const char *target);
+
+/* Creates a new node NODE, in directory DIR, with name NAME and mode
+ M, hard linked to TARGET. */
+extern error_t
+fs_hard_link_node (struct node **node, struct node *dir, char* name,
+ const mode_t m, struct node *target);
+
+/* Returns the path of a given node (relatively to the given root node). */
+extern char* fs_get_path_from_root (struct node *root, struct node *node);
+
+/* Returns the relavive path to the given root node. */
+extern char* fs_get_path_to_root (struct node *root, struct node *node);
+
+/* Gets the first common directory. */
+extern struct node* get_common_root (struct node *node1, struct node *node2);
+
+/* Filters a node name, that is, remove '/' and chars lower than 32.
+ Returns NAME is no change has been made, or a pointer to a newly
+ malloced buffer otherwise. */
+extern char* filter_node_name (char* name);
+
+/* Unlink NODE *without* freeing its resources. */
+extern error_t fs_unlink_node (struct node *node);
+
+/* Frees all memory associated to NODE (which is assumed to be already
+ unlinked) except its 'nn->info' field. */
+extern void fs_free_node (struct node *node);
+
+#endif
diff --git a/main.c b/main.c
new file mode 100644
index 000000000..91b1fcbc4
--- /dev/null
+++ b/main.c
@@ -0,0 +1,76 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+#include <hurd/paths.h>
+#include <argp.h>
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <maptime.h>
+
+#include "backend.h"
+
+/* Choose the right backend here. */
+extern struct fs_backend tarfs_backend;
+struct fs_backend backend;
+
+/* The underlying node. */
+mach_port_t ul_node;
+
+/* Has to be defined for libnetfs... */
+int netfs_maxsymlinks = 2;
+
+/* Main. */
+int
+main (int argc, char **argv)
+{
+ struct argp fs_argp;
+ mach_port_t bootstrap_port;
+ struct iouser *user;
+ error_t err;
+
+ /* Defaults to tarfs. */
+ backend = tarfs_backend;
+
+ backend.get_argp (&fs_argp);
+ argp_parse (&fs_argp, argc, argv, 0, 0, 0);
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap_port);
+
+ /* Init netfs, the root_node and the backend, */
+ netfs_init ();
+ err = iohelp_create_simple_iouser (&user, getuid (), getgid ());
+ if (err)
+ error (1, err, "Cannot create iouser");
+
+ err = backend.init (&netfs_root_node, user);
+ if (err)
+ error (EXIT_FAILURE, err, "cannot create root node");
+ ul_node = netfs_startup (bootstrap_port, 0);
+
+ for (;;)
+ netfs_server_loop ();
+
+ /* Never reached. */
+ exit (0);
+}
diff --git a/names.c b/names.c
new file mode 100644
index 000000000..61abc615d
--- /dev/null
+++ b/names.c
@@ -0,0 +1,218 @@
+/* Look up user and/or group names.
+ Copyright (C) 1988, 1992 Free Software Foundation
+
+ From GNU Tar.
+
+ GNU Tar is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU Tar 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 Library General Public License for more details.
+
+ Namespace: finduname, finduid, findgname, findgid,
+ uid_to_uname, gid_to_gname.
+ */
+
+/*
+ * Look up user and/or group names.
+ *
+ * This file should be modified for non-unix systems to do something
+ * reasonable.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+#define TAR_NAMES
+#include "tar.h"
+#include "names.h"
+
+#include <stdio.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifndef TUNMLEN
+#define TUNMLEN 256
+#endif
+#ifndef TGNMLEN
+#define TGNMLEN 256
+#endif
+
+static int saveuid = -993;
+static char saveuname[TUNMLEN];
+static int my_uid = -993;
+
+static int savegid = -993;
+static char savegname[TGNMLEN];
+static int my_gid = -993;
+
+#define myuid ( my_uid < 0? (my_uid = getuid()): my_uid )
+#define mygid ( my_gid < 0? (my_gid = getgid()): my_gid )
+
+
+/* Make sure you link with the proper libraries if you are running the
+ Yellow Peril (thanks for the good laugh, Ian J.!), or, euh... NIS.
+ This code should also be modified for non-UNIX systems to do something
+ reasonable. */
+
+static char cached_uname[NAMSIZ] = "";
+static char cached_gname[NAMSIZ] = "";
+
+static uid_t cached_uid; /* valid only if cached_uname is not empty */
+static gid_t cached_gid; /* valid only if cached_gname is not empty */
+
+#if 0
+/* These variables are valid only if nonempty. */
+static char cached_no_such_uname[NAMSIZ] = "";
+static char cached_no_such_gname[NAMSIZ] = "";
+#endif
+
+/* These variables are valid only if nonzero. It's not worth optimizing
+ the case for weird systems where 0 is not a valid uid or gid. */
+static uid_t cached_no_such_uid = 0;
+static gid_t cached_no_such_gid = 0;
+
+
+/*
+ * Look up a user or group name from a uid/gid, maintaining a cache.
+ * FIXME, for now it's a one-entry cache.
+ * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
+ *
+ * This is ifdef'd because on Suns, it drags in about 38K of "yellow
+ * pages" code, roughly doubling the program size. Thanks guys.
+ */
+void finduname (char *uname, int uid)
+{
+ struct passwd *pw;
+#ifndef HAVE_GETPWUID
+ extern struct passwd *getpwuid ();
+#endif
+
+ if (uid != saveuid) {
+ saveuid = uid;
+ saveuname[0] = '\0';
+ pw = getpwuid (uid);
+ if (pw)
+ strncpy (saveuname, pw->pw_name, TUNMLEN);
+ }
+ strncpy (uname, saveuname, TUNMLEN);
+}
+
+int finduid (char *uname)
+{
+ struct passwd *pw;
+ extern struct passwd *getpwnam ();
+
+ if (uname[0] != saveuname[0]/* Quick test w/o proc call */
+ ||0 != strncmp (uname, saveuname, TUNMLEN)) {
+ strncpy (saveuname, uname, TUNMLEN);
+ pw = getpwnam (uname);
+ if (pw) {
+ saveuid = pw->pw_uid;
+ } else {
+ saveuid = myuid;
+ }
+ }
+ return saveuid;
+}
+
+
+void findgname (char *gname, int gid)
+{
+ struct group *gr;
+#ifndef HAVE_GETGRGID
+ extern struct group *getgrgid ();
+#endif
+
+ if (gid != savegid) {
+ savegid = gid;
+ savegname[0] = '\0';
+ (void) setgrent ();
+ gr = getgrgid (gid);
+ if (gr)
+ strncpy (savegname, gr->gr_name, TGNMLEN);
+ }
+ (void) strncpy (gname, savegname, TGNMLEN);
+}
+
+
+int findgid (char *gname)
+{
+ struct group *gr;
+ extern struct group *getgrnam ();
+
+ if (gname[0] != savegname[0]/* Quick test w/o proc call */
+ ||0 != strncmp (gname, savegname, TUNMLEN)) {
+ strncpy (savegname, gname, TUNMLEN);
+ gr = getgrnam (gname);
+ if (gr) {
+ savegid = gr->gr_gid;
+ } else {
+ savegid = mygid;
+ }
+ }
+ return savegid;
+}
+
+
+void
+uid_to_uname (uid_t uid, char uname[NAMSIZ])
+{
+ struct passwd *passwd;
+
+ if (uid != 0 && uid == cached_no_such_uid)
+ {
+ *uname = '\0';
+ return;
+ }
+
+ if (!cached_uname[0] || uid != cached_uid)
+ {
+ passwd = getpwuid (uid);
+ if (passwd)
+ {
+ cached_uid = uid;
+ strncpy (cached_uname, passwd->pw_name, NAMSIZ);
+ }
+ else
+ {
+ cached_no_such_uid = uid;
+ *uname = '\0';
+ return;
+ }
+ }
+ strncpy (uname, cached_uname, NAMSIZ);
+}
+
+void
+gid_to_gname (gid_t gid, char gname[NAMSIZ])
+{
+ struct group *group;
+
+ if (gid != 0 && gid == cached_no_such_gid)
+ {
+ *gname = '\0';
+ return;
+ }
+
+ if (!cached_gname[0] || gid != cached_gid)
+ {
+ group = getgrgid (gid);
+ if (group)
+ {
+ cached_gid = gid;
+ strncpy (cached_gname, group->gr_name, NAMSIZ);
+ }
+ else
+ {
+ cached_no_such_gid = gid;
+ *gname = '\0';
+ return;
+ }
+ }
+ strncpy (gname, cached_gname, NAMSIZ);
+}
diff --git a/names.h b/names.h
new file mode 100644
index 000000000..47d7bb34d
--- /dev/null
+++ b/names.h
@@ -0,0 +1,33 @@
+/* Look up user and/or group names.
+ Copyright (C) 1988, 1992 Free Software Foundation
+
+ From GNU Tar.
+
+ GNU Tar is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ GNU Tar 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 Library General Public License for more details.
+
+ Namespace: finduname, finduid, findgname, findgid,
+ uid_to_uname, gid_to_gname.
+ */
+
+#ifndef __TAR_NAMES__
+#define __TAR_NAMES__
+
+#include "tar.h"
+
+extern int finduid (char *name);
+extern void finduname (char *name, int uid);
+extern int findgid (char *name);
+extern void findgname (char *name, int gid);
+
+extern void uid_to_uname (uid_t uid, char uname[NAMSIZ]);
+extern void gid_to_gname (gid_t gid, char gname[NAMSIZ]);
+
+#endif
diff --git a/netfs.c b/netfs.c
new file mode 100644
index 000000000..97653efbf
--- /dev/null
+++ b/netfs.c
@@ -0,0 +1,834 @@
+/* tarfs interface to libnetfs.
+ Copyright (C) 2002 Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+#include <error.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include "backend.h"
+#include "debug.h"
+
+/* BACKEND is defined in main.c */
+extern struct fs_backend backend;
+
+/* The following flag may be defined in order to hide files
+ to users who do not own them. */
+#ifdef HIDE_FILES_NOT_OWNED
+#define OWNERSHIP(__node, __user) \
+ fshelp_isowner (&(__node)->nn_stat, (__user));
+#else
+# define OWNERSHIP(__node, __user) (0)
+#endif /* HIDE_FILES_NOT_OWNED */
+
+/* The user must define this function. Lookup NAME in DIR (which is
+ locked) for USER; set *NP to the found name upon return. If the
+ name was not found, then return ENOENT. On any error, clear *NP.
+ (*NP, if found, should be locked and a reference to it generated.
+ This call should unlock DIR no matter what.) */
+error_t
+netfs_attempt_lookup (struct iouser *user, struct node *dir,
+ char *name, struct node **np)
+{
+ error_t err = 0;
+
+ /* Lookups for "." and "..". */
+ if (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')) )
+ /* Make sure that DIR is an actual directory. */
+ if (S_ISDIR (dir->nn_stat.st_mode))
+ {
+ if (name[1] == '.')
+ *np = dir->nn->dir;
+ else
+ *np = dir;
+ }
+ else
+ {
+ *np = NULL;
+ err = ENOTDIR;
+ }
+ else
+ /* Regular nodes. */
+ err = backend.lookup_node (np, dir, name);
+
+ /* Create a reference to the node (and lock it); unlock DIR. */
+ if (!err && *np)
+ {
+ if (*np != dir)
+ mutex_lock (&(*np)->lock);
+
+ debug (("Node %s: %i references", name, (*np)->references));
+ netfs_nref (*np);
+ }
+
+ mutex_unlock (&dir->lock);
+
+ return err;
+}
+
+/* The user must define this function. Read the contents of locked
+ node NP (a symlink), for USER, into BUF. */
+error_t
+netfs_attempt_readlink (struct iouser *user, struct node *np,
+ char *buf)
+{
+ if ((!buf) || (!np->nn->symlink))
+ return EAGAIN; /* This should never happen. */
+
+ strcpy (buf, np->nn->symlink);
+
+ return 0;
+}
+
+/* The user must define this function. Locked node NP is being opened
+ by USER, with FLAGS. NEWNODE is nonzero if we just created this
+ node. Return an error if we should not permit the open to complete
+ because of a permission restriction. */
+error_t
+netfs_check_open_permissions (struct iouser *user, struct node *np,
+ int flags, int newnode)
+{
+ error_t err = OWNERSHIP (np, user);
+
+ if (! err && (flags & O_READ))
+ err = fshelp_access (&np->nn_stat, S_IREAD, user);
+ if (! err && (flags & O_WRITE))
+ err = fshelp_access (&np->nn_stat, S_IWRITE, user);
+ if (! err && (flags & O_EXEC))
+ err = fshelp_access (&np->nn_stat, S_IEXEC, user);
+
+ return err;
+}
+
+/* The user must define this function. Read from the locked file NP
+ for user CRED starting at OFFSET and continuing for up to *LEN
+ bytes. Put the data at DATA. Set *LEN to the amount successfully
+ read upon return. */
+error_t
+netfs_attempt_read (struct iouser *cred, struct node *np,
+ loff_t offset, size_t *len, void *data)
+{
+ return backend.read_node (np, offset, len, data);
+}
+
+
+/* The user must define this function. Write to the locked file NP
+ for user CRED starting at OFSET and continuing for up to *LEN bytes
+ from DATA. Set *LEN to the amount successfully written upon
+ return. */
+error_t
+netfs_attempt_write (struct iouser *cred, struct node *np,
+ loff_t offset, size_t *len, void *data)
+{
+ if (! backend.write_node)
+ return EROFS;
+ else
+ return backend.write_node (np, offset, len, data);
+}
+
+/* The user must define this function. Return the valid access
+ types (bitwise OR of O_READ, O_WRITE, and O_EXEC) in *TYPES for
+ locked file NODE and user CRED. */
+error_t
+netfs_report_access (struct iouser *cred, struct node *node,
+ int *types)
+{
+ error_t err = OWNERSHIP (node, cred);
+
+ /* FIXME: For a ro-fs, this should only set TYPES to O_READ. */
+ if (! err)
+ {
+ *types = 0;
+ if (fshelp_access (&node->nn_stat, S_IREAD, cred) == 0)
+ *types |= O_READ;
+ if (fshelp_access (&node->nn_stat, S_IWRITE, cred) == 0)
+ *types |= O_WRITE;
+ if (fshelp_access (&node->nn_stat, S_IEXEC, cred) == 0)
+ *types |= O_EXEC;
+ }
+
+ return err;
+}
+
+/* The user must define this function. Create a new user from the
+ specified UID and GID arrays. */
+struct iouser*
+netfs_make_user (uid_t *uids, int nuids,
+ uid_t *gids, int ngids)
+{
+ debug (("Not implemented"));
+ return NULL;
+}
+
+/* The user must define this function. Node NODE has no more references;
+ free all its associated storage. */
+void
+netfs_node_norefs (struct node *node)
+{
+ debug (("Entering"));
+
+ backend.free_node (node);
+}
+
+/* The user must define this function. Fill the array *DATA of size
+ BUFSIZE slast up to NENTRIES dirents from DIR (which is locked)
+ starting with entry ENTRY for user CRED. The number of entries in
+ the array is stored in *AMT and the number of bytes in *DATACNT.
+ If the supplied buffer is not large enough to hold the data, it
+ should be grown. */
+error_t
+netfs_get_dirents (struct iouser *cred, struct node *dir,
+ int entry, int nentries, char **data,
+ mach_msg_type_number_t *datacnt,
+ vm_size_t bufsize, int *amt)
+{
+ int curr_entry; /* current entry */
+ static int curr_amt;
+ struct dirent* curr_dirent;
+ char* curr_datap; /* current position in DATA */
+ int no_more = 0; /* no more entries? */
+
+ curr_amt = 0;
+ curr_datap = *data;
+
+ /* Start with entry ENTRY */
+ backend.set_curr_dir (dir);
+ no_more = backend.skip_entries (entry);
+
+ for (curr_entry = entry;
+ !no_more;
+ curr_entry++)
+ {
+ /* No limitiation when NENTRIES==-1. */
+ if ((nentries >= 0) && (curr_entry > entry+nentries))
+ no_more = 1;
+ else
+ no_more = backend.get_next_entry(&curr_dirent);
+
+ if (!no_more)
+ {
+#ifdef HIDE_FILES_NOT_OWNED
+ /* FIXME: We should do something here to avoid the ENOENT
+ during a dir_lookup () after a dir_readdir (). */
+#endif
+ curr_amt++;
+
+ /* Grow the buffer pointed to by DATA if necessary. */
+ if (((curr_datap - *data) + curr_dirent->d_reclen) > bufsize)
+ {
+ void* newdata;
+ size_t prev_size = bufsize;
+
+ /* Makes BUFSIZE a multiple of VM_PAGE_SIZE or double it. */
+ if (!bufsize)
+ bufsize = vm_page_size;
+ else
+ bufsize = (bufsize%vm_page_size) ?
+ ((bufsize/vm_page_size)+vm_page_size):
+ (bufsize*2);
+
+ newdata = mmap (0, bufsize, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS, 0, 0);
+ assert (newdata != (void*)-1);
+ assert (newdata != NULL);
+
+ if (newdata != *data)
+ {
+ size_t s = curr_datap - *data;
+ memcpy (newdata, (void*) *data, s);
+ curr_datap = (char*)newdata + s;
+
+ munmap (*data, prev_size);
+ *data = (char*)newdata;
+ }
+ }
+ assert (*data != NULL);
+
+ /* Copy CURR_DIRENT into DATA. */
+ memcpy (curr_datap, curr_dirent, curr_dirent->d_reclen);
+ curr_datap += curr_dirent->d_reclen;
+ munmap (curr_dirent, curr_dirent->d_reclen);
+ }
+ }
+
+#if 0
+ /* Deallocate if necessary. */
+ if (bufsize > (curr_datap - *data))
+ munmap (curr_datap, bufsize - (curr_datap - *data));
+#endif
+
+ /* Return */
+ *amt = curr_amt;
+ *datacnt = curr_datap - *data;
+
+ return 0;
+}
+
+/* The user may define this function. For a full description,
+ see hurd/hurd_types.h. The default response indicates a network
+ store. If the supplied buffers are not large enough, they should
+ be grown as necessary. NP is locked. */
+error_t
+netfs_file_get_storage_info (struct iouser *cred,
+ struct node *np,
+ mach_port_t **ports,
+ mach_msg_type_name_t *ports_type,
+ mach_msg_type_number_t *num_ports,
+ int **ints,
+ mach_msg_type_number_t *num_ints,
+ loff_t **offsets,
+ mach_msg_type_number_t *num_offsets,
+ char **data,
+ mach_msg_type_number_t *data_len)
+{
+#ifdef DEBUG
+ error (0, 0, __FUNCTION__);
+#endif
+ return EOPNOTSUPP;
+}
+
+/* The user must define this function. Make sure that NP->nn_stat is
+ filled with the most current information. CRED identifies the user
+ responsible for the operation. NP is locked. */
+error_t
+netfs_validate_stat (struct node *np, struct iouser *cred)
+{
+ return OWNERSHIP (np, cred);
+}
+
+/* The user must define this function. This should attempt a utimes
+ call for the user specified by CRED on locked node NP, to change
+ the atime to ATIME and the mtime to MTIME. If ATIME or MTIME is
+ null, then set to the current time. */
+error_t
+netfs_attempt_utimes (struct iouser *cred, struct node *np,
+ struct timespec *atime, struct timespec *mtime)
+{
+ error_t err = 0;
+
+ if (backend.change_stat)
+ {
+ int flags = TOUCH_CTIME;
+ io_statbuf_t st = np->nn_stat;
+
+ err = fshelp_isowner (&np->nn_stat, cred);
+
+ if (! err)
+ {
+ if (atime)
+ {
+ st.st_atime = atime->tv_sec;
+ st.st_atime_usec = atime->tv_nsec / 1000;
+ }
+ else
+ flags |= TOUCH_ATIME;
+
+ if (mtime)
+ {
+ st.st_mtime = mtime->tv_sec;
+ st.st_mtime_usec = mtime->tv_nsec / 1000;
+ }
+ else
+ flags |= TOUCH_MTIME;
+
+ err = backend.change_stat (np, &st);
+ }
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. This should attempt to set the
+ size of the locked file NP (for user CRED) to SIZE bytes long. */
+error_t
+netfs_attempt_set_size (struct iouser *cred, struct node *np,
+ loff_t size)
+{
+ error_t err = 0;
+
+ if (backend.change_stat)
+ {
+ io_statbuf_t st = np->nn_stat;
+ st.st_size = size;
+ backend.change_stat (np, &st);
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. This should attempt to fetch
+ filesystem status information for the remote filesystem, for the
+ user CRED. NP is locked. */
+error_t
+netfs_attempt_statfs (struct iouser *cred, struct node *np,
+ fsys_statfsbuf_t *st)
+{
+#ifdef DEBUG
+ error (0, 0, __FUNCTION__);
+#endif
+ return EOPNOTSUPP;
+}
+
+/* The user must define this function. This should sync the locked
+ file NP completely to disk, for the user CRED. If WAIT is set,
+ return only after the sync is completely finished. */
+error_t
+netfs_attempt_sync (struct iouser *cred, struct node *np,
+ int wait)
+{
+#ifdef DEBUG
+ error (0, 0, __FUNCTION__);
+#endif
+ return EOPNOTSUPP;
+}
+
+/* The user must define this function. This should sync the entire
+ remote filesystem. If WAIT is set, return only after the sync is
+ completely finished. */
+error_t
+netfs_attempt_syncfs (struct iouser *cred, int wait)
+{
+ error_t err = 0;
+
+ if (backend.sync_fs)
+ {
+ if (cred)
+ {
+ err = fshelp_isowner (&netfs_root_node->nn_stat, cred);
+ if (err)
+ return err;
+ err = backend.sync_fs (wait);
+ }
+ else
+ /* From libnetfs source code, CRED is set to zero in the fsys-goaway
+ stub, so we call go_away () here. */
+ if (backend.go_away)
+ err = backend.go_away (); /* This should call sync_fs () */
+ else
+ err = backend.sync_fs (wait);
+ }
+ else
+ err = EOPNOTSUPP;
+
+ return err;
+}
+
+/* The user may define this function. Attempt to set the passive
+ translator record for FILE to ARGZ (of length ARGZLEN) for user
+ CRED. NP is locked. */
+error_t
+netfs_set_translator (struct iouser *cred, struct node *np,
+ char *argz, size_t argzlen)
+{
+ return EOPNOTSUPP;
+}
+
+/* The user may define this function (but should define it together
+ with netfs_set_translator). For locked node NODE with S_IPTRANS
+ set in its mode, look up the name of its translator. Store the
+ name into newly malloced storage, and return it in *ARGZ; set
+ *ARGZ_LEN to the total length. */
+error_t
+netfs_get_translator (struct node *node, char **argz,
+ size_t *argz_len)
+{
+ *argz_len = 0;
+ *argz = (char*)malloc (sizeof (char));
+ (*argz)[0] = '\0';
+
+ return 0;
+}
+
+/* The user must define this function. This should attempt a chmod
+ call for the user specified by CRED on locked node NP, to change
+ the owner to UID and the group to GID. */
+error_t
+netfs_attempt_chown (struct iouser *cred, struct node *np,
+ uid_t uid, uid_t gid)
+{
+ error_t err = 0;
+
+ if (backend.change_stat)
+ {
+ io_statbuf_t st;
+
+ err = fshelp_isowner (&np->nn_stat, cred);
+
+ if (! err)
+ {
+ st = np->nn_stat;
+ st.st_uid = uid;
+ st.st_gid = gid;
+ err = backend.change_stat (np, &st);
+ }
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. This should attempt a chauthor
+ call for the user specified by CRED on locked node NP, thereby
+ changing the author to AUTHOR. */
+error_t
+netfs_attempt_chauthor (struct iouser *cred, struct node *np,
+ uid_t author)
+{
+ error_t err = 0;
+
+ if (backend.change_stat)
+ {
+ io_statbuf_t st;
+
+ err = fshelp_isowner (&np->nn_stat, cred);
+
+ if (! err)
+ {
+ st = np->nn_stat;
+ st.st_author = author;
+ err = backend.change_stat (np, &st);
+ }
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. This should attempt a chmod
+ call for the user specified by CRED on locked node NODE, to change
+ the mode to MODE. Unlike the normal Unix and Hurd meaning of
+ chmod, this function is also used to attempt to change files into
+ other types. If such a transition is attempted which is
+ impossible, then return EOPNOTSUPP. */
+error_t
+netfs_attempt_chmod (struct iouser *cred, struct node *np,
+ mode_t mode)
+{
+ error_t err = 0;
+
+ if (backend.change_stat)
+ {
+ io_statbuf_t st;
+
+ err = fshelp_isowner (&np->nn_stat, cred);
+
+ if (! err)
+ {
+ st = np->nn_stat;
+
+ if (mode & S_IFMT)
+ {
+ /* User wants to change file type, check whether this is possible */
+ if (S_ISDIR (st.st_mode) || S_ISDIR (mode))
+ {
+ /* Any->Dir and Dir->Any are forbidden transitions */
+ if ((st.st_mode & S_IFMT) != (mode & S_IFMT))
+ err = EOPNOTSUPP;
+ }
+ else
+ /* Let him do it */
+ st.st_mode = 0;
+ }
+ else
+ /* Only clear the permission bits */
+ st.st_mode &= ~S_ISPARE;
+
+ if (!err)
+ {
+ st.st_mode |= mode;
+ err = backend.change_stat (np, &st);
+ }
+ }
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. Attempt to turn locked node NP
+ (user CRED) into a symlink with target NAME. */
+error_t
+netfs_attempt_mksymlink (struct iouser *cred, struct node *np,
+ char *name)
+{
+ error_t err;
+
+ if (backend.symlink_node)
+ {
+ err = fshelp_isowner (&np->nn_stat, cred);
+ /* FIXME: Call fshelp_access () ?! */
+
+ if (! err)
+ err = backend.symlink_node (np, name);
+ }
+ else
+ err = EOPNOTSUPP;
+
+ return err;
+}
+
+/* The user must define this function. Attempt to turn NODE (user
+ CRED) into a device. TYPE is either S_IFBLK or S_IFCHR. NP is
+ locked. */
+error_t
+netfs_attempt_mkdev (struct iouser *cred, struct node *np,
+ mode_t type, dev_t indexes)
+{
+ error_t err;
+
+ if (backend.mkdev_node)
+ {
+ err = fshelp_isowner (&np->nn_stat, cred); /* FIXME: See above */
+
+ if (! err)
+ err = backend.mkdev_node (np, type, indexes);
+ }
+ else
+ err = EOPNOTSUPP;
+
+ return err;
+}
+
+/* The user must define this function. This should attempt a chflags
+ call for the user specified by CRED on locked node NP, to change
+ the flags to FLAGS. */
+error_t
+netfs_attempt_chflags (struct iouser *cred, struct node *np,
+ int flags)
+{
+ debug (("Not implemented"));
+ return EOPNOTSUPP;
+}
+
+/* The user must define this function. Delete NAME in DIR (which is
+ locked) for USER. */
+error_t
+netfs_attempt_unlink (struct iouser *user, struct node *dir,
+ char *name)
+{
+ error_t err;
+
+ if (backend.unlink_node)
+ {
+ struct node *node;
+
+ err = backend.lookup_node (&node, dir, name);
+ //usleep (500); /* FIXME!!! The incredible race condition! */
+
+ if (!err)
+ {
+ mutex_lock (&node->lock);
+
+ debug (("Node %s: %i references", name, node->references));
+ err = fshelp_isowner (&node->nn_stat, user);
+
+ if (!err)
+ err = backend.unlink_node (node);
+
+ mutex_unlock (&node->lock);
+ }
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+/* The user must define this function. Attempt to rename the
+ directory FROMDIR to TODIR. Note that neither of the specific nodes
+ are locked. */
+error_t
+netfs_attempt_rename (struct iouser *user, struct node *fromdir,
+ char *fromname, struct node *todir,
+ char *toname, int excl)
+{
+ debug (("FIXME: Not implemented"));
+ return EOPNOTSUPP;
+}
+
+/* The user must define this function. Attempt to create a new
+ directory named NAME in DIR (which is locked) for USER with mode
+ MODE. */
+error_t
+netfs_attempt_mkdir (struct iouser *user, struct node *dir,
+ char *name, mode_t mode)
+{
+ error_t err = fshelp_isowner (&dir->nn_stat, user);
+ struct node *newdir;
+
+ if (!backend.create_node)
+ err = EROFS;
+ else
+ err = backend.create_node (&newdir, dir, name, mode);
+
+ return err;
+}
+
+
+/* The user must define this function. Attempt to remove directory
+ named NAME in DIR (which is locked) for USER. */
+error_t
+netfs_attempt_rmdir (struct iouser *user, struct node *dir, char *name)
+{
+ /* Simply redirect the call */
+ return netfs_attempt_unlink (user, dir, name);
+}
+
+
+/* The user must define this function. Create a link in DIR with name
+ NAME to FILE for USER. Note that neither DIR nor FILE are
+ locked. If EXCL is set, do not delete the target. Return EEXIST if
+ NAME is already found in DIR. */
+error_t
+netfs_attempt_link (struct iouser *user, struct node *dir,
+ struct node *file, char *name, int excl)
+{
+ error_t err;
+
+ if (backend.link_node)
+ {
+ err = fshelp_isowner (&dir->nn_stat, user);
+ if (!err)
+ err = backend.link_node (dir, file, name, excl);
+ }
+ else
+ err = EROFS;
+
+ return err;
+}
+
+
+/* The user must define this function. Attempt to create an anonymous
+ file related to DIR (which is locked) for USER with MODE. Set *NP
+ to the returned file upon success. No matter what, unlock DIR. */
+error_t
+netfs_attempt_mkfile (struct iouser *user, struct node *dir,
+ mode_t mode, struct node **np)
+{
+ /* Redirect the call */
+ return netfs_attempt_create_file (user, dir, NULL, mode, np);
+}
+
+
+/* The user must define this function. Attempt to create a file named
+ NAME in DIR (which is locked) for USER with MODE. Set *NP to the
+ new node upon return. On any error, clear *NP. *NP should be
+ locked on success; no matter what, unlock DIR before returning. */
+error_t
+netfs_attempt_create_file (struct iouser *user, struct node *dir,
+ char *name, mode_t mode, struct node **np)
+{
+ error_t err = fshelp_isowner (&dir->nn_stat, user);
+
+ if (!backend.create_node)
+ {
+ err = EROFS;
+ *np = NULL;
+ }
+ else
+ {
+ /* Note: create_node () must handle nameless node creation
+ (see netfs_attempt_mkfile ()). */
+ err = backend.create_node (np, dir, name, mode);
+
+ /* Lock the new node and add a reference to it on success. */
+ if (!err && *np)
+ {
+ debug (("Node %s: %i references", name, (*np)->references));
+ mutex_lock (&(*np)->lock);
+ netfs_nref (*np);
+ }
+ }
+
+ mutex_unlock (&dir->lock);
+
+ return err;
+}
+
+/* Append to the malloced string *ARGZ of length *ARGZ_LEN a NUL-separated
+ list of the arguments to this translator. The default definition of this
+ routine simply calls netfs_append_std_options. */
+error_t
+netfs_append_args (char **argz, unsigned *argz_len)
+{
+ error_t err = 0;
+
+ if (backend.get_args)
+ err = backend.get_args (argz, argz_len);
+
+ return err;
+}
+
+/* Parse and execute the runtime options in ARGZ & ARGZ_LEN. EINVAL is
+ returned if some option is unrecognized. The default definition of this
+ routine will parse them using NETFS_RUNTIME_ARGP. */
+error_t
+netfs_set_options (char *argz, size_t argz_len)
+{
+ error_t err = EINVAL;
+
+ if (backend.set_options)
+ err = backend.set_options (argz, argz_len);
+
+ return err;
+}
+
+
+/* We need a particular syncfs stub that doesn't lock the NODE
+ which was passed to file_syncfs () since we want to lock it
+ in tarfs_sync_fs (). */
+error_t
+netfs_S_file_syncfs (struct protid *user,
+ int wait,
+ int dochildren)
+{
+ error_t err;
+
+ if (!user)
+ return EOPNOTSUPP;
+
+ err = netfs_attempt_syncfs (user->user, wait);
+
+ return err;
+}
+
+/* The following stub has been added as a reminder. */
+#if 0
+error_t
+netfs_S_io_map (struct protid *user,
+ mach_port_t *rdobj, mach_msg_type_name_t *rdobjtype,
+ mach_port_t *wrobj, mach_msg_type_name_t *wrobjtype)
+{
+ error (0, 0, "Warning: io_map () not supported");
+ return EOPNOTSUPP;
+}
+#endif
diff --git a/store-bzip2.c b/store-bzip2.c
new file mode 100644
index 000000000..879145e3c
--- /dev/null
+++ b/store-bzip2.c
@@ -0,0 +1,107 @@
+/* Gzip store backend.
+
+ Copyright (C) 1995,96,97,99,2000,01, 02 Free Software Foundation, Inc.
+ Written by Ludovic Courtes <ludo@type-z.org>
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd 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, or (at
+ your option) any later version.
+
+ The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <bzlib.h>
+#include <error.h>
+
+#include <hurd.h>
+#include <hurd/store.h>
+
+#include "zipstores.h"
+
+#ifndef DEBUG_ZIP
+# undef DEBUG
+#endif
+#include "debug.h"
+
+
+
+/* Convert a bzlib error into a libc error. */
+static inline error_t
+bzip2_error (bz_stream *stream, int zerr)
+{
+ error_t err;
+
+ assert (zerr != BZ_OUTBUFF_FULL);
+
+ switch (zerr)
+ {
+ case BZ_OK:
+ case BZ_RUN_OK:
+ case BZ_FLUSH_OK:
+ case BZ_FINISH_OK:
+ err = 0;
+ break;
+ case BZ_MEM_ERROR:
+ err = ENOMEM;
+ break;
+ case BZ_CONFIG_ERROR:
+ err = EFTYPE;
+ break;
+ case BZ_DATA_ERROR:
+ case BZ_DATA_ERROR_MAGIC:
+ case BZ_IO_ERROR:
+ err = EIO;
+ break;
+ case BZ_PARAM_ERROR:
+ err = EINVAL;
+ break;
+ case BZ_SEQUENCE_ERROR:
+ err = EINVAL;
+ break;
+ default:
+ err = EAGAIN; /* Shoudn't happen */
+ }
+
+ return err;
+}
+
+/* The following macros are defined to be then used by the zip store generic
+ code included below. */
+#define ZIP_TYPE bzip2
+
+#define ZIP_DECOMPRESS(Stream) BZ2_bzDecompress ((Stream))
+
+#define ZIP_DECOMPRESS_INIT(Stream) BZ2_bzDecompressInit ((Stream), 1, 0)
+
+#define ZIP_DECOMPRESS_END(Stream) BZ2_bzDecompressEnd ((Stream));
+
+#define ZIP_DECOMPRESS_RESET(Stream) \
+ ZIP_DECOMPRESS_END ((Stream)), ZIP_DECOMPRESS_INIT ((Stream))
+
+#define ZIP_COMPRESS(Stream) BZ2_bzCompress ((Stream), BZ_RUN)
+
+#define ZIP_COMPRESS_FINISH(Stream) BZ2_bzCompress ((Stream), BZ_FINISH)
+
+#define ZIP_COMPRESS_INIT(Stream) BZ2_bzCompressInit ((Stream), 4, 1, 0)
+
+#define ZIP_COMPRESS_END(Stream) BZ2_bzCompressEnd ((Stream))
+
+/* Constants */
+#define ZIP_STREAM bz_stream
+#define ZIP_STREAM_END BZ_STREAM_END
+
+#include "zipstores.c"
diff --git a/store-gzip.c b/store-gzip.c
new file mode 100644
index 000000000..66e479059
--- /dev/null
+++ b/store-gzip.c
@@ -0,0 +1,366 @@
+/* Gzip store backend.
+
+ Copyright (C) 1995,96,97,99,2000,01, 02 Free Software Foundation, Inc.
+ Written by Ludovic Courtes <ludovic.courtes@utbm.fr>
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd 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, or (at
+ your option) any later version.
+
+ The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <zlib.h>
+#include <error.h>
+
+#include <hurd.h>
+#include <hurd/store.h>
+
+#include "zipstores.h"
+
+#ifndef DEBUG_ZIP
+# undef DEBUG
+#endif
+#include "debug.h"
+
+/* A simple gzip header, inspired by zlib's gzio:check_header (). */
+struct gzip_header
+{
+ /* Regular header: 10 bytes */
+ char magic[2];
+ char method; /* Compression method */
+ char flags;
+ char unused[6]; /* time, xflags and OS code */
+};
+
+#define GZIP_HEADER_SIZE (sizeof (struct gzip_header))
+
+/* Gzip magic header */
+static char gzip_magic[2] = { 0x1f, 0x8b };
+
+/* Gzip flag byte */
+#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
+#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define COMMENT 0x10 /* bit 4 set: file comment present */
+#define RESERVED 0xE0 /* bits 5..7: reserved */
+
+
+static inline error_t gzip_error (z_stream *stream, int zerr);
+
+static error_t gzip_read_header (struct store *store,
+ store_offset_t *end_of_header,
+ struct gzip_header *hdr);
+
+static error_t gzip_write_header (error_t (* write) (char *buf, size_t amount));
+
+static error_t gzip_verify_crc (z_stream *stream, uLong crc);
+
+static error_t gzip_write_suffix (z_stream *stream, uLong crc,
+ error_t (* write) (char *buf, size_t amount));
+
+
+/* The following macros are defined to be then used by the zip store generic
+ code included below. */
+#define ZIP_TYPE gzip
+
+#define ZIP_DECOMPRESS(Stream) inflate ((Stream), Z_SYNC_FLUSH)
+
+/* windowBits is passed < 0 to tell that there is no zlib header.
+ Note that in this case inflate *requires* an extra "dummy" byte
+ after the compressed stream in order to complete decompression and
+ return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are
+ present after the compressed stream. */
+#define ZIP_DECOMPRESS_INIT(Stream) inflateInit2 ((Stream), -MAX_WBITS)
+
+#define ZIP_DECOMPRESS_END(Stream) inflateEnd ((Stream))
+
+#define ZIP_DECOMPRESS_RESET(Stream) inflateReset ((Stream))
+
+#define ZIP_COMPRESS(Stream) deflate ((Stream), Z_NO_FLUSH)
+
+#define ZIP_COMPRESS_FINISH(Stream) deflate ((Stream), Z_FINISH)
+
+/* windowBits is passed < 0 to suppress zlib header */
+#define ZIP_COMPRESS_INIT(Stream) deflateInit2 ((Stream), \
+ Z_DEFAULT_COMPRESSION, \
+ Z_DEFLATED, \
+ -MAX_WBITS, \
+ 8, \
+ Z_DEFAULT_STRATEGY)
+
+#define ZIP_COMPRESS_END(Stream) deflateEnd ((Stream))
+
+#define ZIP_CRC_UPDATE(Crc, Buf, Len) crc32 (Crc, Buf, Len)
+
+#define ZIP_CRC_VERIFY(Stream, Crc) gzip_verify_crc (Stream, Crc)
+
+/* Zlib constants */
+#define ZIP_HAS_HEADER
+#define ZIP_STREAM z_stream
+#define ZIP_STREAM_END Z_STREAM_END
+
+#include "zipstores.c"
+
+
+/* Convert a zlib error into a libc error. */
+static inline error_t
+gzip_error (z_stream *stream, int zerr)
+{
+ error_t err;
+
+ /* Z_BUF_ERROR should not happen since we try to always maintain
+ the `next_in' and `next_out' buffers up-to-date. */
+ assert (zerr != Z_BUF_ERROR);
+
+ if (stream->msg)
+ {
+ error (1, 0, "zlib error: %s", stream->msg);
+ free (stream->msg);
+ stream->msg = NULL;
+ }
+
+ switch (zerr)
+ {
+ case Z_OK:
+ return 0;
+ case Z_ERRNO:
+ err = errno;
+ break;
+ case Z_MEM_ERROR:
+ err = ENOMEM;
+ break;
+ case Z_VERSION_ERROR:
+ err = EFTYPE;
+ break;
+ case Z_STREAM_ERROR:
+ err = EINVAL;
+ break;
+ case Z_DATA_ERROR:
+ case Z_STREAM_END:
+ err = EIO;
+ break;
+ default:
+ err = EAGAIN; /* Shoudn't happen */
+ }
+
+ debug (("zlib error: %s", zError (zerr)));
+
+ return err;
+}
+
+
+/* Looks for a gzip header in STORE, starting at its beginning.
+ Returns the position of the first byte available after the header
+ in END_OF_HEADER, and returns the header read in HEADER. */
+static error_t
+gzip_read_header (struct store *store,
+ store_offset_t *end_of_header, struct gzip_header *hdr)
+{
+ error_t err;
+ char buf[ZIP_BUFSIZE];
+ char *p = buf;
+ size_t amount, index = 0;
+
+ /* Load next block and continue */
+ static inline
+ error_t read_next ()
+ {
+ error_t err;
+ size_t len;
+ err = store_simple_read (store, index * ZIP_BUFSIZE,
+ ZIP_BUFSIZE, buf, &len);
+ if (err)
+ return err;
+
+ index++;
+
+ if (len != ZIP_BUFSIZE)
+ return EIO;
+
+ p = buf;
+
+ return err;
+ }
+
+ /* Reads from STORE. */
+ err = store_simple_read (store, 0, ZIP_BUFSIZE, buf, &amount);
+ if (err)
+ return err;
+
+ p += GZIP_HEADER_SIZE;
+ memcpy (hdr, buf, GZIP_HEADER_SIZE);
+ *end_of_header = GZIP_HEADER_SIZE;
+
+ debug (("Gzip compression method: 0x%02x", hdr->method));
+
+ /* Make sure this is a gzip header. */
+ if (strncmp (hdr->magic, gzip_magic, sizeof (gzip_magic)))
+ {
+ error (0, 0, "Invalid gzip header");
+ return EFTYPE;
+ }
+
+ /* Parse the header and skip unused information. */
+ if (hdr->method != Z_DEFLATED || (hdr->flags & RESERVED) != 0)
+ {
+ return EIO; /* Z_DATA_ERROR */
+ }
+
+ if ((hdr->flags & EXTRA_FIELD) != 0)
+ { /* skip the extra field */
+ size_t size = 0;
+ size = *(p++);
+ size += (*(p++)) << 8;
+ debug (("gzip extra field size: %u", size));
+ /* size is garbage if EOF but the loop below will quit anyway */
+ while (size--)
+ if ((p++) - buf >= ZIP_BUFSIZE)
+ {
+ err = read_next ();
+ if (err)
+ return err;
+ }
+ }
+ if ((hdr->flags & ORIG_NAME) != 0)
+ { /* skip the original file name */
+ debug (("gzip origname: %s", p)); /* XXX: might segfault */
+ while (*p)
+ if ((p++) - buf >= ZIP_BUFSIZE)
+ {
+ err = read_next ();
+ if (err)
+ return err;
+ }
+
+ p++;
+ }
+ if ((hdr->flags & COMMENT) != 0)
+ { /* skip the .gz file comment */
+ debug (("gzip comment: %s", p)); /* XXX: might segfault */
+ while (*p)
+ if ((p++) - buf >= ZIP_BUFSIZE)
+ {
+ err = read_next ();
+ if (err)
+ return err;
+ }
+
+ p++;
+ }
+ if ((hdr->flags & HEAD_CRC) != 0)
+ { /* skip the header crc */
+ p += 2;
+ }
+
+ *end_of_header = p - buf + (index * ZIP_BUFSIZE);
+
+ return 0;
+}
+
+/* Compute a CRC and compare it with the last 4 bytes of the gzip file. */
+static error_t
+gzip_verify_crc (z_stream *stream, uLong crc)
+{
+ error_t err = 0;
+ uLong read_crc; /* The crc that we read from file */
+ Bytef *buf = stream->next_in;
+
+ if (stream->avail_in < 4)
+ {
+ error (0, 0, "Unexpected end of gzip file (no CRC)");
+ return EIO;
+ }
+
+ /* Check CRC first */
+ read_crc = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ if (read_crc != crc)
+ {
+ debug (("Invalid CRC: 0x%lx instead of 0x%lx", read_crc, crc));
+ error (0, 0, "Invalid gzip CRC");
+ err = EIO;
+ }
+ else
+ debug (("Valid gzip CRC"));
+
+ /* Check uncompressed stream length */
+ stream->next_in += 4, stream->avail_in -= 4,
+ buf = stream->next_in;
+ if (stream->avail_in < 4)
+ {
+ error (0, 0, "Unexpected end of gzip file (no length)");
+ return EIO;
+ }
+
+ read_crc = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ if (read_crc != stream->total_out)
+ {
+ debug (("Got length=%lu instead of %lu", read_crc, stream->total_out));
+ error (0, 0, "Invalid gzip file length");
+ err = EIO;
+ }
+ else
+ debug (("Valid gzip uncompressed stream size (%lu)", read_crc));
+
+ stream->next_in += 4, stream->avail_in -= 4;
+ if (stream->avail_in > 0)
+ {
+ debug (("%u bytes left", stream->avail_in));
+ error (0, 0, "Trailing characters at end of file");
+ }
+
+ return err;
+}
+
+/* Write a gzip suffix: CRC (4 bytes) and uncompressed stream length
+ (4 bytes). WRITE is called to actually write the suffix. Assume that
+ STREAM is opened for compression. */
+static error_t
+gzip_write_suffix (z_stream *stream, uLong crc,
+ error_t (* write) (char *buf, size_t amount))
+{
+ char buf[8];
+ size_t total = stream->total_in;
+
+ buf[0] = (crc & 0xff);
+ buf[1] = (crc >> 8) & 0xff;
+ buf[2] = (crc >> 16) & 0xff;
+ buf[3] = (crc >> 24) & 0xff;
+ buf[4] = (total & 0xff);
+ buf[5] = (total >> 8) & 0xff;
+ buf[6] = (total >> 16) & 0xff;
+ buf[7] = (total >> 24) & 0xff;
+
+ return write (buf, 8);
+}
+
+/* Write a simple gzip header. WRITE is the method called to actually
+ write the header. Note: The header format being almost
+ completely undocumented: FIXME. */
+static error_t
+gzip_write_header (error_t (* write) (char *buf, size_t amount))
+{
+ struct gzip_header hdr;
+
+ bzero (&hdr, GZIP_HEADER_SIZE);
+ hdr.magic[0] = gzip_magic[0];
+ hdr.magic[1] = gzip_magic[1];
+ hdr.method = Z_DEFLATED;
+
+ return write ((char *)&hdr, GZIP_HEADER_SIZE);
+}
diff --git a/tar.c b/tar.c
new file mode 100644
index 000000000..0b38028fe
--- /dev/null
+++ b/tar.c
@@ -0,0 +1,501 @@
+/* GNU tar files parsing.
+ Copyright (C) 1995 The Free Software Foundation
+
+ Written by: 1995 Jakub Jelinek
+ Rewritten by: 1998 Pavel Machek
+ Modified by: 2002 Ludovic Courtes (for the Hurd tarfs)
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library 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 Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <error.h>
+#include <time.h>
+#include <string.h>
+#include <hurd/netfs.h>
+#include <hurd/store.h>
+
+#include "tar.h"
+#include "names.h"
+
+#include "debug.h"
+
+/* A hook which is called each time a header has been parsed. */
+int (*tar_header_hook) (tar_record_t *, off_t) = NULL;
+
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+#ifndef isspace
+# define isspace(c) ( (c) == ' ' )
+#endif
+
+/*
+ * Quick and dirty octal conversion.
+ *
+ * Result is -1 if the field is invalid (all blank, or nonoctal).
+ */
+static long
+from_oct (int digs, char *where)
+{
+ register long value;
+
+ while (isspace (*where))
+ { /* Skip spaces */
+ where++;
+ if (--digs <= 0)
+ return -1; /* All blank field */
+ }
+ value = 0;
+ while (digs > 0 && isodigit (*where))
+ { /* Scan till nonoctal */
+ value = (value << 3) | (*where++ - '0');
+ --digs;
+ }
+
+ if (digs > 0 && *where && !isspace (*where))
+ return -1; /* Ended on non-space/nul */
+
+ return value;
+}
+
+/* As we open one archive at a time, it is safe to have this static */
+static store_offset_t current_tar_position = 0;
+
+static tar_record_t rec_buf;
+
+
+static tar_record_t *
+get_next_record (struct store *tar_file)
+{
+ error_t err;
+ size_t n;
+ void *buf;
+
+ debug (("Reading at offset %lli", current_tar_position));
+ buf = rec_buf.charptr;
+ err = store_read (tar_file, current_tar_position, RECORDSIZE, &buf, &n);
+
+ if (err)
+ error (1, err, "Read error (offset=%lli)", current_tar_position);
+ assert (n <= RECORDSIZE);
+
+ if (buf != rec_buf.charptr)
+ {
+ memcpy (rec_buf.charptr, buf, n);
+ munmap (buf, n);
+ }
+
+ if (n != RECORDSIZE)
+ return NULL; /* An error has occurred */
+
+ current_tar_position += n;
+
+ return &rec_buf;
+}
+
+static void
+skip_n_records (struct store *tar_file, int n)
+{
+ current_tar_position += n * RECORDSIZE;
+}
+
+void
+tar_header2stat (io_statbuf_t *st, tar_record_t *header)
+{
+ st->st_mode = from_oct (8, header->header.mode);
+
+ /* Adjust st->st_mode because there are tar-files with
+ * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
+ * know about the other modes but I think I cause no new
+ * problem when I adjust them, too. -- Norbert.
+ */
+ if (header->header.linkflag == LF_DIR)
+ {
+ st->st_mode |= S_IFDIR;
+ }
+ else if (header->header.linkflag == LF_SYMLINK)
+ {
+ st->st_mode |= S_IFLNK;
+ }
+ else if (header->header.linkflag == LF_CHR)
+ {
+ st->st_mode |= S_IFCHR;
+ }
+ else if (header->header.linkflag == LF_BLK)
+ {
+ st->st_mode |= S_IFBLK;
+ }
+ else if (header->header.linkflag == LF_FIFO)
+ {
+ st->st_mode |= S_IFIFO;
+ }
+ else
+ st->st_mode |= S_IFREG;
+
+ st->st_rdev = 0;
+ if (!strcmp (header->header.magic, TMAGIC))
+ {
+ st->st_uid = *header->header.uname ? finduid (header->header.uname) :
+ from_oct (8, header->header.uid);
+ st->st_gid = *header->header.gname ? findgid (header->header.gname) :
+ from_oct (8, header->header.gid);
+ switch (header->header.linkflag)
+ {
+ case LF_BLK:
+ case LF_CHR:
+ st->st_rdev = (from_oct (8, header->header.devmajor) << 8) |
+ from_oct (8, header->header.devminor);
+ }
+ }
+ else
+ { /* Old Unix tar */
+ st->st_uid = from_oct (8, header->header.uid);
+ st->st_gid = from_oct (8, header->header.gid);
+ }
+ //st->st_size = hstat.st_size;
+ st->st_size = from_oct (1 + 12, header->header.size);
+ st->st_mtime = from_oct (1 + 12, header->header.mtime);
+ st->st_atime = from_oct (1 + 12, header->header.atime);
+ st->st_ctime = from_oct (1 + 12, header->header.ctime);
+}
+
+
+typedef enum
+{
+ STATUS_BADCHECKSUM,
+ STATUS_SUCCESS,
+ STATUS_EOFMARK,
+ STATUS_EOF,
+}
+ReadStatus;
+/*
+ * Return 1 for success, 0 if the checksum is bad, EOF on eof,
+ * 2 for a record full of zeros (EOF marker).
+ *
+ */
+static ReadStatus
+read_header (struct store *tar_file)
+{
+ register int i;
+ register long sum, signed_sum, recsum;
+ register char *p;
+ register tar_record_t *header;
+ char *data;
+ int size, written;
+ static char *next_lonname = NULL, *next_lonlink = NULL;
+ char *current_file_name, *current_link_name;
+ struct stat hstat; /* Stat struct corresponding */
+
+
+recurse:
+
+ header = get_next_record (tar_file);
+ if (NULL == header)
+ return STATUS_EOF;
+
+ recsum = from_oct (8, header->header.chksum);
+
+ sum = 0;
+ signed_sum = 0;
+ p = header->charptr;
+ for (i = sizeof (*header); --i >= 0;)
+ {
+ /*
+ * We can't use unsigned char here because of old compilers,
+ * e.g. V7.
+ */
+ signed_sum += *p;
+ sum += 0xFF & *p++;
+ }
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof (header->header.chksum); --i >= 0;)
+ {
+ sum -= 0xFF & header->header.chksum[i];
+ signed_sum -= (char) header->header.chksum[i];
+ }
+ sum += ' ' * sizeof header->header.chksum;
+ signed_sum += ' ' * sizeof header->header.chksum;
+
+ /*
+ * This is a zeroed record...whole record is 0's except
+ * for the 8 blanks we faked for the checksum field.
+ */
+ if (sum == 8 * ' ')
+ return STATUS_EOFMARK;
+
+ if (sum != recsum && signed_sum != recsum)
+ return STATUS_BADCHECKSUM;
+
+ /*
+ * linkflag on BSDI tar (pax) always '\000'
+ */
+ if (header->header.linkflag == '\000' &&
+ strlen (header->header.arch_name) &&
+ header->header.arch_name[strlen (header->header.arch_name) - 1] == '/')
+ header->header.linkflag = LF_DIR;
+
+ /*
+ * Good record. Decode file size and return.
+ */
+ if (header->header.linkflag == LF_LINK || header->header.linkflag == LF_DIR)
+ hstat.st_size = 0; /* Links 0 size on tape */
+ else
+ hstat.st_size = from_oct (1 + 12, header->header.size);
+
+ header->header.arch_name[NAMSIZ - 1] = '\0';
+ if (header->header.linkflag == LF_LONGNAME
+ || header->header.linkflag == LF_LONGLINK)
+ {
+ for (size = hstat.st_size; size > 0; size -= written)
+ {
+ data = get_next_record (tar_file)->charptr;
+ if (data == NULL)
+ {
+ error (0, 0, "Unexpected EOF on archive file");
+ return STATUS_BADCHECKSUM;
+ }
+ written = RECORDSIZE;
+ if (written > size)
+ written = size;
+ }
+ goto recurse;
+ }
+ else
+ {
+ long data_position;
+ char *p, *q;
+ int len;
+ int isdir = 0;
+
+ current_file_name = (next_lonname
+ ? next_lonname
+ : strdup (header->header.arch_name));
+ len = strlen (current_file_name);
+ if (current_file_name[len - 1] == '/')
+ {
+ current_file_name[len - 1] = 0;
+ isdir = 1;
+ }
+
+ current_link_name = (next_lonlink
+ ? next_lonlink
+ : strdup (header->header.arch_linkname));
+ len = strlen (current_link_name);
+ if (len && current_link_name[len - 1] == '/')
+ current_link_name[len - 1] = 0;
+
+ next_lonlink = next_lonname = NULL;
+
+ data_position = current_tar_position;
+
+ p = strrchr (current_file_name, '/');
+ if (p == NULL)
+ {
+ p = current_file_name;
+ q = current_file_name + strlen (current_file_name); /* "" */
+ }
+ else
+ {
+ *(p++) = 0;
+ q = current_file_name;
+ }
+
+ if (tar_header_hook)
+ tar_header_hook (header, current_tar_position);
+
+ free (current_file_name);
+
+/* done: */
+ if (header->header.isextended)
+ {
+ while (get_next_record (tar_file)->ext_hdr.isextended);
+ }
+ skip_n_records (tar_file, (hstat.st_size + RECORDSIZE - 1) / RECORDSIZE);
+ return STATUS_SUCCESS;
+ }
+}
+
+/*
+ * Main loop for reading an archive.
+ * Returns 0 on success, -1 on error.
+ */
+int
+tar_open_archive (struct store *tar_file)
+{
+ ReadStatus status = STATUS_EOFMARK; /* Initial status at start of archive */
+ ReadStatus prev_status = STATUS_SUCCESS;
+
+ current_tar_position = 0;
+
+ for (;;)
+ {
+ prev_status = status;
+ status = read_header (tar_file);
+
+
+ switch (status)
+ {
+
+ case STATUS_SUCCESS:
+ continue;
+
+ /*
+ * Invalid header:
+ *
+ * If the previous header was good, tell them
+ * that we are skipping bad ones.
+ */
+ case STATUS_BADCHECKSUM:
+ switch (prev_status)
+ {
+
+ /* Error on first record */
+ case STATUS_EOFMARK:
+ return -1;
+ /* FALL THRU */
+
+ /* Error after header rec */
+ case STATUS_SUCCESS:
+ prev_status = status;
+ {
+ /* FIXME: Bad hack */
+ size_t size;
+ tar_record_t *hdr;
+ current_tar_position -= RECORDSIZE;
+ error (0, 0, "Skipping to next header (offset=%lli)",
+ current_tar_position);
+ hdr = get_next_record (tar_file);
+ size = from_oct (8, hdr->header.size);
+ size = size % RECORDSIZE
+ ? size / RECORDSIZE
+ : (size / RECORDSIZE) + RECORDSIZE;
+ current_tar_position += size;
+ }
+
+ /* Error after error */
+
+ case STATUS_BADCHECKSUM:
+ error (1, 0, "Bad checksum (offset=%lli)", current_tar_position);
+ return -1;
+
+ case STATUS_EOF:
+ return 0;
+ }
+
+ /* Record of zeroes */
+ case STATUS_EOFMARK:
+ status = prev_status; /* If error after 0's */
+ /* FALL THRU */
+
+ case STATUS_EOF: /* End of archive */
+ break;
+ }
+ break;
+ };
+ return 0;
+}
+
+
+/* Create a tar header based on ST and NAME where NAME is a path.
+ If NAME is a hard link (resp. symlink), HARDLINK (resp.
+ SYMLINK) is the path of NAME's target.
+ Also see GNU tar's create.c:start_header() (Ludovic). */
+void
+tar_make_header (tar_record_t *header, io_statbuf_t *st, char *name,
+ char *symlink, char *hardlink)
+{
+ int i;
+ long sum = 0;
+ char *p;
+
+ /* NAME must be at most NAMSIZ long. */
+ assert (strlen (name) <= NAMSIZ);
+
+ bzero (header, sizeof (* header));
+ strcpy (header->header.arch_name, name);
+
+ /* If it's a dir, add a trailing '/' */
+ if (S_ISDIR (st->st_mode))
+ {
+ size_t s = strlen (name);
+ if (s + 1 <= NAMSIZ + 1)
+ {
+ header->header.arch_name[s] = '/';
+ header->header.arch_name[s+1] = '\0';
+ }
+ }
+
+#define TO_OCT(what, where) \
+ sprintf (where, "%07o", what);
+#define LONG_TO_OCT(what, where) \
+ sprintf (where, "%011llo", what);
+#define TIME_TO_OCT(what, where) \
+ sprintf (where, "%011lo", what);
+
+ TO_OCT (st->st_mode, header->header.mode);
+ TO_OCT (st->st_uid, header->header.uid);
+ TO_OCT (st->st_gid, header->header.gid);
+ LONG_TO_OCT (st->st_size, header->header.size);
+ TIME_TO_OCT (st->st_mtime, header->header.mtime);
+
+ /* Set the correct file type. */
+ if (S_ISREG (st->st_mode))
+ header->header.linkflag = LF_NORMAL;
+ else if (S_ISDIR (st->st_mode))
+ header->header.linkflag = LF_DIR;
+ else if (S_ISLNK (st->st_mode))
+ {
+ assert (symlink);
+ assert (strlen (symlink) <= NAMSIZ);
+ header->header.linkflag = LF_SYMLINK;
+ memcpy (header->header.arch_linkname, symlink, strlen (symlink));
+ }
+ else if (S_ISCHR (st->st_mode))
+ header->header.linkflag = LF_CHR;
+ else if (S_ISBLK (st->st_mode))
+ header->header.linkflag = LF_BLK;
+ else if (S_ISFIFO (st->st_mode))
+ header->header.linkflag = LF_FIFO;
+ else if (hardlink)
+ {
+ assert (strlen (hardlink) <= NAMSIZ);
+ header->header.linkflag = LF_LINK;
+ memcpy (header->header.arch_linkname, hardlink, strlen (hardlink));
+ }
+ else
+ header->header.linkflag = LF_NORMAL;
+
+ strncpy (header->header.magic, TMAGIC, TMAGLEN);
+
+ uid_to_uname (st->st_uid, header->header.uname);
+ gid_to_gname (st->st_gid, header->header.gname);
+
+ /* Compute a checksum for this header. */
+ strncpy (header->header.chksum, CHKBLANKS, sizeof (header->header.chksum));
+ p = header->charptr;
+ for (sum = 0, i = sizeof (*header); --i >= 0;)
+ sum += 0xFF & *p++;
+
+ sprintf (header->header.chksum, "%06lo", sum);
+ header->header.chksum[6] = '\0';
+}
diff --git a/tar.h b/tar.h
new file mode 100644
index 000000000..860ad6f0b
--- /dev/null
+++ b/tar.h
@@ -0,0 +1,185 @@
+/* Declarations for the tarfs.
+ Copyright (C) 1995 The Free Software Foundation
+
+ Written by: 1995 Jakub Jelinek
+
+ 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, 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; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef __TAR_H__
+#define __TAR_H__
+
+#if 0
+#include "testpad.h"
+#else
+#define NEEDPAD
+#endif
+
+#include <sys/types.h>
+
+/* major() and minor() macros (among other things) defined here for hpux */
+#ifdef hpux
+#include <sys/mknod.h>
+#endif
+
+/*
+ * Header block on tape.
+ *
+ * I'm going to use traditional DP naming conventions here.
+ * A "block" is a big chunk of stuff that we do I/O on.
+ * A "record" is a piece of info that we care about.
+ * Typically many "record"s fit into a "block".
+ */
+#define RECORDSIZE 512
+#define NAMSIZ 100
+#define TUNMLEN 32
+#define TGNMLEN 32
+#define SPARSE_EXT_HDR 21
+#define SPARSE_IN_HDR 4
+
+struct sparse {
+ char offset[12];
+ char numbytes[12];
+};
+
+struct sp_array {
+ int offset;
+ int numbytes;
+};
+
+union record {
+ char charptr[RECORDSIZE];
+ struct header {
+ char arch_name[NAMSIZ];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char chksum[8];
+ char linkflag;
+ char arch_linkname[NAMSIZ];
+ char magic[8];
+ char uname[TUNMLEN];
+ char gname[TGNMLEN];
+ char devmajor[8];
+ char devminor[8];
+ /* these following fields were added by JF for gnu */
+ /* and are NOT standard */
+ char atime[12];
+ char ctime[12];
+ char offset[12];
+ char longnames[4];
+#ifdef NEEDPAD
+ char pad;
+#endif
+ struct sparse sp[SPARSE_IN_HDR];
+ char isextended;
+ char realsize[12]; /* true size of the sparse file */
+ /* char ending_blanks[12];*//* number of nulls at the
+ end of the file, if any */
+ } header;
+ struct extended_header {
+ struct sparse sp[21];
+ char isextended;
+ } ext_hdr;
+};
+
+/* The checksum field is filled with this while the checksum is computed. */
+#define CHKBLANKS " " /* 8 blanks, no null */
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar " /* 7 chars and a null */
+#define TMAGLEN 6
+#define TVERSION "00" /* 00 and no null */
+#define TVERSLEN 2
+
+/* The linkflag defines the type of file */
+#define LF_OLDNORMAL '\0' /* Normal disk file, Unix compat */
+#define LF_NORMAL '0' /* Normal disk file */
+#define LF_LINK '1' /* Link to previously dumped file */
+#define LF_SYMLINK '2' /* Symbolic link */
+#define LF_CHR '3' /* Character special file */
+#define LF_BLK '4' /* Block special file */
+#define LF_DIR '5' /* Directory */
+#define LF_FIFO '6' /* FIFO special file */
+#define LF_CONTIG '7' /* Contiguous file */
+/* Further link types may be defined later. */
+
+/* Note that the standards committee allows only capital A through
+ capital Z for user-defined expansion. This means that defining something
+ as, say '8' is a *bad* idea. */
+#define LF_DUMPDIR 'D' /* This is a dir entry that contains
+ the names of files that were in
+ the dir at the time the dump
+ was made */
+#define LF_LONGLINK 'K' /* Identifies the NEXT file on the tape
+ as having a long linkname */
+#define LF_LONGNAME 'L' /* Identifies the NEXT file on the tape
+ as having a long name. */
+#define LF_MULTIVOL 'M' /* This is the continuation
+ of a file that began on another
+ volume */
+#define LF_NAMES 'N' /* For storing filenames that didn't
+ fit in 100 characters */
+#define LF_SPARSE 'S' /* This is for sparse files */
+#define LF_VOLHDR 'V' /* This file is a tape/volume header */
+/* Ignore it on extraction */
+
+#define LF_TRANS 'T' /* GNU/Hurd passive translator */
+
+/*
+ * Exit codes from the "tar" program
+ */
+#define EX_SUCCESS 0 /* success! */
+#define EX_ARGSBAD 1 /* invalid args */
+#define EX_BADFILE 2 /* invalid filename */
+#define EX_BADARCH 3 /* bad archive */
+#define EX_SYSTEM 4 /* system gave unexpected error */
+#define EX_BADVOL 5 /* Special error code means
+ Tape volume doesn't match the one
+ specified on the command line */
+
+/*
+ * We default to Unix Standard format rather than 4.2BSD tar format.
+ * The code can actually produce all three:
+ * f_standard ANSI standard
+ * f_oldarch V7
+ * neither 4.2BSD
+ * but we don't bother, since 4.2BSD can read ANSI standard format anyway.
+ * The only advantage to the "neither" option is that we can cmp our
+ * output to the output of 4.2BSD tar, for debugging.
+ */
+#define f_standard (!f_oldarch)
+
+
+/* The following was added by Ludovic for the GNU Hurd's tarfs. */
+#include <hurd/store.h>
+#include <hurd.h>
+#include "tarfs.h"
+
+typedef union record tar_record_t;
+
+extern int tar_open_archive (struct store *tar_file);
+extern void tar_header2stat (io_statbuf_t *st, tar_record_t *header);
+
+/* Create a tar header based on ST and NAME where NAME is a path.
+ If NAME is a hard link (resp. symlink), HARDLINK (resp.
+ SYMLINK) is the path of NAME's target.
+ Also see GNU tar's create.c:start_header() (Ludovic). */
+void tar_make_header (tar_record_t *header, io_statbuf_t *st, char *name,
+ char *symlink, char *hardlink);
+
+
+#endif
diff --git a/tarfs.c b/tarfs.c
new file mode 100644
index 000000000..dc163d779
--- /dev/null
+++ b/tarfs.c
@@ -0,0 +1,1364 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, 2003 Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+#include <hurd.h>
+#include <hurd/netfs.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <error.h>
+#include <sys/mman.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <argp.h>
+#include <argz.h>
+
+#include "backend.h"
+#include "tarfs.h"
+#include "fs.h"
+#include "cache.h"
+#include "zipstores.h"
+#include "debug.h"
+
+/* New netfs variables */
+char *netfs_server_name = "tarfs";
+char *netfs_server_version = "(rw-alpha)";
+
+/* Filesystem options */
+struct tarfs_opts tarfs_options;
+const char *argp_program_version =
+ "tarfs(rw-alpha) for the GNU Hurd (compiled: " __DATE__ ")";
+
+/* Argp data */
+const char *argp_program_bug_address = "Ludovic Courtès <ludo@type-z.org>";
+const char *args_doc = "ARCHIVE";
+const char *doc = "Hurd tar filesystem:\n"
+ "parses a tar archive and creates the corresponding filesystem\n";
+
+const struct argp_option fs_options[] =
+{
+#ifdef DEBUG
+ { "debug", 'D', "FILE", 0, "Print debug output to FILE" },
+#endif
+ { "gzip", 'z', NULL, 0, "Archive file is gzipped" },
+ { "bzip2", 'j', NULL, 0, "Archive file is bzip2'd" },
+ { "no-timeout", 't', NULL, 0, "Parse file in a separate thread "
+ "(thus avoiding startup timeouts)" },
+ { "readonly", 'r', NULL, 0, "Start tarfs read-only" },
+ { "writable", 'w', NULL, 0, "Start tarfs writable (default)" },
+ { "volatile", 'v', NULL, 0, "Start tarfs volatile "
+ "(ie writable but not synced)" },
+ { "create", 'c', NULL, 0, "Create tar file if not there" },
+#if 0
+ { "sync", 's', "INTERVAL", 0, "Sync all data not actually written "
+ "to disk every INTERVAL seconds (by "
+ "default, the data is *not* synced unless "
+ "explicitely requested)" },
+#endif
+ { 0 }
+};
+
+/* Tar file store & lock. */
+static struct store *tar_file;
+static struct mutex tar_file_lock;
+
+/* Archive parsing hook (see tar.c) */
+extern int (* tar_header_hook) (tar_record_t *, off_t);
+
+/* List of tar items for this file */
+static struct tar_list tar_list;
+
+/* Data used by netfs_get_dirents() */
+static struct node *curr_dir;
+static struct node *curr_node;
+static int curr_entry = 0;
+
+/* A convenience macro */
+#define IF_RWFS \
+ if (tarfs_options.readonly) \
+ return EROFS;
+
+
+#define D(_s) strdup(_s)
+
+/* Open the tar file STORE according to TARFS_OPTIONS. Assumes the
+ store is already locked. */
+static error_t
+open_store ()
+{
+ error_t err;
+ int flags = tarfs_options.readonly || tarfs_options.volatil
+ ? STORE_READONLY
+ : 0;
+
+ switch (tarfs_options.compress)
+ {
+ case COMPRESS_NONE:
+ err = store_file_open (tarfs_options.file_name, flags, &tar_file);
+ break;
+ case COMPRESS_GZIP:
+ err = store_gzip_open (tarfs_options.file_name, flags, &tar_file);
+ break;
+ case COMPRESS_BZIP2:
+ err = store_bzip2_open (tarfs_options.file_name, flags, &tar_file);
+ break;
+ default:
+ error (1, EINVAL, "Compression method not implemented (yet)");
+ }
+
+ return err;
+}
+
+/* Close the tar file assuming that it is already locked. */
+static void
+close_store ()
+{
+ store_free (tar_file);
+ tar_file = NULL;
+}
+
+/* Reads NODE from file. This is called by the cache backend. */
+static error_t
+read_from_file (struct node *node, off_t offset, size_t howmuch,
+ size_t *actually_read, void *data)
+{
+ error_t err = 0;
+ store_offset_t start = NODE_INFO(node)->tar->offset;
+ void *d = data;
+
+ mutex_lock (&tar_file_lock);
+
+ if (!tar_file)
+ err = open_store ();
+
+ if (!err)
+ err = store_read (tar_file,
+ start + offset,
+ howmuch,
+ &d,
+ actually_read);
+
+ mutex_unlock (&tar_file_lock);
+
+ if (err)
+ return err;
+
+ assert (*actually_read <= howmuch);
+
+ /* Checks whether store_read() has allocated a new buffer. */
+ if (data != d)
+ {
+ memcpy (data, d, *actually_read);
+ munmap (d, *actually_read);
+ }
+
+ return 0;
+}
+
+
+/* Argp options parser. */
+error_t
+tarfs_parse_opts (int key, char *arg, struct argp_state *sate)
+{
+ switch (key)
+ {
+#ifdef DEBUG
+ case 'D':
+ debug_set_file (arg);
+ break;
+#endif
+ case 'c':
+ tarfs_options.create = 1;
+ break;
+ case 'v':
+ tarfs_options.volatil = 1;
+ break;
+ case 'r':
+ tarfs_options.readonly = 1;
+ break;
+ case 'w':
+ tarfs_options.readonly = 0;
+ break;
+ case 't':
+ tarfs_options.threaded = 1;
+ break;
+ case 'z':
+ tarfs_options.compress = COMPRESS_GZIP;
+ break;
+ case 'j':
+ tarfs_options.compress = COMPRESS_BZIP2;
+ break;
+ case 's':
+ tarfs_options.interval = atoi (arg);
+ break;
+ case ARGP_KEY_ARG:
+ tarfs_options.file_name = strdup (arg);
+ if (!tarfs_options.file_name || !strlen (tarfs_options.file_name))
+ error (1, 1, "No archive specified.");
+ }
+
+ return 0;
+}
+
+/* Returns the tarfs' struct argp. */
+void
+tarfs_get_argp (struct argp *a)
+{
+ bzero (a, sizeof (struct argp));
+ a->options = fs_options;
+ a->parser = tarfs_parse_opts;
+ a->args_doc = args_doc;
+ a->doc = doc;
+}
+
+/* Append to the malloced string *ARGZ of len *ARGZ_LEN a NULL-separated list
+ of arguments. */
+error_t
+tarfs_get_args (char **argz, unsigned *argz_len)
+{
+ error_t err = 0;
+
+ if (!err && tarfs_options.volatil)
+ err = argz_add (argz, argz_len, "--volatile");
+ else
+ {
+ if (tarfs_options.readonly)
+ err = argz_add (argz, argz_len, "--readonly");
+ else
+ err = argz_add (argz, argz_len, "--writable");
+ }
+
+ if (err)
+ return err;
+
+ switch (tarfs_options.compress)
+ {
+ case COMPRESS_GZIP:
+ err = argz_add (argz, argz_len, "--gzip");
+ break;
+ case COMPRESS_BZIP2:
+ err = argz_add (argz, argz_len, "--bzip2");
+ }
+
+ if (err)
+ return err;
+
+ err = argz_add (argz, argz_len, tarfs_options.file_name);
+
+ return err;
+}
+
+/* A basic set_options (). Only runtime options can be changed (using
+ fsysopts): for instance, --no-timeout won't work (it doesn't make
+ sense when tarfs is already running). */
+error_t
+tarfs_set_options (char *argz, size_t argz_len)
+{
+ error_t err = 0;
+
+ /* If going readonly (resp. read-write) while the store is currently
+ opened read-write (resp. read-only) then close it first. */
+
+ if (!strcmp (argz, "-r") || !strcmp (argz, "--readonly"))
+ {
+ if (!tarfs_options.readonly)
+ {
+ mutex_lock (&tar_file_lock);
+ tarfs_options.readonly = 1;
+ close_store ();
+ err = open_store ();
+ mutex_unlock (&tar_file_lock);
+
+ if (err)
+ tarfs_options.readonly = 0;
+ else
+ tarfs_options.volatil = 0;
+ }
+ }
+ else if (!strcmp (argz, "-w") || !strcmp (argz, "--writable"))
+ {
+ if (tarfs_options.readonly)
+ {
+ mutex_lock (&tar_file_lock);
+ tarfs_options.readonly = 0;
+ close_store ();
+ err = open_store ();
+ mutex_unlock (&tar_file_lock);
+
+ if (err)
+ tarfs_options.readonly = 1;
+ else
+ tarfs_options.volatil = 0;
+ }
+ }
+ else if (!strcmp (argz, "-v") || !strcmp (argz, "--volatile"))
+ tarfs_options.readonly = 0, tarfs_options.volatil = 1;
+ else
+ err = EINVAL;
+
+ return err;
+}
+
+
+
+error_t tarfs_create_node (struct node **newnode, struct node *dir,
+ char *name, mode_t mode);
+
+/* This function is called every time a header has been successfully parsed.
+ It simply creates the node corresponding to the header.
+ OFFSET denotes the offset of the header in the archive. */
+int
+tarfs_add_header (tar_record_t *hdr, off_t offset)
+{
+ error_t err;
+ static struct tar_item *last_item = NULL;
+ struct node *dir, *new = NULL;
+ char *name, *notfound, *retry;
+
+ assert (hdr != NULL);
+
+ dir = netfs_root_node;
+ name = D (hdr->header.arch_name);
+ assert (name);
+ debug (("name = %s", name));
+
+ /* Find the new node's parent directory. */
+ do
+ {
+ err = fs_find_node_path (&dir, &retry, &notfound, name);
+
+ /* If a subdirectory wasn't found, then complain, create it and go on.
+ eg.: if we wan't to create "/foo/bar" and "/foo" does not exist
+ yet, then create "/foo" first and then continue with "bar". */
+ if (retry)
+ {
+ error (0, 0, "Inconsistent tar archive "
+ "(directory \"%s\" not found)", notfound);
+ err = tarfs_create_node (&new, dir, notfound, S_IFDIR | 755);
+ assert_perror (err);
+ /*fs_make_subdir (&new, dir, notfound);
+ NEW_NODE_INFO (new);*/
+ free (name);
+ name = retry;
+ }
+ }
+ while (retry);
+
+ if (!notfound)
+ {
+ /* Means that this node is already here: do nothing. Complain only
+ if the node we found is not the root dir ("tar cf x ." creates
+ './' as the first tar entry). */
+ if (dir != netfs_root_node)
+ error (0, 0, "Warning: node \"%s\" already exists", name);
+
+ return 0;
+ }
+
+ /* Now, go ahead and create the node. */
+ name = notfound;
+ assert (strlen (name) > 0);
+
+ switch (hdr->header.linkflag)
+ {
+ /* Hard link. */
+ case LF_LINK:
+ {
+ char *tgname;
+ struct node *target;
+
+ debug (("Hard linking \"%s\"", name));
+
+ /* Get the target's node first. */
+ tgname = strdup (hdr->header.arch_linkname);
+ target = netfs_root_node;
+ fs_find_node_path (&target, &retry, &notfound, tgname);
+
+ if ((!retry) && (!notfound))
+ {
+ /* FIXME: Call tarfs_create_node () and tarfs_link_node instead */
+ fs_hard_link_node (&new, dir, name, target->nn_stat.st_mode, target);
+
+ /* Update node info & stats */
+ if (new)
+ {
+ NEW_NODE_INFO (new);
+
+ /* No need to create a cache for hard links. */
+
+ /* Add the tar item into the list. */
+ err = tar_make_item (&NODE_INFO(new)->tar, new,
+ 0, offset);
+ assert_perror (err);
+ }
+ }
+ else
+ error (0, 0, "Hard link target not found (%s -> %s)", name, tgname);
+
+ break;
+ }
+
+ /* Other node types. */
+ default:
+ err = fs_make_node (&new, dir, name, 0);
+ assert_perror (err);
+
+ /* Update node info & stats */
+ if (new)
+ {
+ NEW_NODE_INFO (new);
+ tar_header2stat (&new->nn_stat, hdr);
+
+ /* Create a cache for the new node. */
+ err = cache_create (new);
+ if (err)
+ error (1, err, "An error occured while creating the filesystem");
+
+ /* Add the tar item into the list. */
+ err = tar_make_item (&NODE_INFO(new)->tar, new,
+ new->nn_stat.st_size, offset);
+ assert_perror (err);
+ }
+ }
+
+ if (!new || err)
+ error (1, err, "Filesystem could not be built");
+
+ tar_insert_item (&tar_list, last_item, NODE_INFO(new)->tar);
+ last_item = NODE_INFO(new)->tar;
+
+#if 0
+ debug (("%s: Created node \"%s\" (size=%li)\n",
+ __FUNCTION__, name, new->nn_stat.st_size));
+#endif
+
+ /* Symlinks handling */
+ if (S_ISLNK (new->nn_stat.st_mode))
+ {
+ if (hdr->header.arch_linkname[0])
+ fs_link_node_path (new, strdup (hdr->header.arch_linkname));
+ else
+ {
+ error (0, 0, "Warning: empty symlink target for node \"%s\"",
+ new->nn->name);
+ fs_link_node_path (new, strdup (""));
+ }
+ }
+
+ /* Directories */
+ if (S_ISDIR (new->nn_stat.st_mode))
+ {
+ new->nn_stat.st_nlink = 2;
+ new->nn->dir->nn_stat.st_nlink++;
+ }
+
+ return 0;
+}
+
+
+error_t
+tarfs_init (struct node **root, struct iouser *user)
+{
+ error_t err = 0;
+ io_statbuf_t st;
+ int flags;
+ file_t tarfile;
+ mode_t mode = 0644;
+
+ /* Sync the archive. */
+ static void
+ sync_archive ()
+ {
+ error_t tarfs_sync_fs (int wait);
+
+ while (1)
+ {
+ sleep (tarfs_options.interval);
+
+ if (!tarfs_options.readonly)
+ tarfs_sync_fs (0);
+ }
+ }
+
+ /* Reads and parses a tar archive, possibly in a separate thread. */
+ static void
+ read_archive ()
+ {
+ error_t err;
+
+ /* Go ahead: parse and build. */
+ mutex_lock (&tar_file_lock);
+ err = tar_open_archive (tar_file);
+ mutex_unlock (&tar_file_lock);
+
+ if (err)
+ error (1, 0, "Invalid tar archive (%s)", tarfs_options.file_name);
+#if 0
+ else
+ cthread_fork ((cthread_fn_t) sync_archive, NULL);
+#endif
+ }
+
+
+ /* Init. */
+ if ((! tarfs_options.file_name) || (! strlen (tarfs_options.file_name)))
+ error (1, 0, "No file specified");
+ if (tarfs_options.create)
+ tarfs_options.readonly = 0, flags = O_CREAT | O_READ | O_WRITE;
+ else
+ flags = tarfs_options.readonly || tarfs_options.volatil
+ ? O_READ
+ : O_READ | O_WRITE;
+
+ /* Get the tarfile's stat. */
+ tarfile = file_name_lookup (tarfs_options.file_name, flags, mode);
+ if (tarfile != MACH_PORT_NULL)
+ {
+ /* Check permissions */
+ err = io_stat (tarfile, &st);
+
+ if (!err && (flags & O_READ))
+ err = fshelp_access (&st, S_IREAD, user);
+ if (!err && (flags & O_WRITE))
+ err = fshelp_access (&st, S_IWRITE, user);
+ }
+ else
+ err = errno;
+
+ if (err)
+ {
+ error (1, err, "%s", tarfs_options.file_name);
+ return err;
+ }
+ mach_port_deallocate (mach_task_self (), tarfile);
+
+ err = fs_init ();
+ if (err)
+ return err;
+
+ /* Create root node */
+ st.st_mode &= ~S_IFMT;
+ st.st_mode |= S_IFDIR | S_IROOT | S_IATRANS;
+ err = fs_make_node (&netfs_root_node, NULL, NULL, st.st_mode);
+ if (err)
+ return err;
+
+ /* Parse the archive and build the filesystem */
+ cache_init (read_from_file);
+ tar_header_hook = tarfs_add_header;
+ tar_list_init (&tar_list);
+
+ /* Open the corresponding store */
+ err = open_store ();
+ if (err)
+ error (1, err, "%s", tarfs_options.file_name);
+
+ assert (tar_file);
+
+ /* We make the following assumption because this is the way it's gotta
+ be with these stores. */
+ assert (tar_file->block_size == 1);
+
+ if (st.st_size)
+ {
+ if (tarfs_options.threaded)
+ cthread_fork ((cthread_fn_t) read_archive, NULL);
+ else
+ read_archive ();
+ }
+
+ return 0;
+}
+
+
+int
+tarfs_set_cd (struct node *dir)
+{
+ curr_dir = dir;
+ curr_node = dir->nn->entries;
+
+ /* Skip anonymous nodes (created by dir_mkfile ()). */
+ while (curr_node && (! curr_node->nn->name))
+ curr_node = curr_node->next;
+
+ curr_entry = 0;
+ return 0;
+}
+
+int
+tarfs_skip_entries (int n)
+{
+ assert (n >= 0);
+
+ /* Skip N first DIR entries. */
+ curr_node = curr_dir->nn->entries;
+
+ if (n > 2)
+ {
+ /* Skip more than `.' and `..' */
+ curr_entry = 2;
+ while ((curr_entry < n) && (curr_node))
+ {
+ /* Skip anonymous nodes (created by dir_mkfile ()). */
+ do
+ curr_node = curr_node->next;
+ while (curr_node && (! curr_node->nn->name));
+
+ curr_entry++;
+ }
+ }
+ else
+ curr_entry = n;
+
+ /* Returns non-null if could not skip N entries. */
+ return (curr_entry<=n)?0:1;
+}
+
+static inline int
+_new_dirent (struct dirent** e, const struct node *n, const char* nodename)
+{
+ size_t namelen;
+ char* name;
+
+ assert (nodename != NULL);
+
+ /* N==NULL means that we are considering the node on which the
+ translator is set. */
+ namelen = (n) ? strlen (nodename) : 2;
+
+ /* Allocate it. */
+ *e = mmap (NULL, sizeof (struct dirent) + namelen,
+ PROT_READ|PROT_WRITE, MAP_ANONYMOUS, 0, 0);
+ assert (*e != NULL);
+
+ /* Copy node name */
+ name = &(*e)->d_name[0];
+
+ if (n == NULL)
+ {
+ /* `..' */
+ memcpy (name, nodename, 3);
+ namelen = 2;
+ (*e)->d_type = DT_DIR;
+ (*e)->d_ino = netfs_root_node->nn_stat.st_ino;
+ }
+ else
+ {
+ memcpy (name, nodename, namelen);
+
+ /* Set the type corresponding to n->nn_stat.st_mode */
+ if (n->nn_stat.st_mode & S_IFREG)
+ (*e)->d_type = DT_REG;
+ else if (n->nn_stat.st_mode & S_IFDIR)
+ (*e)->d_type = DT_DIR;
+ else if (n->nn_stat.st_mode & S_IFLNK)
+ (*e)->d_type = DT_LNK;
+ else
+ (*e)->d_type = DT_UNKNOWN;
+
+ /* if FILENO==0 then the node won't appear. */
+ (*e)->d_fileno = n->nn_stat.st_ino;
+ }
+
+ assert (namelen != 0);
+
+ (*e)->d_namlen = namelen;
+ (*e)->d_reclen = sizeof (struct dirent) + namelen;
+
+ return 0;
+}
+
+int
+tarfs_get_next_entry (struct dirent **entry)
+{
+ switch (curr_entry++)
+ {
+ case 0:
+ _new_dirent (entry, curr_dir, ".");
+ break;
+ case 1:
+ _new_dirent (entry, curr_dir->nn->dir, "..");
+ break;
+ default:
+ if (!curr_node)
+ return 1; /* no more entries */
+ else
+ {
+ _new_dirent (entry, curr_node, curr_node->nn->name);
+
+ /* Skip anonymous nodes (created by dir_mkfile ()). */
+ do
+ curr_node = curr_node->next;
+ while (curr_node && (! curr_node->nn->name));
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* Looks up node named NAME and returns the result in NODE. */
+error_t
+tarfs_lookup_node (struct node** node, struct node* dir, const char* name)
+{
+ struct node *n = dir->nn->entries;
+
+ /* Look for NAME in DIR entries. */
+ while (n)
+ {
+ char *this = n->nn->name;
+
+ if (this)
+ if (!strcmp (this, name))
+ break;
+
+ n = n->next;
+ }
+
+ *node = n;
+
+ if (!n)
+ return ENOENT;
+
+ return 0;
+}
+
+error_t
+tarfs_read_node (struct node *node, off_t offset, size_t *len, void* data)
+{
+ if (S_ISDIR (node->nn_stat.st_mode))
+ {
+ *len = 0;
+ return EISDIR;
+ }
+ else
+ return cache_read (node, offset, *len, data, len);
+}
+
+
+/* Write to NODE through its cache. */
+error_t
+tarfs_write_node (struct node *node, off_t offset, size_t *len, void *data)
+{
+ IF_RWFS;
+
+ if (S_ISDIR (node->nn_stat.st_mode))
+ {
+ *len = 0;
+ return EISDIR;
+ }
+ else
+ {
+ error_t err;
+ /* Checks whether we need to actually write to another node.
+ (hard links are not handled by cache_write ()). */
+ struct node *what = node->nn->hardlink ? node->nn->hardlink : node;
+
+ err = cache_write (node, offset, data, *len, len);
+
+ /* Synchronize stat with hard link's target. */
+ if ((! err) && (what != node))
+ node->nn_stat.st_size = what->nn_stat.st_size;
+
+ return err;
+ }
+}
+
+/* Update NODE stat structure and mark it as dirty. */
+error_t
+tarfs_change_stat (struct node *node, const io_statbuf_t *st)
+{
+ error_t err = 0;
+ struct node *what = node->nn->hardlink ? node->nn->hardlink : node;
+
+ IF_RWFS;
+
+ if (st->st_size != what->nn_stat.st_size)
+ /* Update the cache size */
+ err = cache_set_size (what, st->st_size);
+
+ if (!err)
+ {
+ what->nn_stat = *st;
+ NODE_INFO(what)->stat_changed = 1;
+
+ /* Synchronize NODE with its TARGET if it's a hard link. */
+ if (what != node)
+ {
+ node->nn_stat = what->nn_stat;
+ NODE_INFO(node)->stat_changed = 1;
+ }
+ }
+
+ return err;
+}
+
+
+/* Create a node named NAME in directory DIR. If NEWNODE is non-zero then
+ it will point to the new node. NAME is duplicated. */
+error_t
+tarfs_create_node (struct node **newnode, struct node *dir,
+ char *name, mode_t mode)
+{
+ error_t err;
+ struct node *new = NULL;
+
+ IF_RWFS;
+
+ /* Allow anonymous (nameless) nodes (created by dir_mkfile ()). */
+ if (name)
+ {
+ /* NODE's path has to be at most NAMSIZ long. */
+ char *path = fs_get_path_from_root (netfs_root_node, dir);
+ if (strlen (name) + strlen (path) + 1 > NAMSIZ)
+ return ENAMETOOLONG;
+
+ debug (("Creating node %s", name));
+ }
+ else
+ {
+ debug (("Creating anonymous node", dir->nn->name));
+
+ /* Don't add anonymous nodes into the tar list. */
+ err = fs_make_node (&new, dir, NULL, mode);
+
+ if (!err && new)
+ NEW_NODE_INFO (new);
+
+ *newnode = new;
+
+ err = cache_create (new);
+
+ return err;
+ }
+
+ err = fs_make_node (&new, dir, strdup (name), mode);
+ if (!err && new)
+ {
+ struct tar_item *tar, *prev_tar;
+
+ NEW_NODE_INFO (new);
+ err = cache_create (new);
+
+ if (!err)
+ {
+ /* Insert a corresponding tar item into the list.
+ Offset `-1' denotes a note that does not exist inside the tar file. */
+ err = tar_make_item (&tar, new, 0, -1);
+ assert_perror (err);
+
+ /* Find a place to put TAR. */
+ tar_put_item (&prev_tar, tar);
+ tar_insert_item (&tar_list, prev_tar, tar);
+ }
+ }
+
+ if (newnode)
+ *newnode = new;
+
+ return err;
+}
+
+/* Unlink NODE. NODE's tar_item will remain in the list until the filesystem
+ is linked, *except* if its offset is `-1' (new node). */
+error_t
+tarfs_unlink_node (struct node *node)
+{
+ error_t err = 0;
+ struct tar_item *tar = NODE_INFO(node)->tar;
+
+ IF_RWFS;
+
+ debug (("Unlinking %s", node->nn->name));
+
+ /* Delete NODE. */
+ err = fs_unlink_node (node);
+ if (err)
+ return err;
+
+ /* If NODE has never existed inside the tar file, then remove its tar_item
+ from the list. */
+ if (tar->offset == -1)
+ tar_unlink_item (&tar_list, tar);
+
+ return err;
+}
+
+
+void
+tarfs_free_node (struct node *node)
+{
+ struct tar_item *tar = NODE_INFO (node)->tar;
+
+ /* Free all related resources */
+ cache_free (node);
+ free (NODE_INFO (node));
+ fs_free_node (node);
+ tar->node = NULL;
+}
+
+
+/* Tries to create a hard link named NAME in DIR to file NODE. */
+error_t
+tarfs_link_node (struct node *dir, struct node *target,
+ char *name, int excl)
+{
+ error_t err = 0;
+ struct tar_item *prev_tar, *tar;
+ struct node *new;
+
+ if (fs_find_node (dir, name))
+ return excl ? EEXIST : 0;
+
+ /* If the link's target is anonymous (nameless), then don't create
+ a new node, just change its name. */
+ if (!target->nn->name)
+ {
+ new = target;
+ new->nn->name = strdup (name);
+
+ /* Insert NEW into the tar list */
+ err = tar_make_item (&tar, new, 0, -1);
+ if (!err)
+ tar_put_item (&prev_tar, tar);
+ }
+ else
+ {
+ err = fs_hard_link_node (&new, dir, strdup (name),
+ target->nn_stat.st_mode, target);
+ if (! err && new)
+ {
+ struct tar_item *t;
+ NEW_NODE_INFO (new);
+
+ /* Insert NEW into the tar list */
+ err = tar_make_item (&tar, new, 0, -1);
+ if (!err)
+ {
+ tar_put_item (&prev_tar, tar);
+
+ /* Since NEW must appear after TARGET in the tar list,
+ Make sure that PREV_TAR comes *before* TARGET's tar, otherwise
+ set PREV_TAR to be TARGET's tar item. */
+ for (t = NODE_INFO(target)->tar;
+ t && (t != prev_tar);
+ t = t->next);
+
+ if (!t)
+ prev_tar = NODE_INFO(new)->tar;
+ }
+ }
+ }
+
+ if (!err)
+ {
+ tar_insert_item (&tar_list, prev_tar, tar);
+ NODE_INFO(new)->tar = tar;
+ }
+
+ return err;
+}
+
+/* Tries to turn NODE into a symlink to TARGET. */
+error_t
+tarfs_symlink_node (struct node *node, const char *target)
+{
+ error_t err;
+
+ err = fs_link_node_path (node, target);
+
+ return err;
+}
+
+/* Tries to turn NODE into a device of type TYPE (either S_IFBLK or S_IFCHR).
+ */
+error_t
+tarfs_mkdev_node (struct node *node, mode_t type, dev_t indexes)
+{
+ debug (("Not implemented"));
+ return EOPNOTSUPP;
+}
+
+
+/* Rounds SIZE to the upper RECORDSIZE. */
+static inline size_t
+round_size (size_t s)
+{
+ return (RECORDSIZE * ( (s / RECORDSIZE) \
+ + ((s % RECORDSIZE) ? 1 : 0 )) );
+}
+
+/* Cache nodes ahead CURR_TAR whose data reside in the region
+ [OFFS, OFFS+SIZE] of the tar file. */
+static inline error_t
+cache_ahead (struct tar_item *curr_tar, off_t offs, size_t size)
+{
+ error_t err = 0;
+ struct node *node; /* Corresponding node */
+ size_t node_size; /* Node size */
+ off_t node_offs; /* Node offset */
+
+ assert (size);
+
+ do
+ {
+ /* Looks for an item available in the tar file. */
+ while (curr_tar)
+ if ((curr_tar->offset != -1) && (curr_tar->node))
+ break;
+ else
+ curr_tar = curr_tar->next;
+
+ if (curr_tar)
+ {
+ node = curr_tar->node;
+ node_offs = curr_tar->offset;
+ node_size = node->nn_stat.st_size;
+
+ /* If we are beyond NODE's boundary, assume it's already cached. */
+ if ( (offs < node_offs + node_size)
+ && (offs + size > node_offs) )
+ {
+ /* Cache either the whole node or just what we need. */
+ size_t how_much;
+ how_much = (offs + size > node_offs + node_size)
+ ? node_size
+ : offs + size - node_offs;
+
+ debug (("Caching %i bytes from \"%s\"", how_much, node->nn->name));
+ err = cache_cache (node, how_much);
+ if (err)
+ return err;
+ }
+
+ curr_tar = curr_tar->next;
+ }
+ }
+ while (curr_tar && (node_offs < offs + size));
+
+ return err;
+}
+
+/* Store the filesystem into the tar file. */
+error_t
+tarfs_sync_fs (int wait)
+{
+ error_t err = 0;
+ char buf[RECORDSIZE];
+ off_t file_offs = 0; /* Current offset in the tar file */
+ size_t orig_size = 0; /* Total original tar file size */
+ int need_trailing_block = 0; /* Do we need an additional block at the end? */
+ struct tar_item *tar;
+
+ /* Dump BUF to the tar file's store, enlarging it if necessary. */
+ error_t
+ tar_write (off_t offset, void *buf, size_t len, size_t *amount)
+ {
+ error_t err = 0;
+ int cnt = 0;
+
+ while (1)
+ {
+ mutex_lock (&tar_file_lock);
+
+ if (!tar_file)
+ err = open_store ();
+
+ if (!err)
+ err = store_write (tar_file, offset, buf, len, amount);
+
+ mutex_unlock (&tar_file_lock);
+
+ cnt++;
+
+ if (! err)
+ break;
+ if (cnt > 1)
+ break;
+
+ if (err == EIO)
+ {
+ /* Try to enlarge the file. */
+ debug (("Enlarging file from %lli to %lli",
+ tar_file->size, offset + len));
+ err = store_set_size (tar_file, offset + len);
+ if (err)
+ break;
+ }
+ }
+
+ if (err)
+ error (0, err,
+ "Could not write to file (offs="OFF_FMT")", file_offs);
+
+ return err;
+ }
+
+
+ /* Traverse the tar items list and sync them. */
+ tar_list_lock (&tar_list);
+
+ for (tar = tar_list_head (&tar_list);
+ tar;
+ /* TAR is incremented inside the loop */ )
+ {
+ struct node *node = tar->node;
+
+ /* Compute the original tar file size. */
+ if (tar->offset != -1)
+ orig_size += round_size (tar->orig_size) + RECORDSIZE;
+
+ if (node)
+ {
+ int have_to_sync;
+ char *path;
+ size_t size;
+
+ /* Lock the node first */
+ mutex_lock (&node->lock);
+ have_to_sync = (tar->offset != file_offs + RECORDSIZE);
+ path = fs_get_path_from_root (netfs_root_node, node);
+ size = node->nn_stat.st_size;
+
+ /* Round SIZE. */
+ size = round_size (size);
+
+ /* Synchronize NODE's stat. */
+ if ((NODE_INFO(node)->stat_changed) ||
+ (node->nn_stat.st_size != tar->orig_size) ||
+ (have_to_sync))
+ {
+ size_t amount;
+ char *target;
+
+ debug (("%s: syncing stat", path));
+
+ /* Cache all the nodes that would have been overwritten otherwise. */
+ err = cache_ahead (tar, file_offs, RECORDSIZE);
+ if (err)
+ break;
+
+ target = node->nn->hardlink
+ ? fs_get_path_from_root (netfs_root_node, node->nn->hardlink)
+ : NULL;
+
+ /* Create and write the corresponding tar header. */
+ tar_make_header ((tar_record_t *)buf, &node->nn_stat,
+ path, node->nn->symlink, target);
+
+ err = tar_write (file_offs, buf, RECORDSIZE, &amount);
+ if (err)
+ break;
+
+ assert (amount == RECORDSIZE);
+
+ /* Never finish the tar file with a stat record. */
+ need_trailing_block = 1;
+ }
+ file_offs += RECORDSIZE;
+
+ /* Synchronize NODE's contents except if it's a directory/link. */
+ if ((! S_ISDIR (node->nn_stat.st_mode))
+ && (! node->nn->symlink)
+ && (! node->nn->hardlink)
+ && ((! cache_synced (node)) || (have_to_sync)) )
+ {
+ off_t start = file_offs; /* NODE's start */
+ off_t offs = 0; /* Offset in NODE */
+
+ /* We don't need to cache_ahead () if we already are more than
+ one block behind the original item since we write only
+ RECORDSIZE bytes record. */
+ int ahead = (tar->offset - (long)start < RECORDSIZE);
+
+ debug (("%s: syncing contents (%i bytes)", path, size));
+
+ /* Write RECORDSIZE-long blocks. */
+ while (offs < size)
+ {
+ size_t amount;
+
+ if (ahead)
+ {
+ /* Cache everything that will be overlapped. */
+ err = cache_ahead (tar, file_offs, RECORDSIZE);
+ if (err)
+ break;
+ }
+
+ err = cache_read (node, offs, RECORDSIZE, buf, &amount);
+ assert_perror (err);
+
+ /* Last block: fill it with zeros if necessary. */
+ if (amount < RECORDSIZE)
+ {
+ assert (offs + RECORDSIZE == size);
+ bzero (&buf[amount], RECORDSIZE - amount);
+ }
+
+ /* Write the whole record, regardless of the amount of data
+ actually read. */
+ err = tar_write (file_offs, buf, RECORDSIZE, &amount);
+ if (err)
+ break;
+
+ assert (amount == RECORDSIZE);
+
+ offs += RECORDSIZE;
+ file_offs += RECORDSIZE;
+ }
+
+ if (err)
+ break;
+
+ /* Update NODE's offset *after* cache_ahead () ! */
+ tar->offset = start;
+
+ /* Update the original item size. */
+ tar->orig_size = node->nn_stat.st_size;
+
+ /* If this is the last tar item, tell whether we need an additional
+ trailing block: if NODE's size is a RECORDSIZE multiple then we
+ need one. */
+ need_trailing_block = ! (tar->orig_size % RECORDSIZE);
+ }
+ else
+ {
+ /* Update NODE's offset. */
+ tar->offset = file_offs;
+
+ /* Skip record anyway. */
+ file_offs += RECORDSIZE * ((node->nn_stat.st_size / RECORDSIZE)
+ + ( (node->nn_stat.st_size % RECORDSIZE) ? 1 : 0 ));
+ }
+
+ cache_free (node);
+ free (path);
+ mutex_unlock (&node->lock);
+
+ /* Go to next item. */
+ tar = tar->next;
+ }
+ else
+ {
+ struct tar_item *next = tar->next;
+ debug (("Node removed (size=%i)", tar->orig_size));
+ tar_unlink_item_safe (&tar_list, tar);
+
+ /* Go to next item. */
+ tar = next;
+ }
+ }
+
+ tar_list_unlock (&tar_list);
+
+
+ /* Add an empty record (FIXME: GNU tar added several of them) */
+ if (!err)
+ {
+ size_t amount;
+
+ if (!file_offs)
+ error (0, 0, "Warning: archive is empty");
+
+ bzero (buf, RECORDSIZE);
+ err = tar_write (file_offs, buf, RECORDSIZE, &amount);
+
+ if (err || (amount < RECORDSIZE))
+ {
+ if (!err)
+ err = EIO;
+ }
+
+ file_offs += amount;
+ }
+
+ /* Checks whether the tar file needs to be truncated. */
+ if (!err && (file_offs < orig_size))
+ {
+ debug (("Truncating tar file from %u to "OFF_FMT" bytes",
+ orig_size, file_offs));
+
+ err = store_set_size (tar_file, file_offs);
+ if (err)
+ error (0, err, "Cannot truncate \"%s\"", tarfs_options.file_name);
+ }
+
+
+ if (!err)
+ {
+ /* Call store_free () to commit the changes. This is actually only useful
+ for zip stores. */
+ close_store ();
+ }
+
+ return err;
+}
+
+/* Tarfs destructor. */
+error_t
+tarfs_go_away ()
+{
+ error_t err;
+
+ if (!tarfs_options.readonly && !tarfs_options.volatil)
+ {
+ err = tarfs_sync_fs (0);
+ if (err)
+ error (0, err, "Syncing failed");
+ }
+
+ if (tar_file)
+ store_close_source (tar_file);
+
+ debug (("Bye!"));
+
+ return 0;
+}
+
+
+/* Defines the filesystem backend. */
+struct fs_backend tarfs_backend =
+{
+ tarfs_init,
+ tarfs_get_argp,
+ tarfs_get_args,
+ tarfs_set_options,
+ tarfs_set_cd,
+ tarfs_skip_entries,
+ tarfs_get_next_entry,
+ tarfs_lookup_node,
+ tarfs_read_node,
+
+ /* Write support */
+ tarfs_write_node,
+ tarfs_change_stat,
+ tarfs_create_node,
+ tarfs_unlink_node,
+
+ tarfs_link_node,
+ tarfs_symlink_node,
+ tarfs_mkdev_node,
+
+ tarfs_free_node,
+
+ tarfs_sync_fs,
+ tarfs_go_away
+};
diff --git a/tarfs.h b/tarfs.h
new file mode 100644
index 000000000..9ccf07119
--- /dev/null
+++ b/tarfs.h
@@ -0,0 +1,145 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, 2003 Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/*
+ * Tarfs common definitions.
+ */
+
+#ifndef __TARFS_DEFS_H__
+#define __TARFS_DEFS_H__
+
+#include <hurd/netfs.h>
+#include <hurd/store.h>
+#include "backend.h"
+#include "tar.h"
+#include "cache.h"
+
+
+/** Filesystem options. **/
+
+struct tarfs_opts
+{
+ char* file_name; /* archive file name. */
+ int create:1; /* TRUE if we want to create a new file. */
+ int readonly:1; /* TRUE when filesystem is started readonly. */
+ int volatil:1; /* TRUE if we want the fs to be volatile. */
+ int compress:3; /* compression type (see flags below) */
+ int threaded:1; /* tells whether archive should be parsed in
+ another thread to avoid startup timeout. */
+ int interval; /* Sync interval (in seconds) */
+};
+
+/* Compression types */
+#define COMPRESS_NONE 0
+#define COMPRESS_GZIP 1
+#define COMPRESS_BZIP2 2
+
+
+
+/** Tar lists (see tarlist.c) **/
+
+/* Struct tar_item is used to create a list of tar items in their order of
+ appearance in the original tar file. New files are inserted into this list
+ in tarfs_create_node (). Finally, tarfs_sync_fs () traverses this list in
+ order to sync files in the "right order". */
+struct tar_item
+{
+ /* File offset in the tar file or `-1' if this item is not part of the file */
+ off_t offset;
+
+ /* Original size in the tar file (tar header excluded) */
+ size_t orig_size;
+
+ /* Corresponding node (NULL if it's been unlinked) */
+ struct node *node;
+
+ /* Previous and next items in the tar file. */
+ struct tar_item *prev;
+ struct tar_item *next;
+};
+
+/* Struct tar_list represents a list of tar items. */
+struct tar_list
+{
+ struct tar_item *head;
+ struct mutex lock;
+};
+
+/* Unless stated otherwise, all the following functions taking a tar_list
+ as an argument will first try to grab their lock. */
+
+/* Initialize LIST. */
+extern void tar_list_init (struct tar_list *list);
+
+/* Make a tar item containing the given information. NEW points to the
+ newly created item. */
+extern error_t tar_make_item (struct tar_item **new_item,
+ struct node *node, size_t orig_size,
+ off_t offset);
+
+/* Insert tar item NEW right after PREV in LIST. */
+extern error_t tar_insert_item (struct tar_list *list,
+ struct tar_item *prev,
+ struct tar_item *new);
+
+/* Remove ITEM from LIST. */
+extern void tar_unlink_item (struct tar_list *list, struct tar_item *item);
+
+/* Same except that this one assumes that LIST is already locked. */
+extern void tar_unlink_item_safe (struct tar_list *list,
+ struct tar_item *item);
+
+/* Attempt to find a place for TAR, an new yet unlinked tar item, into the
+ tar list in an optimal way. Returns in PREV_ITEM the item after which
+ TAR should be inserted but don't actually insert it. */
+extern void tar_put_item (struct tar_item **prev_tar, struct tar_item *tar);
+
+/* Accessor for a list's head. */
+#define tar_list_head(List) (List)->head
+
+/* An iterator. */
+#define tar_list_iterate(List, Item, Expr1, Expr2) \
+ for ((Item) = (mutex_lock (&(List)->lock), (List)->head); \
+ (Expr1); (Expr2))
+
+/* These two macros can be used to implement critical things (e.g. traversing
+ the whole list). */
+#define tar_list_lock(List) mutex_lock (&(List)->lock);
+#define tar_list_unlock(List) mutex_unlock (&(List)->lock);
+
+
+/** Node information. **/
+
+/* Each node has such a structure. */
+struct tarfs_info
+{
+ struct tar_item *tar;
+ struct cache cache;
+
+ int stat_changed; /* TRUE when stat changed. */
+};
+
+/* The following macros take struct node *_N as an argument. */
+#define NEW_NODE_INFO(Node) \
+ (Node)->nn->info = calloc (1, sizeof (struct tarfs_info));
+
+#define NODE_INFO(Node) \
+ ((struct tarfs_info*)((Node)->nn->info))
+
+
+#endif
diff --git a/tarlist.c b/tarlist.c
new file mode 100644
index 000000000..917efdcb9
--- /dev/null
+++ b/tarlist.c
@@ -0,0 +1,232 @@
+/* tarfs - A GNU tar filesystem for the Hurd.
+ Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA */
+
+/* Tar list management functions. This is used as a model representing
+ the contents of a tar file, i.e. the items in the order in which they
+ appear (or should appear) in the tar file. */
+
+#include <hurd/netfs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <error.h>
+
+#include "tarfs.h"
+#include "fs.h"
+#include "debug.h"
+
+
+/* Initialize LIST. */
+void
+tar_list_init (struct tar_list *list)
+{
+ list->head = NULL;
+ mutex_init (&list->lock);
+}
+
+/* Make a tar item containing the given information. NEW points to the
+ newly created item. */
+error_t
+tar_make_item (struct tar_item **new_item,
+ struct node *node, size_t orig_size, off_t offset)
+{
+ struct tar_item *new;
+
+ new = calloc (1, sizeof (struct tar_item));
+ if (! new)
+ return ENOMEM;
+
+ assert (node != NULL);
+ new->orig_size = orig_size;
+ new->offset = offset;
+ new->node = node;
+ NODE_INFO(node)->tar = new;
+
+ *new_item = new;
+
+ return 0;
+}
+
+/* Insert tar item NEW right after PREV in LIST. */
+error_t
+tar_insert_item (struct tar_list *list,
+ struct tar_item *prev, struct tar_item *new)
+{
+ struct tar_item **head, *next;
+
+ assert (prev != new);
+
+ mutex_lock (&list->lock);
+ head = &list->head;
+
+ if (! prev)
+ if (! *head)
+ {
+ *head = new;
+ next = NULL;
+ }
+ else
+ {
+ prev = *head;
+ next = (*head)->next;
+ (*head)->next = new;
+ }
+ else
+ {
+ next = prev->next;
+ prev->next = new;
+ }
+
+ new->prev = prev;
+ new->next = next;
+ if (next)
+ next->prev = new;
+
+ mutex_unlock (&list->lock);
+
+ return 0;
+}
+
+/* Remove ITEM from LIST. */
+void
+tar_unlink_item_safe (struct tar_list *list, struct tar_item *item)
+{
+ struct tar_item **head;
+
+ /* The corresponding node should have been destroyed first. */
+ assert (item->node == NULL);
+
+ head = &list->head;
+
+ /* Make sure LIST is not already empty */
+ assert (*head != NULL);
+
+ if (! item->prev)
+ {
+ *head = item->next;
+ if (*head)
+ (*head)->prev = NULL;
+ }
+ else
+ {
+ item->prev->next = item->next;
+ if (item->next)
+ item->next->prev = item->prev;
+ }
+
+ /* Free ITEM. */
+ free (item);
+}
+
+void
+tar_unlink_item (struct tar_list *list, struct tar_item *tar)
+{
+ mutex_lock (&list->lock);
+ tar_unlink_item_safe (list, tar);
+ mutex_unlock (&list->lock);
+}
+
+
+/* Attempt to find a place for TAR, an new yet unlinked tar item, into the
+ tar list in an optimal way. Returns in PREV_ITEM the item after which
+ TAR should be inserted but don't actually insert it. */
+void
+tar_put_item (struct tar_item **prev_tar, struct tar_item *tar)
+{
+ struct node *node = tar->node;
+ struct node *dir, *last_entry;
+
+ /* TAR has to be linked to the filesystem. */
+ assert (tar->node);
+
+ dir = node->nn->dir;
+
+ /* Try to insert the node inside the tar list in such a way that it
+ appears:
+ - after its parent directory;
+ - after all the entries of its parent dir;
+ - after the last entry of its parent dir's last entry.
+
+ The 1st thing is needed to have a consistent tar archive, and the two
+ latter requirements are needed for better performance at sync time
+ (it avoids having to sync everything before NEWNODE).
+
+ So that we have, for instance:
+ 0: file
+ 1: dir/
+ 2: dir/file1
+ 3: dir/file2
+ 4: NEWNODE. */
+
+ last_entry = dir->nn->entries;
+
+ if (last_entry)
+ {
+ /* Get a reference to DIR's last entry. */
+ struct node *next;
+
+ for ( ;
+ (next = last_entry->next) != NULL;
+ last_entry = next)
+ {
+ if (next == node)
+ {
+ /* If LAST_ENTRY is TAR's node... */
+ if (node->next)
+ /* ... skip it */
+ next = node->next;
+ else
+ /* ... or keep the next-to-last entry */
+ break;
+ }
+ }
+
+ if (last_entry == node)
+ last_entry = NULL;
+
+ /* Jump to the last node of LAST_ENTRY's deepest subdir */
+ while (last_entry)
+ {
+ /* If it's a directory, get its last entry. */
+ if ( (S_ISDIR (last_entry->nn_stat.st_mode))
+ && (last_entry->nn->entries) )
+ {
+ for (last_entry = last_entry->nn->entries;
+ last_entry->next;
+ last_entry = last_entry->next);
+ }
+ else
+ break;
+ }
+ }
+
+ if ((last_entry) && (last_entry != node))
+ {
+ assert (NODE_INFO(last_entry)->tar);
+ *prev_tar = NODE_INFO(last_entry)->tar;
+ }
+ else
+ {
+ if (dir == netfs_root_node)
+ *prev_tar = NULL;
+ else
+ *prev_tar = NODE_INFO(dir)->tar;
+ }
+
+ return;
+}
diff --git a/testfs.sh b/testfs.sh
new file mode 100755
index 000000000..b42a95f87
--- /dev/null
+++ b/testfs.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+# A simple and stupid script to test basic features of tarfs
+
+TARNAME=test-tar
+TRANSNODE=t
+LOGFILE=test-log
+
+# Start tarfs on TRANSNODE with the given args
+function start_trans
+{
+ settrans -fgca $TRANSNODE ./tarfs -D $LOGFILE $*
+ return $?
+}
+
+# Stop tarfs
+function stop_trans
+{
+ [ -f $TRANSNODE ] && settrans -g $TRANSNODE || settrans -fg $TRANSNODE
+ return $?
+}
+
+# A simple data consistency check
+function do_diff
+{
+ local ret=0
+
+ echo -n "Consistency check... "
+ cd $TRANSNODE
+ for i in $contents
+ do
+ diff "$i" ../"$i" > /dev/null || ret=1 && break
+ done
+ cd $homedir
+ [ $ret -ne 0 ] && echo failed || echo ok
+
+ return $ret
+}
+
+# Checks the ability to sync the filesystem
+function do_rm_cp
+{
+ echo -n "Deleting files... "
+ rm -rf ${TRANSNODE}/* && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return 1
+ echo -n "Syncing... "
+ stop_trans && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return 1
+ echo -n "Remounting... "
+ start_trans $tarfs_opts -w $tarfile && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return 1
+ echo -n "Recopying everything... "
+ cp -r $contents $TRANSNODE && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return 1
+ echo -n "Syncing... "
+ stop_trans && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return 1
+ echo -n "Remounting... "
+ start_trans $tarfs_opts -w $tarfile && echo "ok"
+ [ $? -ne 0 ] && echo "failed" && return $?
+ return 0
+}
+
+# Hello world
+echo "A Small Test Suite for tarfs"
+echo
+
+# Clean up the directory and get a list of the files in here
+stop_trans
+rm -f $TARNAME $TARNAME.gz $TARNAME.bz2 $LOGFILE $TRANSNODE
+contents=`echo *`
+homedir=`pwd`
+
+# Pack the current directory
+echo -n "Building test archive ($TARNAME)... "
+tar cf $TARNAME * && echo "done"
+[ $? -ne 0 ] && echo "failed" && exit 1
+
+# Launch the test suite
+for tarfile in "$TARNAME" "${TARNAME}.gz" "${TARNAME}.bz2"
+do
+ # Get the appropriate file and tarfs options
+ case "$tarfile" in
+ *.gz) tarfs_opts="-z"
+ gzip $TARNAME ;;
+ *.bz2) tarfs_opts="-j"
+ gunzip -c $TARNAME.gz | bzip2 -c > $tarfile && rm $TARNAME.gz ;;
+ esac
+
+ echo
+ echo "*** File $tarfile ***"
+ start_trans $tarfs_opts "$tarfile"
+
+ if [ $? -ne 0 ]
+ then
+ echo "Unable to read $tarfile"
+ else
+ # Read-only tests
+ do_diff || exit 1
+ do_rm_cp || exit 1
+ do_diff || exit 1
+ fi
+
+ stop_trans
+done
diff --git a/zipstores.c b/zipstores.c
new file mode 100644
index 000000000..5ae29f52f
--- /dev/null
+++ b/zipstores.c
@@ -0,0 +1,1291 @@
+/* Compression store backend.
+
+ Copyright (C) 1995,96,97,99,2000,01, 02 Free Software Foundation, Inc.
+ Written by Ludovic Courtes <ludovic.courtes@utbm.fr>
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd 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, or (at
+ your option) any later version.
+
+ The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#ifndef ZIP_TYPE
+# error "Don't try to compile this file directly."
+#endif
+
+/* Stringification macros stolen from libstore's unzipstores.c */
+
+#define STRINGIFY(name) STRINGIFY_1(name)
+#define STRINGIFY_1(name) #name
+
+#define ZIP(name) ZIP_1 (ZIP_TYPE, name)
+#define ZIP_1(zip, name) ZIP_2 (zip, name)
+#define ZIP_2(zip, name) zip ## _ ## name
+
+#define STORE_ZIP(name) STORE_ZIP_1 (ZIP_TYPE, name)
+#define STORE_ZIP_1(zip,name) STORE_ZIP_2 (zip, name)
+#define STORE_ZIP_2(zip,name) store_ ## zip ## _ ## name
+#define STORE_STD_CLASS_1(name) STORE_STD_CLASS(name)
+
+
+#ifndef ZIP_BUFSIZE_LOG2
+# define ZIP_BUFSIZE_LOG2 13 /* zlib uses up to 16kb */
+# define ZIP_BUFSIZE (1 << ZIP_BUFSIZE_LOG2)
+#endif
+
+/* Copy-on-write cache size */
+#define CACHE_BLOCK_SIZE_LOG2 ZIP_BUFSIZE_LOG2
+#define CACHE_BLOCK_SIZE ZIP_BUFSIZE
+
+/* BLOCK_NUMBER gives the number in which Offset can be found
+ (equivalent to Offset/CACHE_BLOCK_SIZE). */
+#define BLOCK_NUMBER(Offset) \
+ ((Offset) >> CACHE_BLOCK_SIZE_LOG2)
+
+/* BLOCK_RELATIVE_OFFSET gives the relative offset inside a block
+ (equivalent to AbsoluteOffset%CACHE_BLOCK_SIZE). */
+#define BLOCK_RELATIVE_OFFSET(AbsoluteOffset) \
+ ((AbsoluteOffset) & (CACHE_BLOCK_SIZE - 1))
+
+
+typedef unsigned char uchar;
+
+/* Read status */
+enum status
+{
+ /* Status `idle' is only relevant for stream, i.e. uninitialized streams */
+ STATUS_IDLE = 0,
+
+ /* File/stream is being used */
+ STATUS_RUNNING,
+
+ /* End of file/stream has been reached */
+ STATUS_EOF
+};
+
+/* Compression/decompression state */
+struct stream_state
+{
+ /* The actual stream */
+ ZIP_STREAM stream;
+
+ /* A buffer used for input/output */
+ char buf[ZIP_BUFSIZE];
+
+ /* Current offset in the underlying store (ie. compressed stream) */
+ store_offset_t file_offs;
+
+ /* Current offset in this store (ie. uncompressed stream) */
+ store_offset_t zip_offs;
+
+ /* File and zip stream status */
+ enum status file_status;
+ enum status zip_status;
+
+#if (defined ZIP_CRC_UPDATE && defined ZIP_CRC_VERIFY)
+ /* CRC as used by gzip */
+ uLong crc;
+#endif
+
+ /* Stream lock */
+ struct mutex lock;
+};
+
+/* Zip object information */
+struct ZIP (object)
+{
+ /* The underlying store */
+ struct store *source;
+
+ /* The store represented by this object */
+ struct store *store;
+
+#ifdef ZIP_HAS_HEADER
+ /* File header (only used by gzip) */
+ struct ZIP (header) header;
+#endif
+
+ /* Streams for reading and writing */
+ struct stream_state read;
+ struct stream_state write;
+
+ /* Position of the compressed stream start in the underlying storage
+ (ie. right after the gzip header) */
+ store_offset_t start_file_offs;
+
+ /* Original size of the uncompressed stream */
+ size_t zip_orig_size;
+ size_t zip_orig_blocks_size;
+
+ /* Copy-on-write cache of the uncompressed stream */
+ struct
+ {
+ /* Vector of cache blocks */
+ char **blocks;
+
+ /* Size of BLOCKS */
+ size_t size;
+
+ /* Cache lock */
+ struct mutex lock;
+ } cache;
+};
+
+#ifndef MAX
+# define MAX(A,B) ((A) < (B) ? (B) : (A))
+#endif
+#ifndef MIN
+# define MIN(A,B) ((A) < (B) ? (A) : (B))
+#endif
+
+
+/* Reads from STORE and stores the data in BUF.
+ Makes sure BUF remains unchanged. */
+static inline error_t
+store_simple_read (struct store *store, off_t addr,
+ size_t amount, void *buf, size_t *len)
+{
+ error_t err;
+ void *p = buf;
+
+ err = store_read (store, addr, amount, &p, len);
+ if (err)
+ return err;
+
+ assert (*len <= amount);
+
+ if (p != buf)
+ {
+ /* Copy the data back to BUF and deallocate P */
+ int e;
+ memcpy (buf, p, *len);
+ e = munmap (p, *len);
+ assert (!e);
+ }
+
+ return 0;
+}
+
+/* Writes AMOUNT bytes to STORE after enlarging STORE if necessary. */
+static inline error_t
+store_simple_write (struct store *store, off_t addr, void *buf,
+ size_t len, size_t *amount)
+{
+ error_t err;
+ size_t newsize = addr + len;
+
+ if (newsize > store->size)
+ {
+ /* Enlarge the underlying store */
+ err = store_set_size (store, newsize);
+ if (err)
+ {
+ error (0, err, "Unable to set store size to %u", newsize);
+ return err;
+ }
+ }
+
+ err = store_write (store, addr, buf, len, amount);
+
+ return err;
+}
+
+
+/* Initializes GZIP: Resets its file/zip offsets and prepare it for
+ reading. */
+static error_t
+ZIP (stream_read_init) (struct ZIP (object) *zip)
+{
+ error_t err;
+ int zerr;
+ ZIP_STREAM *stream = &zip->read.stream;
+
+ mutex_lock (&zip->read.lock);
+
+ /* Check whether STREAM had already been initialized */
+ if (stream->state)
+ {
+ zerr = ZIP_DECOMPRESS_END (stream);
+ err = ZIP (error) (stream, zerr);
+ assert_perror (err);
+ }
+
+ /* Initialize READ.STREAM for decompression. */
+ zerr = ZIP_DECOMPRESS_INIT (stream);
+ err = ZIP (error) (stream, zerr);
+
+ if (!err)
+ {
+ stream->next_in = stream->next_out = NULL;
+ stream->avail_in = stream->avail_out = 0;
+
+ zip->read.file_offs = zip->start_file_offs;
+ zip->read.zip_offs = 0;
+ zip->read.file_status = zip->read.zip_status = STATUS_RUNNING;
+
+#ifdef ZIP_CRC_UPDATE
+ /* Initialize running CRC */
+ zip->read.crc = ZIP_CRC_UPDATE (0, NULL, 0);
+#endif
+ }
+
+ mutex_unlock (&zip->read.lock);
+
+ return err;
+}
+
+/* Initializes GZIP: Resets its file/zip offsets and prepare it for
+ writing. */
+static error_t
+ZIP (stream_write_init) (struct ZIP (object) *zip)
+{
+ error_t err;
+ ZIP_STREAM *stream = &zip->write.stream;
+ int zerr;
+
+ mutex_lock (&zip->write.lock);
+
+ /* Check whether STREAM has already been initialized */
+ if (stream->state)
+ {
+ zerr = ZIP_COMPRESS_END (stream);
+ err = ZIP (error) (stream, err);
+ assert_perror (err);
+ }
+#ifdef ZIP_HAS_HEADER
+ else
+ {
+ /* Check whether we need to write a gzip header */
+ if (!zip->source->size)
+ {
+ static error_t
+ do_write (char *buf, size_t amount)
+ {
+ size_t len;
+ err = store_simple_write (zip->source, 0, buf, amount, &len);
+ if (!err && (len != amount))
+ err = EIO;
+ zip->start_file_offs = len;
+ return err;
+ }
+
+ debug (("Writing a new gzip header"));
+
+ err = gzip_write_header (do_write);
+ if (err)
+ return err;
+ }
+ }
+#endif
+
+ /* Initialize STREAM for compression. */
+ zerr = ZIP_COMPRESS_INIT (stream);
+ err = ZIP (error) (stream, zerr);
+
+ if (!err)
+ {
+ stream->next_in = NULL;
+ stream->avail_in = 0;
+ stream->next_out = zip->write.buf;
+ stream->avail_out = ZIP_BUFSIZE;
+
+ zip->write.file_offs = zip->start_file_offs;
+ zip->write.zip_offs = 0;
+ zip->write.file_status = zip->write.zip_status = STATUS_RUNNING;
+
+#ifdef ZIP_CRC_UPDATE
+ /* Initialize running CRC */
+ zip->write.crc = ZIP_CRC_UPDATE (0, NULL, 0);
+#endif
+ }
+
+ mutex_unlock (&zip->write.lock);
+
+ return err;
+}
+
+#ifdef DEBUG_ZIP
+ /* Dumps the state of a stream */
+# define DUMP_STATE() \
+ { \
+ if (!stream->state) \
+ debug (("!stream->state")); \
+ debug (("offset file/zip = %llu / %llu", \
+ *file_offs, *zip_offs)); \
+ debug (("avail in/out = %u / %u", \
+ stream->avail_in, stream->avail_out)); \
+ }
+#else
+# define DUMP_STATE()
+#endif
+
+/* Directly read AMOUNT bytes from GZIP's zip stream starting at its current
+ position (GZIP->READ.FILE_OFFS). Update the FILE_OFFS and GZIP_OFFS fields.
+ This is the canonical way to read the stream. */
+static error_t
+ZIP (stream_read) (struct ZIP (object) *const zip,
+ size_t amount, void *const buf,
+ size_t *const len)
+{
+ error_t err = 0;
+ int zerr = 0;
+ ZIP_STREAM *stream = &zip->read.stream;
+ store_offset_t *zip_offs = &zip->read.zip_offs,
+ *file_offs = &zip->read.file_offs;
+ store_offset_t zip_start = *zip_offs;
+
+
+ mutex_lock (&zip->read.lock);
+ assert (zip->read.zip_status != STATUS_IDLE);
+
+ /* Check whether we have already reached the end of stream */
+ if (zip->read.zip_status == STATUS_EOF)
+ {
+ debug (("eof: doing nothing"));
+ *len = 0;
+ mutex_unlock (&zip->read.lock);
+ return 0;
+ }
+
+ /* Check whether the underlying file is empty */
+ if (zip->source->size <= zip->start_file_offs)
+ {
+ *len = 0;
+ zip->read.zip_status = STATUS_EOF;
+ zip->read.file_status = STATUS_EOF;
+ mutex_unlock (&zip->read.lock);
+ return 0;
+ }
+
+ /* Prepare for reading/decompressing */
+ stream->next_out = (uchar *) buf;
+ stream->avail_out = amount;
+
+ while (stream->avail_out != 0)
+ {
+ size_t avail_in, avail_out;
+
+ if (stream->avail_in == 0)
+ {
+ /* Load the compressed stream */
+ size_t read = MIN (zip->source->size - *file_offs, ZIP_BUFSIZE);
+ stream->next_in = zip->read.buf;
+
+ err = store_simple_read (zip->source, *file_offs,
+ read, zip->read.buf, &read);
+ if (err)
+ break;
+
+ stream->avail_in = read;
+
+ if (*file_offs + read >= zip->source->size)
+ {
+ zip->read.file_status = STATUS_EOF;
+ debug (("End of file"));
+ }
+ }
+
+ /* Inflate and update the file/zip pointers accordingly. */
+ DUMP_STATE ();
+ avail_in = stream->avail_in;
+ avail_out = stream->avail_out;
+
+ zerr = ZIP_DECOMPRESS (stream);
+
+ *file_offs += avail_in - stream->avail_in;
+ *zip_offs += avail_out - stream->avail_out;
+
+ if (zerr == ZIP_STREAM_END)
+ {
+ zip->read.zip_status = STATUS_EOF;
+ debug (("End of stream"));
+ if (zip->read.file_status != STATUS_EOF)
+ error (0, 0, "Trailing characters at end of file");
+ break;
+ }
+
+ err = ZIP (error) (stream, zerr);
+ if (err)
+ break;
+ }
+
+ *len = *zip_offs - zip_start;
+
+#ifdef ZIP_CRC_UPDATE
+ zip->read.crc = ZIP_CRC_UPDATE (zip->read.crc, buf, *len);
+ if (zip->read.zip_status == STATUS_EOF)
+ {
+ /* Check gzip's CRC and length (4 bytes) */
+ ZIP_CRC_VERIFY (stream, zip->read.crc);
+ }
+#endif
+
+ debug (("requested/read = %i / %i", amount, *len));
+ assert (*len <= amount);
+
+ mutex_unlock (&zip->read.lock);
+
+ return err;
+}
+
+/* Directly writes AMOUNT bytes from GZIP's zip stream starting at its current
+ position. Update the FILE_OFFS and GZIP_OFFS fields. If FINISH is set, then
+ terminate compression and close the compression stream thereafter.
+ ADVERTISE is called before writing data to the underlying store in order
+ to enable, e.g., caching of the region that is going to be overwritten.
+ This is the canonical way to write a stream.
+ (note: this function is similar to ZIP (stream_read)) */
+static error_t
+ZIP (stream_write) (struct ZIP (object) *const zip,
+ size_t amount, void *const buf,
+ size_t *const len, const int finish,
+ error_t (* advertise)
+ (struct ZIP (object) *zip,
+ store_offset_t offs, size_t amount))
+{
+ error_t err = 0;
+ int zerr = 0;
+ ZIP_STREAM *stream = &zip->write.stream;
+ store_offset_t *zip_offs = &zip->write.zip_offs,
+ *file_offs = &zip->write.file_offs;
+ store_offset_t zip_start = *zip_offs;
+
+ /* Write AMOUNT bytes from BUF (compressed stream). */
+ error_t
+ do_write (char *buf, size_t amount)
+ {
+ error_t err;
+ size_t len;
+
+ /* Advertise the region that's going to be overwritten */
+ err = advertise (zip, *file_offs, amount);
+ if (err)
+ return err;
+
+ err = store_simple_write (zip->source, *file_offs, buf,
+ amount, &len);
+ if (!err)
+ assert (len == amount);
+
+ *file_offs += amount;
+
+ return err;
+ }
+
+
+ mutex_lock (&zip->write.lock);
+ assert (zip->write.zip_status != STATUS_IDLE);
+
+ if (zip->write.zip_status == STATUS_EOF)
+ {
+ /* End of file reached */
+ debug (("eof: doing nothing"));
+ *len = 0;
+ mutex_unlock (&zip->write.lock);
+ return 0;
+ }
+
+ stream->next_in = (uchar *) buf;
+ stream->avail_in = amount;
+
+ while (finish || stream->avail_in)
+ {
+ size_t avail_out, avail_in;
+
+ if (stream->avail_out == 0)
+ {
+ /* Flush the compressed stream */
+ err = do_write (zip->write.buf, ZIP_BUFSIZE);
+ if (err)
+ break;
+
+ stream->next_out = zip->write.buf;
+ stream->avail_out = ZIP_BUFSIZE;
+ }
+
+ /* Inflate and update the file/zip pointers accordingly. */
+ DUMP_STATE ();
+ avail_out = stream->avail_out;
+ avail_in = stream->avail_in;
+
+ if (!finish)
+ zerr = ZIP_COMPRESS (stream);
+ else
+ zerr = ZIP_COMPRESS_FINISH (stream);
+
+ *zip_offs += avail_in - stream->avail_in;
+
+ if (finish)
+ {
+ if (zerr == ZIP_STREAM_END)
+ /* Compression over */
+ break;
+ else
+ /* Continue till there is no more pending output */
+ continue;
+ }
+
+ err = ZIP (error) (stream, zerr);
+ if (err)
+ {
+ debug (("zerr = %s", strerror (err)));
+ break;
+ }
+ }
+
+ *len = *zip_offs - zip_start;
+
+#ifdef ZIP_CRC_UPDATE
+ zip->write.crc = ZIP_CRC_UPDATE (zip->write.crc, buf, *len);
+#endif
+
+ if (!err && finish)
+ {
+ /* Terminate */
+ size_t write = ZIP_BUFSIZE - stream->avail_out;
+ debug (("Flushing & terminating"));
+
+ err = do_write (zip->write.buf, write);
+ if (err)
+ goto end;
+
+ /* Close the compression stream */
+ zerr = ZIP_COMPRESS_END (stream);
+ err = ZIP (error) (stream, zerr);
+ assert_perror (err);
+
+#ifdef ZIP_CRC_UPDATE
+ /* Write gzip CRC (4 bytes) and total uncompressed stream size (4 bytes) */
+ err = ZIP (write_suffix) (stream, zip->write.crc, do_write);
+ if (err)
+ {
+ debug (("Failed to write suffix"));
+ goto end;
+ }
+#endif
+
+ debug (("Finished at file/zip: %lli / %lli", *file_offs, *zip_offs));
+ }
+
+end:
+ debug (("requested/written = %i / %i", amount, *len));
+
+ mutex_unlock (&zip->write.lock);
+
+ return err;
+}
+
+
+/* Jump at offset OFFS of ZIP's raw decompression stream, without
+ taking the cache data into account. When ZIP is being written, only
+ forward seeks are allowed. */
+static error_t
+ZIP (stream_read_seek) (struct ZIP (object) *const zip, store_offset_t offs)
+{
+ error_t err = 0;
+ char buf[ZIP_BUFSIZE];
+ const store_offset_t *zip_offs = &zip->read.zip_offs;
+
+ if (*zip_offs > offs)
+ {
+ /* Reverse seek are forbidden when writing */
+ assert (zip->write.zip_status != STATUS_RUNNING);
+
+ /* Start from the beginning */
+ err = ZIP (stream_read_init) (zip);
+ if (err)
+ return err;
+ }
+
+ if (offs != *zip_offs)
+ {
+ /* Emulate a seek by reading the data before OFFS */
+ size_t len;
+ size_t amount;
+
+ debug (("Seeking from "OFF_FMT" to "OFF_FMT, *zip_offs, offs));
+
+ while (*zip_offs < offs)
+ {
+ /* Read from zero to BLOCK_OFFS. */
+ amount = MIN (offs - *zip_offs, ZIP_BUFSIZE);
+
+ err = ZIP (stream_read) (zip, amount, buf, &len);
+ if (err)
+ return err;
+ if (len < amount)
+ {
+ debug (("Couln't seek to %lli (got %u instead of %u)",
+ offs, len, amount));
+ return EIO;
+ }
+ }
+ }
+
+ /* Make sure we got there. */
+ assert (*zip_offs == offs);
+
+ return err;
+}
+
+/* Read AMOUNT bytes from STORE at offset OFFSET. Returns the number of bytes
+ actually read in LEN. */
+static error_t
+ZIP (read) (struct store *store,
+ store_offset_t offset, size_t index, size_t amount, void **buf,
+ size_t *len)
+{
+ error_t err = 0;
+ struct ZIP (object) *zip = store->misc;
+
+ char **blocks;
+ size_t blocks_size;
+ size_t block = BLOCK_NUMBER (offset); /* 1st block to read */
+ store_offset_t block_offset;
+ char *datap = *buf; /* current pointer */
+ size_t size;
+
+ if (offset >= store->size)
+ {
+ *len = 0;
+ return EIO;
+ }
+
+ /* Adjust SIZE and LEN to the maximum that can be read. */
+ size = store->size - offset;
+ size = (size > amount) ? amount : size;
+ *len = size;
+
+ /* Get the relative offset inside cache block BLOCK. */
+ block_offset = BLOCK_RELATIVE_OFFSET (offset);
+
+ /* Lock the file during the whole reading (XXX: not very fine-grained) */
+ mutex_lock (&zip->cache.lock);
+ blocks = zip->cache.blocks;
+ blocks_size = zip->cache.size;
+
+ while (size > 0)
+ {
+ size_t read = (size > CACHE_BLOCK_SIZE)
+ ? (CACHE_BLOCK_SIZE - block_offset)
+ : (size);
+
+ if ((block < blocks_size) && (blocks[block]))
+ /* Read block from cache */
+ memcpy (datap, &blocks[block][block_offset], read);
+ else
+ {
+ /* Read block directly from file */
+ size_t actually_read;
+
+ err = ZIP (stream_read_seek) (zip, offset);
+ assert_perror (err);
+
+ err = ZIP (stream_read) (zip, read, datap, &actually_read);
+ if (err)
+ break;
+
+ /* We should have read everything. */
+ assert (actually_read == read);
+ }
+
+ /* Go ahead with next block. */
+ block++;
+ size -= read;
+ block_offset = 0;
+ datap = datap + read;
+ }
+
+ mutex_unlock (&zip->cache.lock);
+
+ return err;
+}
+
+/* Fetches block number BLOCK from STORE and caches it.
+ Cache is assumed to be locked when this is called. */
+static inline error_t
+fetch_block (struct ZIP (object) *zip, size_t block)
+{
+ error_t err = 0;
+ char **blocks = zip->cache.blocks;
+ size_t last_block = BLOCK_NUMBER (zip->zip_orig_size - 1);
+ size_t read;
+ size_t actually_read = 0;
+
+ /* Don't try to go beyond the boundaries. */
+ assert (block <= last_block);
+
+ if (blocks[block])
+ /* Nothing to do */
+ return 0;
+
+ /* Allocate a new block. */
+ blocks[block] = calloc (CACHE_BLOCK_SIZE, sizeof (char));
+ if (!blocks[block])
+ return ENOMEM;
+
+ /* If this is the last block, then we may have less to read. */
+ if (block == last_block)
+ read = zip->zip_orig_size % CACHE_BLOCK_SIZE;
+ else
+ read = CACHE_BLOCK_SIZE;
+
+ err = ZIP (stream_read_seek) (zip, block << CACHE_BLOCK_SIZE_LOG2);
+ assert_perror (err);
+
+ err = ZIP (stream_read) (zip, read, blocks[block], &actually_read);
+
+ if (!err)
+ /* We should have read everything. */
+ assert (actually_read == read);
+
+ return err;
+}
+
+/* Write LEN bytes from BUF to STORE at offset ADDR. Returns the number of
+ bytes actually written in AMOUNT. */
+static error_t
+ZIP (write) (struct store *store,
+ store_offset_t offset, size_t index, const void *buf, size_t len,
+ size_t *amount)
+{
+ error_t err = 0;
+ struct ZIP (object) *zip = store->misc;
+ size_t size;
+
+ char **blocks;
+ int block = BLOCK_NUMBER (offset); /* 1st block to read. */
+ const void *datap = buf; /* current pointer */
+
+ mutex_lock (&zip->cache.lock);
+ blocks = zip->cache.blocks;
+
+ if (offset >= store->size)
+ {
+ debug (("Trying to write at offs %lli (size=%u)", offset, store->size));
+ *amount = 0;
+ mutex_unlock (&zip->cache.lock);
+ return EIO;
+ }
+
+ /* Adjust SIZE and LEN to the maximum that can be read. */
+ size = store->size - offset;
+ size = (size > len) ? len : size;
+ *amount = size;
+
+ /* Set OFFSET to be the relative offset inside cache block num. BLOCK. */
+ offset = BLOCK_RELATIVE_OFFSET (offset);
+
+ while (size > 0)
+ {
+ size_t write = (size + offset > CACHE_BLOCK_SIZE)
+ ? (CACHE_BLOCK_SIZE - offset)
+ : (size);
+
+ /* Allocate/fetch this block if not here yet (copy-on-write). */
+ if (!blocks[block])
+ {
+ if (block < zip->zip_orig_blocks_size)
+ {
+ /* Fetch this block */
+ err = fetch_block (zip, block);
+ if (err)
+ break;
+ }
+ else
+ {
+ /* Allocate a new block */
+ char *b = calloc (CACHE_BLOCK_SIZE, sizeof (char));
+ if (!b)
+ {
+ err = ENOMEM;
+ break;
+ }
+ blocks[block] = b;
+ }
+ }
+
+ /* Copy the new data into cache. */
+ memcpy (&blocks[block][offset], datap, write);
+
+ /* Go ahead with next block. */
+ block++;
+ size -= write;
+ offset = 0;
+ datap = datap + write;
+ }
+
+ mutex_unlock (&zip->cache.lock);
+
+ return err;
+}
+
+/* Set STORE's size to SIZE (in bytes), i.e. adjust its cache size. */
+static error_t
+ZIP (set_size) (struct store *store, size_t size)
+{
+ error_t err = 0;
+ struct ZIP (object) *zip = store->misc;
+ size_t *blocks_size;
+ char ***blocks;
+ size_t newsize, oldsize; /* Size of BLOCKS */
+
+ mutex_lock (&zip->cache.lock);
+ blocks_size = &zip->cache.size;
+ blocks = &zip->cache.blocks;
+ oldsize = *blocks_size;
+ newsize = size ? BLOCK_NUMBER (size - 1) + 1 : 0;
+
+ debug (("old/new size = %lli / %u", store->size, size));
+
+ /* Check whether the cache needs to be grown */
+ if (size > store->size)
+ {
+ if (newsize > oldsize)
+ {
+ /* Enlarge the block vector */
+ char **newblocks;
+
+ newblocks = realloc (*blocks, newsize * sizeof (char *));
+ if (!newblocks)
+ err = ENOMEM;
+ else
+ {
+ *blocks = newblocks;
+
+ /* Zero the new blocks but don't actually allocate them */
+ bzero (&(*blocks)[*blocks_size],
+ (newsize - *blocks_size) * sizeof (char *));
+
+ *blocks_size = newsize;
+ }
+ }
+ }
+ else
+ {
+ int i;
+
+ /* Free unused cache blocks */
+ for (i = newsize; i < *blocks_size; i++)
+ free ((*blocks)[i]);
+
+ /* Reduce cache vector */
+ *blocks = realloc (*blocks, newsize * sizeof (char *));
+ *blocks_size = newsize;
+ }
+
+ if (!err)
+ store->size = store->end = store->wrap_src = store->runs[0].length = size;
+
+ mutex_unlock (&zip->cache.lock);
+
+ debug (("newsize is %lli (err = %s)", store->size, strerror (err)));
+
+ return err;
+}
+
+/* Modify SOURCE to reflect those runs in RUNS, and return it in STORE. */
+error_t
+ZIP (remap) (struct store *source,
+ const struct store_run *runs, size_t num_runs,
+ struct store **store)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+ZIP (allocate_encoding) (const struct store *store, struct store_enc *enc)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+ZIP (encode) (const struct store *store, struct store_enc *enc)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+ZIP (decode) (struct store_enc *enc, const struct store_class *const *classes,
+ struct store **store)
+{
+ return EOPNOTSUPP;
+}
+
+static error_t
+ZIP (validate_name) (const char *name, const struct store_class *const *classes)
+{
+ return 0;
+}
+
+static error_t
+ZIP (map) (const struct store *store, vm_prot_t prot, mach_port_t *memobj)
+{
+ *memobj = MACH_PORT_NULL;
+ return EOPNOTSUPP;
+}
+
+
+/* Traverses the whole zip store STORE and allocate its cache.
+ Returns STORE's size (the uncompressed stream size) in SIZE.
+ This should be called *only once* when initializing STORE. */
+static inline error_t
+traverse (struct store *const store, size_t *const size)
+{
+ error_t err;
+ struct ZIP (object) *zip = store->misc;
+ size_t cache_size, total_size = 0, block = 0;
+ char buf[ZIP_BUFSIZE];
+
+ /* No need to lock the cache here since this is called from
+ the open method. */
+
+ /* Create an arbitrary size cache for the uncompressed stream */
+ cache_size = (BLOCK_NUMBER (zip->source->size) + 1) << 1;
+ zip->cache.blocks = calloc (cache_size, sizeof (char *));
+ if (!zip->cache.blocks)
+ return ENOMEM;
+
+ zip->cache.size = cache_size;
+
+ /* We could cache the whole file but we don't, in order to minimize memory
+ usage. */
+ while (zip->read.zip_status != STATUS_EOF)
+ {
+ size_t len;
+
+ err = ZIP (stream_read) (zip, ZIP_BUFSIZE, buf, &len);
+ if (err || !len)
+ break;
+
+ total_size += len;
+
+ if (++block >= cache_size)
+ {
+ /* Grow the cache block vector */
+ char **blocks;
+ cache_size <<= 1;
+ blocks = realloc (zip->cache.blocks,
+ cache_size * sizeof (char *));
+ if (!blocks)
+ {
+ err = ENOMEM;
+ break;
+ }
+
+ bzero (&blocks[zip->cache.size],
+ (cache_size - zip->cache.size) * sizeof (char *));
+
+ zip->cache.size = cache_size;
+ zip->cache.blocks = blocks;
+ }
+ }
+
+ if (err)
+ {
+ debug (("error = %s", strerror (err)));
+ return err;
+ }
+
+ debug (("file traversed (offset file/zip = %llu / %llu)",
+ zip->read.file_offs, zip->read.zip_offs));
+
+ *size = total_size;
+
+ return err;
+}
+
+
+/* Synchronizes STORE if it's opened read-write and if there are dirty pages.
+ This is our cleanup procedure which gets called *only* when the user
+ calls store_free (). */
+void
+ZIP (sync) (struct store *store)
+{
+ error_t err;
+ int dirty = 0, zerr;
+ struct ZIP (object) *zip = store->misc;
+ ZIP_STREAM *stream = &zip->write.stream;
+ char **blocks;
+ size_t block;
+
+ /* This is our ZIP (stream_write) callback. All it does is cache
+ the region [OFFS, OFFS+AMOUNT] of the underlying source file
+ (or at least skip it) so that we don't lose it when overwriting it. */
+ error_t
+ cache_ahead (struct ZIP (object) *zip, store_offset_t offs, size_t amount)
+ {
+ char lostbuf[CACHE_BLOCK_SIZE];
+ store_offset_t *read_foffs = &zip->read.file_offs,
+ *read_zoffs = &zip->read.zip_offs;
+ enum status *read_fstatus = &zip->read.file_status;
+ size_t block = BLOCK_NUMBER (*read_zoffs);
+ size_t read;
+
+ debug (("Region offs=%lli amount=%u", offs, amount));
+
+ /* Cache and put the decompression stream beyond where we are going
+ to write. */
+ while ((*read_fstatus != STATUS_EOF) &&
+ (*read_foffs < offs + amount))
+ {
+ /* Assume the read stream is at the beginning of a block */
+ assert (BLOCK_RELATIVE_OFFSET (*read_zoffs) == 0);
+
+ debug (("At block %i (offset %lli)", block, *read_zoffs));
+
+ if (!blocks[block])
+ /* Cache this block */
+ err = fetch_block (zip, block);
+ else
+ /* Just skip this block */
+ err = ZIP (stream_read) (zip, CACHE_BLOCK_SIZE, lostbuf, &read);
+
+ if (err)
+ break;
+ }
+
+ return err;
+ }
+
+
+ if ((store->flags && STORE_READONLY) ||
+ (store->flags && STORE_HARD_READONLY))
+ /* Store opened read-only */
+ goto terminate;
+
+ /* Hold the cache lock till the end--anyway, no one should try to get
+ this lock since we are called from store_free (). */
+ mutex_lock (&zip->cache.lock);
+ blocks = zip->cache.blocks;
+
+ /* Initialize STREAM since this should not have be done before. */
+ err = ZIP (stream_write_init) (zip);
+ assert_perror (err);
+
+ if (store->size != zip->zip_orig_size)
+ /* Size has changed: We need to rewrite the whole file */
+ dirty = 1;
+
+ /* Look for dirty cache pages */
+ for (block = 0;
+ (!dirty) && (block <= BLOCK_NUMBER (store->size - 1));
+ block++)
+ dirty = (blocks[block] != NULL);
+
+ if (!dirty)
+ /* Nothing to do */
+ goto terminate;
+
+ /* Traverse the file and sync it */
+ debug (("Syncing!"));
+ err = ZIP (stream_read_init) (zip);
+ assert_perror (err);
+
+ for (block = 0;
+ block <= BLOCK_NUMBER (store->size - 1);
+ block++)
+ {
+ int end = (block == BLOCK_NUMBER (store->size - 1));
+ size_t amount, len;
+
+ amount = end ? (store->size % CACHE_BLOCK_SIZE) : CACHE_BLOCK_SIZE;
+
+ /* Make sure we do have this block */
+ if (!blocks[block])
+ {
+ if (block < zip->zip_orig_blocks_size)
+ {
+ /* Fetch this block */
+ err = fetch_block (zip, block);
+ if (err)
+ break;
+ }
+ else
+ {
+ /* Allocate a new (zeroed) block */
+ char *b = calloc (CACHE_BLOCK_SIZE, sizeof (char));
+ if (!b)
+ {
+ err = ENOMEM;
+ break;
+ }
+ blocks[block] = b;
+ }
+ }
+
+ /* Write the compressed stream for this block */
+ err = ZIP (stream_write) (zip, amount, blocks[block],
+ &len, end, cache_ahead);
+ assert_perror (err);
+
+ free (blocks[block]);
+ }
+
+ if (zip->source->size > zip->write.file_offs)
+ {
+ /* Reduce the underlying store */
+ err = store_set_size (zip->source, zip->write.file_offs);
+ if (err)
+ error (0, err, "Unable to reduce store to %lli", zip->write.file_offs);
+ }
+
+terminate:
+ debug (("Size file/zip/zip_orig: %lli / %lli / %u",
+ zip->source->size, store->size, zip->zip_orig_size));
+
+ /* Deallocate everything and leave */
+ zerr = ZIP_DECOMPRESS_END (&zip->read.stream);
+ err = ZIP (error) (stream, zerr);
+ assert_perror (err);
+
+ free (zip->cache.blocks);
+ free (zip);
+ store->misc = NULL;
+ store->misc_len = 0;
+}
+
+
+error_t ZIP (open) (const char *name, int flags,
+ const struct store_class *const *classes,
+ struct store **store);
+
+const struct store_class
+STORE_ZIP (class) =
+{
+ STORAGE_OTHER, STRINGIFY (ZIP_TYPE), ZIP (read), ZIP (write), ZIP (set_size),
+ ZIP (allocate_encoding), ZIP (encode), ZIP (decode),
+ 0, 0, ZIP (sync), 0, ZIP (remap), ZIP (open), ZIP (validate_name),
+ ZIP (map)
+};
+
+
+/* Open an existing zip store. */
+error_t
+ZIP (open) (const char *name, int flags,
+ const struct store_class *const *classes,
+ struct store **store)
+{
+ error_t err;
+ file_t source;
+ struct store *from; /* Underlying store */
+ struct ZIP (object) *zip;
+ ZIP_STREAM *stream;
+
+#ifdef ZIP_CRC_UPDATE
+ /* Begin with a sanity check */
+ assert (sizeof (zip->write.crc) == 4);
+#endif
+
+ /* Get a port to the underlying file (used by store_create ()) */
+ source = file_name_lookup (name,
+ (flags & (STORE_READONLY | STORE_HARD_READONLY))
+ ? O_READ
+ : O_READ | O_WRITE,
+ S_IFREG);
+ if (source == MACH_PORT_NULL)
+ return errno;
+
+ /* Open the underlying store */
+ /* FIXME: We should use store_typed_open () but this requires to have
+ a pointer to store_std_classes which we don't have. */
+ err = store_file_open (name, flags, &from);
+ if (err)
+ return err;
+
+ /* FIXME: The following assumption should be removed at some point. */
+ assert (from->block_size == 1);
+ debug (("Underlying file size is %i bytes", from->size));
+
+ /* Actually create the store. */
+#if 0
+ err = store_create (source,
+ flags | STORE_NO_FILEIO,
+ NULL,
+ store);
+#else
+ err = store_file_create (source, flags, store);
+#endif
+
+ if (err)
+ return err;
+
+ /* Allocate our data structure */
+ zip = (struct ZIP (object) *) calloc (1, sizeof (struct ZIP (object)));
+ if (!zip)
+ return ENOMEM;
+
+ zip->source = from;
+ zip->read.file_status = zip->write.file_status = STATUS_RUNNING;
+ zip->store = *store;
+ stream = &zip->read.stream;
+
+ mutex_init (&zip->read.lock);
+ mutex_init (&zip->write.lock);
+ mutex_init (&zip->cache.lock);
+
+ (*store)->flags = flags;
+ (*store)->block_size = 1;
+ (*store)->log2_block_size = 0;
+ (*store)->class = &STORE_ZIP (class);
+ (*store)->misc = zip;
+ (*store)->misc_len = sizeof (struct ZIP (object));
+
+#ifdef ZIP_HAS_HEADER
+ if (from->size)
+ {
+ /* Read & skip the gzip header */
+ err = ZIP (read_header) (zip->source, &zip->start_file_offs,
+ &zip->header);
+ assert_perror (err);
+ }
+#endif
+
+ debug (("start_file_offs = %llu", zip->start_file_offs));
+
+ /* Init zip stream */
+ err = ZIP (stream_read_init) (zip);
+ assert_perror (err);
+
+ /* Traverse the whole file in order to create its offset map
+ and get its size (ie. the uncompressed stream length). */
+ err = traverse (*store, &zip->zip_orig_size);
+ if (err)
+ {
+ free (zip);
+ return err;
+ }
+
+ zip->zip_orig_blocks_size = zip->zip_orig_size
+ ? BLOCK_NUMBER (zip->zip_orig_size - 1) + 1
+ : 0;
+ (*store)->size = (*store)->end = (*store)->wrap_src = zip->zip_orig_size;
+ debug (("Uncompressed stream size is %u", zip->zip_orig_size));
+
+ {
+ /* Assign just a single run to STORE */
+ const struct store_run run = { 0, zip->zip_orig_size };
+ err = store_set_runs (*store, &run, 1);
+ assert_perror (err);
+
+ /* Make sure that store_set_runs () and pals didn't change anything */
+ assert ((*store)->size == zip->zip_orig_size);
+ }
+
+ return err;
+}
+
+error_t
+STORE_ZIP (open) (const char *name, int flags, struct store **store)
+{
+ return ZIP (open) (name, flags, NULL, store);
+}
diff --git a/zipstores.h b/zipstores.h
new file mode 100644
index 000000000..c8a2e318d
--- /dev/null
+++ b/zipstores.h
@@ -0,0 +1,34 @@
+/* Gzip/Bzip2 store backends.
+
+ Copyright (C) 1995,96,97,99,2000,01, 02 Free Software Foundation, Inc.
+ Written by Ludovic Courtes <ludo@type-z.org>
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd 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, or (at
+ your option) any later version.
+
+ The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#ifndef __ZIPSTORES_H__
+#define __ZIPSTORES_H__
+
+/* Open an existing store. */
+extern error_t store_gzip_open (const char *name,
+ int flags, struct store **store);
+
+extern error_t store_bzip2_open (const char *name,
+ int flags, struct store **store);
+
+extern const struct store_class store_gzip_class;
+extern const struct store_class store_bzip2_class;
+
+#endif