diff options
author | Ludovic Courtès <ludo@gnu.org> | 2003-01-31 18:41:20 +0000 |
---|---|---|
committer | Ludovic Courtès <ludo@gnu.org> | 2003-01-31 18:41:20 +0000 |
commit | 823053ec9f580c44aea57ca473a86452e7e332a7 (patch) | |
tree | b26ac31982a993ed21e2021f32d0c9e21f1833b1 |
Initial commit of a libnetfs-based tarfs including a gzip/bzip2 store.
-rw-r--r-- | BUGS | 30 | ||||
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | ChangeLog | 388 | ||||
-rw-r--r-- | Makefile | 65 | ||||
-rw-r--r-- | README | 67 | ||||
-rw-r--r-- | TODO | 15 | ||||
-rw-r--r-- | backend.h | 123 | ||||
-rw-r--r-- | cache.c | 470 | ||||
-rw-r--r-- | cache.h | 83 | ||||
-rw-r--r-- | debug.c | 88 | ||||
-rw-r--r-- | debug.h | 64 | ||||
-rw-r--r-- | fs.c | 739 | ||||
-rw-r--r-- | fs.h | 123 | ||||
-rw-r--r-- | main.c | 76 | ||||
-rw-r--r-- | names.c | 218 | ||||
-rw-r--r-- | names.h | 33 | ||||
-rw-r--r-- | netfs.c | 834 | ||||
-rw-r--r-- | store-bzip2.c | 107 | ||||
-rw-r--r-- | store-gzip.c | 366 | ||||
-rw-r--r-- | tar.c | 501 | ||||
-rw-r--r-- | tar.h | 185 | ||||
-rw-r--r-- | tarfs.c | 1364 | ||||
-rw-r--r-- | tarfs.h | 145 | ||||
-rw-r--r-- | tarlist.c | 232 | ||||
-rwxr-xr-x | testfs.sh | 104 | ||||
-rw-r--r-- | zipstores.c | 1291 | ||||
-rw-r--r-- | zipstores.h | 34 |
27 files changed, 8085 insertions, 0 deletions
@@ -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 @@ -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> @@ -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 @@ -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; +} @@ -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 @@ -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); +} @@ -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'; +} @@ -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, ¬found, 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, ¬found, 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 |