From da6e25e5c86d399ed9b1527b7d49157b72d2c1b2 Mon Sep 17 00:00:00 2001 From: Thomas Schmitt Date: Sat, 23 Feb 2008 10:01:07 +0000 Subject: [PATCH] Effective base of libisoburn+xorriso 0.1.1 2008.02.22.124732 --- AUTHORS | 3 + COPYING | 280 +++ COPYRIGHT | 19 + ChangeLog | 1 + INSTALL | 234 ++ Makefile.am | 180 ++ NEWS | 20 + README | 341 +++ Roadmap | 33 + TODO | 35 + acinclude.m4 | 22 + bootstrap | 10 + configure.ac | 155 ++ demo/cat.c | 74 + demo/cat_buffer.c | 127 + demo/ecma119_tree.c | 136 + demo/iso.c | 176 ++ demo/iso_cat.c | 95 + demo/iso_grow.c | 257 ++ demo/iso_modify.c | 109 + demo/iso_ms.c | 114 + demo/iso_read.c | 167 ++ demo/lsl.c | 130 + demo/tree.c | 107 + doc/Tutorial | 506 ++++ doc/Wiki | 32 + doc/devel/1. Overview | 0 doc/devel/2. Features | 193 ++ doc/devel/3. Use Cases | 193 ++ doc/devel/4. Design | 0 doc/devel/5. Implementation | 0 doc/devel/README | 7 + doc/devel/UML/BuilderSec.png | Bin 0 -> 25202 bytes doc/devel/UML/BuilderSec.violet | 821 ++++++ doc/devel/UML/builder.violet | 884 +++++++ doc/devel/UML/builder.violet.png | Bin 0 -> 34098 bytes doc/devel/UML/burn_source.class.violet | 634 +++++ doc/devel/UML/burn_source.png | Bin 0 -> 35652 bytes doc/devel/UML/eltorito.violet | 552 +++++ doc/devel/UML/eltorito.violet.png | Bin 0 -> 24861 bytes doc/devel/UML/iso_tree.violet | 748 ++++++ doc/devel/UML/iso_tree.violet.png | Bin 0 -> 38189 bytes doc/devel/UML/nglibisofs.violet | 1059 ++++++++ doc/devel/UML/stream.violet | 492 ++++ doc/devel/UML/stream.violet.png | Bin 0 -> 21694 bytes doc/devel/codestyle.xml | 91 + doc/devel/cookbook/ISO 9660-1999 | 119 + doc/doxygen.conf.in | 1298 ++++++++++ libisofs-1.pc.in | 11 + libisofs/buffer.c | 328 +++ libisofs/buffer.h | 95 + libisofs/builder.c | 208 ++ libisofs/builder.h | 81 + libisofs/data_source.c | 195 ++ libisofs/ecma119.c | 1579 ++++++++++++ libisofs/ecma119.h | 476 ++++ libisofs/ecma119_tree.c | 846 +++++++ libisofs/ecma119_tree.h | 90 + libisofs/eltorito.c | 913 +++++++ libisofs/eltorito.h | 103 + libisofs/filesrc.c | 393 +++ libisofs/filesrc.h | 84 + libisofs/fs_image.c | 2642 ++++++++++++++++++++ libisofs/fs_local.c | 645 +++++ libisofs/fsource.c | 111 + libisofs/fsource.h | 32 + libisofs/image.c | 277 +++ libisofs/image.h | 112 + libisofs/iso1999.c | 1016 ++++++++ libisofs/iso1999.h | 59 + libisofs/joliet.c | 1081 ++++++++ libisofs/joliet.h | 56 + libisofs/libiso_msgs.c | 439 ++++ libisofs/libiso_msgs.h | 682 +++++ libisofs/libisofs.h | 3165 ++++++++++++++++++++++++ libisofs/messages.c | 428 ++++ libisofs/messages.h | 49 + libisofs/node.c | 884 +++++++ libisofs/node.h | 306 +++ libisofs/rockridge.c | 1208 +++++++++ libisofs/rockridge.h | 267 ++ libisofs/rockridge_read.c | 419 ++++ libisofs/stream.c | 434 ++++ libisofs/stream.h | 145 ++ libisofs/tree.c | 751 ++++++ libisofs/tree.h | 21 + libisofs/util.c | 1264 ++++++++++ libisofs/util.h | 438 ++++ libisofs/util_htable.c | 340 +++ libisofs/util_rbtree.c | 296 +++ libisofs/writer.h | 43 + test/mocked_fsrc.c | 378 +++ test/mocked_fsrc.h | 31 + test/test.c | 26 + test/test.h | 14 + test/test_image.c | 354 +++ test/test_node.c | 690 ++++++ test/test_rockridge.c | 1395 +++++++++++ test/test_stream.c | 155 ++ test/test_tree.c | 566 +++++ test/test_util.c | 1072 ++++++++ version.h.in | 3 + 102 files changed, 38150 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYRIGHT create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 Roadmap create mode 100644 TODO create mode 100644 acinclude.m4 create mode 100755 bootstrap create mode 100644 configure.ac create mode 100644 demo/cat.c create mode 100644 demo/cat_buffer.c create mode 100644 demo/ecma119_tree.c create mode 100644 demo/iso.c create mode 100644 demo/iso_cat.c create mode 100644 demo/iso_grow.c create mode 100644 demo/iso_modify.c create mode 100644 demo/iso_ms.c create mode 100644 demo/iso_read.c create mode 100644 demo/lsl.c create mode 100644 demo/tree.c create mode 100755 doc/Tutorial create mode 100755 doc/Wiki create mode 100644 doc/devel/1. Overview create mode 100644 doc/devel/2. Features create mode 100644 doc/devel/3. Use Cases create mode 100644 doc/devel/4. Design create mode 100644 doc/devel/5. Implementation create mode 100644 doc/devel/README create mode 100644 doc/devel/UML/BuilderSec.png create mode 100644 doc/devel/UML/BuilderSec.violet create mode 100644 doc/devel/UML/builder.violet create mode 100644 doc/devel/UML/builder.violet.png create mode 100644 doc/devel/UML/burn_source.class.violet create mode 100644 doc/devel/UML/burn_source.png create mode 100644 doc/devel/UML/eltorito.violet create mode 100644 doc/devel/UML/eltorito.violet.png create mode 100644 doc/devel/UML/iso_tree.violet create mode 100644 doc/devel/UML/iso_tree.violet.png create mode 100644 doc/devel/UML/nglibisofs.violet create mode 100644 doc/devel/UML/stream.violet create mode 100644 doc/devel/UML/stream.violet.png create mode 100644 doc/devel/codestyle.xml create mode 100644 doc/devel/cookbook/ISO 9660-1999 create mode 100644 doc/doxygen.conf.in create mode 100644 libisofs-1.pc.in create mode 100644 libisofs/buffer.c create mode 100644 libisofs/buffer.h create mode 100644 libisofs/builder.c create mode 100644 libisofs/builder.h create mode 100644 libisofs/data_source.c create mode 100644 libisofs/ecma119.c create mode 100644 libisofs/ecma119.h create mode 100644 libisofs/ecma119_tree.c create mode 100644 libisofs/ecma119_tree.h create mode 100644 libisofs/eltorito.c create mode 100644 libisofs/eltorito.h create mode 100644 libisofs/filesrc.c create mode 100644 libisofs/filesrc.h create mode 100644 libisofs/fs_image.c create mode 100644 libisofs/fs_local.c create mode 100644 libisofs/fsource.c create mode 100644 libisofs/fsource.h create mode 100644 libisofs/image.c create mode 100644 libisofs/image.h create mode 100644 libisofs/iso1999.c create mode 100644 libisofs/iso1999.h create mode 100644 libisofs/joliet.c create mode 100644 libisofs/joliet.h create mode 100644 libisofs/libiso_msgs.c create mode 100644 libisofs/libiso_msgs.h create mode 100644 libisofs/libisofs.h create mode 100644 libisofs/messages.c create mode 100644 libisofs/messages.h create mode 100644 libisofs/node.c create mode 100644 libisofs/node.h create mode 100644 libisofs/rockridge.c create mode 100644 libisofs/rockridge.h create mode 100644 libisofs/rockridge_read.c create mode 100644 libisofs/stream.c create mode 100644 libisofs/stream.h create mode 100644 libisofs/tree.c create mode 100644 libisofs/tree.h create mode 100644 libisofs/util.c create mode 100644 libisofs/util.h create mode 100644 libisofs/util_htable.c create mode 100644 libisofs/util_rbtree.c create mode 100644 libisofs/writer.h create mode 100644 test/mocked_fsrc.c create mode 100644 test/mocked_fsrc.h create mode 100644 test/test.c create mode 100644 test/test.h create mode 100644 test/test_image.c create mode 100644 test/test_node.c create mode 100644 test/test_rockridge.c create mode 100644 test/test_stream.c create mode 100644 test/test_tree.c create mode 100644 test/test_util.c create mode 100644 version.h.in diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..4b18677 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Vreixo Formoso +Mario Danic +Thomas Schmitt diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5a965fb --- /dev/null +++ b/COPYING @@ -0,0 +1,280 @@ + 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 diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..8d562a3 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,19 @@ +Vreixo Formoso , +Mario Danic , +Thomas Schmitt +Copyright (C) 2007-2008 Vreixo Formoso, Mario Danic, Thomas Schmitt + + + 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 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..5458714 --- /dev/null +++ b/INSTALL @@ -0,0 +1,234 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006 Free Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6492a71 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,180 @@ +pkgconfigdir=$(libdir)/pkgconfig +libincludedir=$(includedir)/libisofs + +lib_LTLIBRARIES = libisofs/libisofs.la + +## ========================================================================= ## + +# Build libraries + +libisofs_libisofs_la_LDFLAGS = \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) +libisofs_libisofs_la_SOURCES = \ + libisofs/builder.h \ + libisofs/builder.c \ + libisofs/node.h \ + libisofs/node.c \ + libisofs/tree.h \ + libisofs/tree.c \ + libisofs/image.h \ + libisofs/image.c \ + libisofs/fsource.h \ + libisofs/fsource.c \ + libisofs/fs_local.c \ + libisofs/fs_image.c \ + libisofs/messages.h \ + libisofs/messages.c \ + libisofs/libiso_msgs.h \ + libisofs/libiso_msgs.c \ + libisofs/stream.h \ + libisofs/stream.c \ + libisofs/util.h \ + libisofs/util.c \ + libisofs/util_rbtree.c \ + libisofs/util_htable.c \ + libisofs/filesrc.h \ + libisofs/filesrc.c \ + libisofs/ecma119.h \ + libisofs/ecma119.c \ + libisofs/ecma119_tree.h \ + libisofs/ecma119_tree.c \ + libisofs/writer.h \ + libisofs/buffer.h \ + libisofs/buffer.c \ + libisofs/rockridge.h \ + libisofs/rockridge.c \ + libisofs/rockridge_read.c \ + libisofs/joliet.h \ + libisofs/joliet.c \ + libisofs/eltorito.h \ + libisofs/eltorito.c \ + libisofs/iso1999.h \ + libisofs/iso1999.c \ + libisofs/data_source.c +libinclude_HEADERS = \ + libisofs/libisofs.h + +## ========================================================================= ## + +## Build demo applications +noinst_PROGRAMS = \ + demo/lsl \ + demo/cat \ + demo/catbuffer \ + demo/tree \ + demo/ecma119tree \ + demo/iso \ + demo/isoread \ + demo/isocat \ + demo/isomodify \ + demo/isoms \ + demo/isogrow + +demo_lsl_CPPFLAGS = -Ilibisofs +demo_lsl_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_lsl_SOURCES = demo/lsl.c + +demo_cat_CPPFLAGS = -Ilibisofs +demo_cat_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_cat_SOURCES = demo/cat.c + +demo_catbuffer_CPPFLAGS = -Ilibisofs +demo_catbuffer_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_catbuffer_SOURCES = demo/cat_buffer.c + +demo_tree_CPPFLAGS = -Ilibisofs +demo_tree_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_tree_SOURCES = demo/tree.c + +demo_ecma119tree_CPPFLAGS = -Ilibisofs +demo_ecma119tree_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_ecma119tree_SOURCES = demo/ecma119_tree.c + +demo_iso_CPPFLAGS = -Ilibisofs +demo_iso_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_iso_SOURCES = demo/iso.c + +demo_isoread_CPPFLAGS = -Ilibisofs +demo_isoread_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_isoread_SOURCES = demo/iso_read.c + +demo_isocat_CPPFLAGS = -Ilibisofs +demo_isocat_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_isocat_SOURCES = demo/iso_cat.c + +demo_isomodify_CPPFLAGS = -Ilibisofs +demo_isomodify_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_isomodify_SOURCES = demo/iso_modify.c + +demo_isoms_CPPFLAGS = -Ilibisofs +demo_isoms_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) +demo_isoms_SOURCES = demo/iso_ms.c + +demo_isogrow_CPPFLAGS = -Ilibisofs -Ilibburn +demo_isogrow_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) -lburn +demo_isogrow_SOURCES = demo/iso_grow.c + + +## Build unit test + +check_PROGRAMS = \ + test/test + +test_test_CPPFLAGS = -Ilibisofs +test_test_LDADD = $(libisofs_libisofs_la_OBJECTS) $(THREAD_LIBS) -lcunit +test_test_LDFLAGS = -L.. -lm + +test_test_SOURCES = \ + test/test.h \ + test/test.c \ + test/test_node.c \ + test/test_image.c \ + test/test_tree.c \ + test/test_util.c \ + test/test_rockridge.c \ + test/test_stream.c \ + test/mocked_fsrc.h \ + test/mocked_fsrc.c + +## ========================================================================= ## + +## Build documentation (You need Doxygen for this to work) + +docdir = $(DESTDIR)$(prefix)/share/doc/$(PACKAGE)-$(VERSION) + +doc: doc/html + +doc/html: doc/doxygen.conf + $(RM) -r doc/html; \ + doxygen doc/doxygen.conf; + +install-data-local: + if [ -d doc/html ]; then \ + $(mkinstalldirs) $(docdir)/html; \ + $(INSTALL_DATA) doc/html/* $(docdir)/html; \ + fi + +uninstall-local: + rm -rf $(docdir) + +## ========================================================================= ## + +# Extra things +nodist_pkgconfig_DATA = \ + libisofs-1.pc + +EXTRA_DIST = \ + libisofs-1.pc.in \ + version.h.in \ + doc/doxygen.conf.in \ + doc/Tutorial \ + README \ + AUTHORS \ + COPYRIGHT \ + COPYING \ + NEWS \ + INSTALL \ + TODO \ + ChangeLog \ + Roadmap + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..8acff7b --- /dev/null +++ b/NEWS @@ -0,0 +1,20 @@ +== unknown == + +Libisofs v0.6.4 +=============== + +- + +== Fri Feb 22 2008 == + +Libisofs v0.6.2.1 +================= + +- FIX: missing buffer.h in tarball + +== Thu Feb 14 2008 == + +Libisofs v0.6.2 +================ + +- Initial release diff --git a/README b/README new file mode 100644 index 0000000..1ead917 --- /dev/null +++ b/README @@ -0,0 +1,341 @@ +------------------------------------------------------------------------------ + libisofs +------------------------------------------------------------------------------ + +Released under GPL (see COPYING file for details). + +Copyright (C) 2008 Vreixo Formoso, Mario Danic, Thomas Schmitt + +libisofs is part of the libburnia project (libburnia-project.org) +------------------------------------------------------------------------------ + +libisofs is a library to create an ISO-9660 filesystem, and supports extensions +like RockRidge or Joliet. It is also a full featured ISO-9660 editor, allowing +you to modify an ISO image or multisession disc, including file addition and +removal, change of file names and attributes, etc + +Features: +--------- + +- Image creation + - Creates ISO-9660 images from local files. + - Support for RockRidge and Joliet extensions. + - Support for ISO-9660:1999 (version 2) + - Support for El-Torito bootable images. + - Full featured edition of file names and attributes on the image. + - Several options to relax ISO-9660 constraints. + - Special options for images intended for distribution (suitable default + modes for files, hiding of real timestamps...) +- Multisession + - Support for growing an existing image + - Full-featured edition of the image files, including: addition of new + files, removing of existent files, moving files, renaming files, + change file attributes (permissions, timestamps...) + - Support for "emulated multisession" or image growing, suitable for non + multisession media such as DVD+RW +- Image modification + - It can create a completely new image from files on another image. + - Full-featured edition of image contents +- Others + - Handling of different input and output charset + - Good integration with libburn for image burning. + - Reliable, good handling of different kind of errors. + +Requirements: +------------- + +- libburn 0.4.2 headers must be installed at compile time. It is not required + at runtime. + +Know bugs: +---------- + +Multisession and image growing can lead to undesired results in several cases: + +a) Images with unsupported features, such as: + - UDF. + - HSF/HFS+ or other Mac extensions. + - El-Torito with multiple entries. + - ECMA-119 with extended attributes, multiple extends per file. + - Non El-Torito boot info. + - zisofs compressed images. + - ... + In all these cases, the resulting new image (or new session) could lack some + features of the original image. + In some cases libisofs will issue warning messages, or even refuse to grow + or modify the image. Others remain undetected. Images created with libisofs + do not have this problems. + +b) Bootable El-Torito images may have several problems, that result in a new + image that is not bootable, or that boots from an outdated session. In many + cases it is recommended to add boot info again in the new session. + + - isolinux images won't be bootable after a modify. This is because + isolinux images need to have hardcoded the root dir lba. libisofs cannot + know whether an image is an isolinux image or not, so the user is + responsible to tell libisofs that it must patch the image, with the + el_torito_patch_isolinux_image() function. This problem could also exists + on other boot images. + - Most boot images are highly dependent of the image contents, so if the + user moves or removes some files on image it is possible they won't boot + anymore. + - There is no safer way to modify hidden boot images, as the size of the + boot image can't be figured out. + +c) Generated images could have different ECMA-119 low level names, due to + different way to mangle names, to new files added that force old files to + be renamed, to different relaxed contraints... This only affect the + ISO-9660 info, not the RR names, so it shouldn't be a problem in most + cases. If your app. relies on low level ISO-9660 names, you will need to + ensure all node names are valid ISO names (maybe together with some + relaxed contraints), otherwise libisofs might arbitrarily change the names. + + +------------------------------------------------------------------------------ + + Download, Build and Installation + +libisofs code is mantained in a Bazaar repository at Launchpad +(https://launchpad.net/libisofs/). You can download it with: + +$ bzr branch lp:libisofs + +Our build system is based on autotools. For preparing the build you will need +autotools of at least version 1.7. If you have download the code from the +repository, first of all you need to execute + + ./autogen.sh + +on toplevel dir to execute autotools. + +Alternatively you may unpack a release tarball for which you do not need +autotools installed. + +To build libisofs it should be sufficient to go into its toplevel directory +and execute + + ./configure --prefix=/usr + make + +To make the libraries accessible for running resp. developing applications + make install + +See INSTALL file for further details. + + +------------------------------------------------------------------------------ + + Overview of libburnia-project.org + +libburnia-project.org is an open-source software project for reading, mastering +and writing optical discs. +For now this means only CD media and all single layer DVD media except DVD+R. + +The project comprises of several more or less interdependent parts which +together strive to be a usable foundation for application development. +These are libraries, language bindings, and middleware binaries which emulate +classical (and valuable) Linux tools. + +Our scope is currently Linux 2.4 and 2.6 only. For ports to other systems +we would need : login on a development machine resp. a live OS on CD or DVD, +advise from a system person about the equivalent of Linux sg or FreeBSD CAM, +volunteers for testing of realistic use cases. + +We have a workable code base for burning CD and most single layer DVD. +The burn API is quite comprehensively documented and can be used to build a +presentable application. +We have a functional binary which emulates parts of cdrecord in order to +prove that usability, and in order to allow you to explore libburnia's scope +by help of existing cdrecord frontends. + +The project components (list subject to growth, hopefully): + +- libburn is the library by which preformatted data get onto optical media. + It uses either /dev/sgN (e.g. on kernel 2.4 with ide-scsi) or + /dev/hdX (e.g. on kernel 2.6). + libburn is the foundation of our cdrecord emulation. Its code is + independent of cdrecord. Its DVD capabilities are learned from + studying the code of dvd+rw-tools and MMC-5 specs. No code but only + the pure SCSI knowledge has been taken from dvd+rw-tools, though. + +- libisofs is the library to pack up hard disk files and directories into a + ISO 9660 disk image. This may then be brought to media via libburn. + libisofs is to be the foundation of our upcoming mkisofs emulation. + +- cdrskin is a limited cdrecord compatibility wrapper for libburn. + Cdrecord is a powerful GPL'ed burn program included in Joerg + Schilling's cdrtools. cdrskin strives to be a second source for + the services traditionally provided by cdrecord. Additionally it + provides libburn's DVD capabilities, where only -sao is compatible + with cdrecord. + cdrskin does not contain any bytes copied from cdrecord's sources. + Many bytes have been copied from the message output of cdrecord + runs, though. + See cdrskin/README and man cdrskin/cdrskin.1 for more. + +- test is a collection of application gestures and examples given by the + authors of the library features. The main API example for libburn + is test/libburner.c . + Explore these examples if you look for inspiration. + +We plan to be a responsive upstream. Bear with us. We are still practicing. + + +------------------------------------------------------------------------------ +Project history as far as known to me: + +- Founded in 2002 as it seems. See mailing list archives + http://lists.freedesktop.org/archives/libburn/ + The site of this founder team is reachable and offers download of a + (somewhat outdated) tarball and from CVS : + http://icculus.org/burn/ + Copyright holders and most probably founders: + Derek Foreman and Ben Jansens. + +- I came to using libburn in 2005. Founded the cdrskin project and submitted + necessary patches which were accepted or implemented better. Except one + remaining patch which prevented cdrskin from using vanilla libburn from CVS. + The cdrskin project site is reachable and offers download of the heavily + patched (elsewise outdated) tarball under the name cdrskin-0.1.2 : + http://scdbackup.sourceforge.net/cdrskin_eng.html + It has meanwhile moved to use vanilla libburn.pykix.org , though. + Version 0.1.4 constitutes the first release of this kind. + +- In July 2006 our team mate Mario Danic announced a revival of libburn + which by about nearly everybody else was perceived as unfriendly fork. + Derek Foreman four days later posted a message which expressed his + discontent. + The situation first caused me to publically regret it and then - after i + got the opportunity to move in with cdrskin - gave me true reason to + personally apologize to Derek Foreman, Ben Jansens and the contibutors at + icculus.org/burn. Posted to both projects: + http://lists.freedesktop.org/archives/libburn/2006-August/000446.html + http://mailman-mail1.webfaction.com/pipermail/libburn-hackers/2006-August/000024.html + +- Mid August 2006 project cdrskin established a branch office in + libburn.pykix.org so that all maintainers of our tools have one single place + to get the current (at least slightely) usable coordinated versions of + everything. + Project cdrskin will live forth independendly for a while but it is committed + to stay in sync with libburn.pykix.org (or some successor, if ever). + cdrskin is also committed to support icculus.org/burn if the pending fork + is made reality by content changes in that project. It will cease to maintain + a patched version of icculus.org/burn though. Precondition for a new + release of cdrskin on base of icculus.org/burn would be the pending + "whitelist patch" therefore. + I would rather prefer if both projects find consense and merge, or at least + cooperate. I have not given up hope totally, yet. + I, personally, will honor any approach. + +- 2nd September 2006 the decision is made to strive for a consolidation of + copyright and a commitment to GPL in a reasonable and open minded way. + This is to avoid long term problems with code of unknown origin and + with finding consense among the not so clearly defined group of copyright + claimers and -holders. + libisofs is already claimed sole copyright Mario Danic. + cdrskin and libburner are already claimed sole copyright Thomas Schmitt. + Rewrites of other components will follow and concluded by claiming full + copyright within the group of libburn.pykix.org-copyright holders. + +- 16th September 2006 feature freeze for release of libburn-0.2.2 . + +- 20th September 2006 release of libburn-0.2.2 . + +- 26th October 2006 feature freeze for cdrskin-0.2.4 based on libburn-0.2.3 . + This version of cdrskin is much more cdrecord compatible in repect + to drive addressing and audio features. + +- 30th October 2006 release of cdrskin-0.2.4 . + +- 13th November 2006 splitting releases of libburn+cdrskin from libisofs. + +- 24th November 2006 release of libburn-0.2.6 and cdrskin-0.2.6 . cdrskin has + become suitable for unaware frontends as long as they perform only the core + of cdrecord use cases (including open-ended input streams, audio, and + multi-session). + +- 28th November 2006 the umbrella project which encloses both, libisofs and + libburn, is now called libburnia. For the origin of this name, see + http://en.wikipedia.org/wiki/Liburnians . + +- 16th January 2007 release of libburn-0.3.0 and cdrskin-0.3.0 . Now the scope + is widened to a first class of DVD media: overwriteable single layer types + DVD-RAM, DVD+RW, DVD-RW. This is not a cdrecord emulation but rather inspired + by dvd+rw-tools' "poor man" writing facility for this class of media. + Taking a bow towards Andy Polyakov. + +- 11th February 2007 version 0.3.2 covers sequential DVD-RW and DVD-R with + multi-session and with DAO. + +- 12th March 2007 version 0.3.4 supports DVD+R and thus covers all single layer + DVD media. Code for double layer DVD+/-R is implemented but awaits a tester + yet. + +- 23th April 2007 version 0.3.6 follows the unanimous opinion of Linux kernel + people that one should not use /dev/sg on kernel 2.6. + +- 31st July 2007 version 0.3.8 marks the first anniversary of libburn revival. + We look back on improved stability, a substantially extended list of media + and write modes, and better protection against typical user mishaps. + +- 24th October 2007 version 0.4.0 is the foundation of new library libisoburn + and an upcomming integrated application for manipulating and writing + ISO 9660 + Rock Ridge images. cdrskin-0.4.0 got capabilities like growisofs + by these enhancements: growing of overwriteable media and disk files. + Taking again a bow towards Andy Polyakov. + +- 26th Januar 2008 version 0.4.2 rectifies the version numbering so that we + reliably release libburn.so.4 as should have been done since libburn-0.3.2. + cdrskin now is by default linked dynamically and does a runtime check + to ensure not to be started with a libburn which is older than itself. + + +------------------------------------------------------------------------------ + + 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. To be exact: version 2 of that License. + + 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 + +------------------------------------------------------------------------------ +Clarification in my name and in the name of Mario Danic, upcoming copyright +holders on toplevel of libburnia. To be fully in effect after the remaining +other copyrighted code has been replaced by ours and by copyright-free +contributions of our friends: +------------------------------------------------------------------------------ + +We, the copyright holders, agree on the interpretation that +dynamical linking of our libraries constitutes "use of" and +not "derivation from" our work in the sense of GPL, provided +those libraries are compiled from our unaltered code. + +Thus you may link our libraries dynamically with applications +which are not under GPL. You may distribute our libraries and +application tools in binary form, if you fulfill the usual +condition of GPL to offer a copy of the source code -altered +or unaltered- under GPL. + +We ask you politely to use our work in open source spirit +and with the due reference to the entire open source community. + +If there should really arise the case where above clarification +does not suffice to fulfill a clear and neat request in open source +spirit that would otherwise be declined for mere formal reasons, +only in that case we will duely consider to issue a special license +covering only that special case. +It is the open source idea of responsible freedom which will be +decisive and you will have to prove that you exhausted all own +means to qualify for GPL. + +For now we are firmly committed to maintain one single license: GPL. + +signed: Mario Danic, Thomas Schmitt + diff --git a/Roadmap b/Roadmap new file mode 100644 index 0000000..926ecef --- /dev/null +++ b/Roadmap @@ -0,0 +1,33 @@ + +>>>>>>>>>> RELEASE 0.6.1 (development) >>>>>>>>>>>>>>>>>>>>> + +- Review error severities +OK - Prepare API for stability and compatibility check +- Documentation + +>>>>>>>>>> RELEASE 0.6.2 (stable) >>>>>>>>>>>>>>>>>>>>>>>>>> + +- Intensive testing and bug fixing + +>>>>>>>>>> RELEASE 0.6.3 (development) >>>>>>>>>>>>>>>>>>>>> + +- Improves to public tree + -> Expose node extended info. Always compile it. + (little memory cost) + -> Review builder / tree / node relation + -> Optimize storage of children in node? + -> Inode object? +- Expose Builder and Streams +- Implement filters: compression, encryption... +- Consider some kind of plugin system for Builders, Filesystems and Filters. +- ECMA-119, Joliet, and ISO-9660:1999 writers can share most of the code. + Create a new writer as a generalization of these. +- Update Java bindings + +>>>>>>>>>>> ...... + +>>>>>>>>>>> RELEASE 1.0.0 (stable) >>>>>>>>>>>>>>>>>>>>>>>>>> + +- UDF +- HFS + diff --git a/TODO b/TODO new file mode 100644 index 0000000..bad313f --- /dev/null +++ b/TODO @@ -0,0 +1,35 @@ +FEATURES +======== + + +TODO +==== + +#00001 (node.h) -> consider adding new timestamps to IsoTreeNode +#00004 (libisofs.h) -> Add a get_mime_type() function. +#00005 (node.c) -> optimize iso_dir_iter_take. +#00006 (libisofs.h) -> define more replace values when adding a node to a dir +#00007 (libisofs.h) -> expose iso_tree_add_new_file +#00008 (data_dource.c) -> guard against partial reads +#00009 (ecma119_tree.c/h) -> add true support for harlinks and inode numbers +#00010 (buffer.c) -> optimize ring buffer +#00011 (ecma119.c) -> guard against bad path table usage with more than 65535 dirs +#00012 (fs_image.c) -> support follow symlinks on imafe filesystem +#00013 (fs_image.c) -> check for unsupported flags when reading a dir record +#00014 (fs_image.c) -> more sanity checks to ensure dir record info is valid +#00015 (fs_image.c) -> take care of CD-ROM XA discs when reading SP entry +#00016 (fs_image.c) -> handle non RR ER entries +#00017 (fs_image.c) -> take advantage of other atts of PVD +#00018 (fs_image.c) -> check if there are more entries in the boot catalog +#00019 (fs_image.c) -> set IsoImage attribs from Joliet SVD? +#00020 (fs_image.c) -> handle RR info in Joliet tree +#00021 (fs_image.c) -> handle RR info in ISO 9660:1999 tree +#00022 (joliet.c) -> support relaxed constraints in joliet filenames +#00024 (libisofs.h) -> option to convert names to lower case for iso reading +#00025 (libisofs.h) -> support for merging old image files +#00026 (libisofs.h) -> add support for "hidden" bootable images. +#00027 (iso1999.h) -> Follow ISO 9660:1999 specs when sorting files + +FIXME +===== + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..861847b --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,22 @@ +AC_DEFUN([TARGET_SHIZZLE], +[ + ARCH="" + + AC_MSG_CHECKING([target operating system]) + + case $target in + *-*-linux*) + ARCH=linux + LIBBURN_ARCH_LIBS= + ;; + *-*-freebsd*) + ARCH=freebsd + LIBBURN_ARCH_LIBS=-lcam + ;; + *) + AC_ERROR([You are attempting to compile for an unsupported platform]) + ;; + esac + + AC_MSG_RESULT([$ARCH]) +]) diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..86709bf --- /dev/null +++ b/bootstrap @@ -0,0 +1,10 @@ +#!/bin/sh -x + +aclocal +libtoolize --copy --force +autoconf + +# ts A61101 : libburn is not prepared for config.h +# autoheader + +automake --foreign --add-missing --copy --include-deps diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..69b7dc5 --- /dev/null +++ b/configure.ac @@ -0,0 +1,155 @@ +AC_INIT([libisofs], [0.6.3], [http://libburnia-project.org]) +AC_PREREQ([2.50]) +dnl AC_CONFIG_HEADER([config.h]) + +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects]) + +dnl A61101 This breaks Linux build (makes 32 bit off_t) +dnl http://sourceware.org/autobook/autobook/autobook_96.html says +dnl one must include some config.h and this was a pitfall. +dnl So why dig the pit at all ? +dnl AM_CONFIG_HEADER(config.h) + +dnl +dnl if MAJOR or MINOR version changes, be sure to change AC_INIT above to match +dnl +dnl CURRENT and AGE describe the binary compatibility interval of a +dnl dynamic library. +dnl See also http://www.gnu.org/software/libtool/manual.html#Interfaces +dnl +dnl The name of the library will be libisofs.so.$CURRENT-$AGE.$AGE.$REV +dnl In the terminology of this file: +dnl CURRENT = LT_CURRENT +dnl REV = LT_REVISION +dnl AGE = LT_AGE +dnl +dnl LT_CURRENT, LT_REVISION and LT_AGE get set directly now. +dnl +dnl SONAME of the emerging library is LT_CURRENT - LT_AGE. +dnl The linker will do no finer checks. Especially no age range check for +dnl the cdrskin binary. If SONAME matches, then the couple starts. +dnl +dnl Therefore a run time check is provided by libisofs function +dnl iso_lib_version(). It returns the major, minor and micro revision of the +dnl library. This means LIBISOFS_*_VERSION kept its second job which does not +dnl comply to the usual ways of configure.ac . I.e. now *officially* this is +dnl the source code release version as announced to the public. It has no +dnl conection to SONAME or libtool version numbering. +dnl It rather feeds the API function iso_lib_version(). +dnl +dnl If LIBISOFS_*_VERSION changes, be sure to change AC_INIT above to match. +dnl +LIBISOFS_MAJOR_VERSION=0 +LIBISOFS_MINOR_VERSION=6 +LIBISOFS_MICRO_VERSION=3 +LIBISOFS_VERSION=$LIBISOFS_MAJOR_VERSION.$LIBISOFS_MINOR_VERSION.$LIBISOFS_MICRO_VERSION + +AC_SUBST(LIBISOFS_MAJOR_VERSION) +AC_SUBST(LIBISOFS_MINOR_VERSION) +AC_SUBST(LIBISOFS_MICRO_VERSION) +AC_SUBST(LIBISOFS_VERSION) + +dnl Libtool versioning +LT_RELEASE=$LIBISOFS_MAJOR_VERSION.$LIBISOFS_MINOR_VERSION +# SONAME = 6 - 0 = 6 . Library name = libisofs.6.0.0 +LT_CURRENT=6 +LT_REVISION=0 +LT_AGE=0 +LT_CURRENT_MINUS_AGE=`expr $LT_CURRENT - $LT_AGE` + +AC_SUBST(LT_RELEASE) +AC_SUBST(LT_CURRENT) +AC_SUBST(LT_REVISION) +AC_SUBST(LT_AGE) +AC_SUBST(LT_CURRENT_MINUS_AGE) + +AC_PREFIX_DEFAULT([/usr/local]) +test "$prefix" = "NONE" && prefix=$ac_default_prefix + +AM_MAINTAINER_MODE + +AM_PROG_CC_C_O +AC_C_CONST +AC_C_INLINE +AC_C_BIGENDIAN + +dnl Large file support +AC_SYS_LARGEFILE +AC_FUNC_FSEEKO +AC_CHECK_FUNC([fseeko]) +if test ! $ac_cv_func_fseeko; then + AC_MSG_ERROR([Libisofs requires largefile support.]) +fi + +AC_PROG_LIBTOOL +AC_SUBST(LIBTOOL_DEPS) +LIBTOOL="$LIBTOOL --silent" + +AC_PROG_INSTALL + +AC_CHECK_HEADERS() + +dnl Use GNU extensions if available +AC_DEFINE(_GNU_SOURCE, 1) + +dnl Check for tm_gmtoff field in struct tm +AC_CHECK_MEMBER([struct tm.tm_gmtoff], + [AC_DEFINE(HAVE_TM_GMTOFF, 1, + [Define this if tm structure includes a tm_gmtoff entry.])], + , + [#include ]) + +dnl Check if non standard timegm() function is available +AC_CHECK_DECL([timegm], + [AC_DEFINE(HAVE_TIMEGM, 1, [Define this if timegm function is available])], + , + [#include ]) + +dnl Check if non standard eaccess() function is available +AC_CHECK_DECL([eaccess], + [AC_DEFINE(HAVE_EACCESS, 1, [Define this if eaccess function is available])], + , + [#include ]) + +THREAD_LIBS=-lpthread +AC_SUBST(THREAD_LIBS) + +TARGET_SHIZZLE +AC_SUBST(ARCH) +AC_SUBST(LIBBURN_ARCH_LIBS) + +dnl Add compiler-specific flags + +dnl See if the user wants aggressive optimizations of the code +AC_ARG_ENABLE(debug, +[ --enable-debug Disable aggressive optimizations [default=yes]], + , enable_debug=yes) +if test x$enable_debug != xyes; then + if test x$GCC = xyes; then + CFLAGS="$CFLAGS -O3" + CFLAGS="$CFLAGS -fexpensive-optimizations" + fi + CFLAGS="$CFLAGS -DNDEBUG" +else + if test x$GCC = xyes; then + CFLAGS="$CFLAGS -g -pedantic -Wall" + fi + CFLAGS="$CFLAGS -DDEBUG" +fi + +dnl Verbose debug to make libisofs issue more debug messages +AC_ARG_ENABLE(verbose-debug, +[ --enable-verbose-debug Enable verbose debug messages [default=no]], + AC_DEFINE(LIBISOFS_VERBOSE_DEBUG, 1)) + + +AC_CONFIG_FILES([ + Makefile + doc/doxygen.conf + version.h + libisofs-1.pc + ]) +AC_OUTPUT diff --git a/demo/cat.c b/demo/cat.c new file mode 100644 index 0000000..a95eebc --- /dev/null +++ b/demo/cat.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "fsource.h" + +#include +#include + +/* + * Little test program to test filesystem implementations. + * Outputs file contents to stdout! + */ + +int main(int argc, char **argv) +{ + int res; + IsoFilesystem *fs; + IsoFileSource *file; + struct stat info; + + if (argc != 2) { + fprintf(stderr, "Usage: cat /path/to/file\n"); + return 1; + } + + /* create filesystem object */ + res = iso_local_filesystem_new(&fs); + if (res < 0) { + fprintf(stderr, "Can't get local fs object, err = %d\n", res); + return 1; + } + + res = fs->get_by_path(fs, argv[1], &file); + if (res < 0) { + fprintf(stderr, "Can't get file, err = %d\n", res); + return 1; + } + + res = iso_file_source_lstat(file, &info); + if (res < 0) { + fprintf(stderr, "Can't stat file, err = %d\n", res); + return 1; + } + + if (S_ISDIR(info.st_mode)) { + fprintf(stderr, "Path refers to a directory!!\n"); + return 1; + } else { + char buf[1024]; + res = iso_file_source_open(file); + if (res < 0) { + fprintf(stderr, "Can't open file, err = %d\n", res); + return 1; + } + while ((res = iso_file_source_read(file, buf, 1024)) > 0) { + fwrite(buf, 1, res, stdout); + } + if (res < 0) { + fprintf(stderr, "Error reading, err = %d\n", res); + return 1; + } + iso_file_source_close(file); + } + + iso_file_source_unref(file); + iso_filesystem_unref(fs); + return 0; +} diff --git a/demo/cat_buffer.c b/demo/cat_buffer.c new file mode 100644 index 0000000..74657ee --- /dev/null +++ b/demo/cat_buffer.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "buffer.h" + +#include +#include +#include +#include +#include +#include +#include + +/* + * Little test program that reads a file and outputs it to stdout, using + * the libisofs ring buffer as intermediate memory + */ + +struct th_data +{ + IsoRingBuffer *rbuf; + char *path; +}; + +#define WRITE_CHUNK 2048 +#define READ_CHUNK 2048 + +static +void *write_function(void *arg) +{ + ssize_t bytes; + int res; + unsigned char tmp[WRITE_CHUNK]; + struct th_data *data = (struct th_data *) arg; + + int fd = open(data->path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Writer thread error: Can't open file"); + iso_ring_buffer_writer_close(data->rbuf, 1); + pthread_exit(NULL); + } + + res = 1; + while ( (bytes = read(fd, tmp, WRITE_CHUNK)) > 0) { + res = iso_ring_buffer_write(data->rbuf, tmp, bytes); + if (res <= 0) { + break; + } + /* To test premature reader exit >>>>>>>>>>> + iso_ring_buffer_writer_close(data->rbuf); + pthread_exit(NULL); + <<<<<<<<<<<<<<<<<<<<<<<<< */ + // if (rand() > 2000000000) { + // fprintf(stderr, "Writer sleeping\n"); + // sleep(1); + // } + } + fprintf(stderr, "Writer finish: %d\n", res); + + close(fd); + iso_ring_buffer_writer_close(data->rbuf, 0); + pthread_exit(NULL); +} + +static +void *read_function(void *arg) +{ + unsigned char tmp[READ_CHUNK]; + int res = 1; + struct th_data *data = (struct th_data *) arg; + + while ( (res = iso_ring_buffer_read(data->rbuf, tmp, READ_CHUNK)) > 0) { + write(1, tmp, READ_CHUNK); + /* To test premature reader exit >>>>>>>>>>> + iso_ring_buffer_reader_close(data->rbuf); + pthread_exit(NULL); + <<<<<<<<<<<<<<<<<<<<<<<<< */ + // if (rand() > 2000000000) { + // fprintf(stderr, "Reader sleeping\n"); + // sleep(1); + // } + } + fprintf(stderr, "Reader finish: %d\n", res); + + iso_ring_buffer_reader_close(data->rbuf, 0); + + pthread_exit(NULL); +} + +int main(int argc, char **argv) +{ + int res; + struct th_data data; + pthread_t reader; + pthread_t writer; + + if (argc != 2) { + fprintf(stderr, "Usage: catbuffer /path/to/file\n"); + return 1; + } + + res = iso_ring_buffer_new(1024, &data.rbuf); + if (res < 0) { + fprintf(stderr, "Can't create buffer\n"); + return 1; + } + data.path = argv[1]; + + res = pthread_create(&writer, NULL, write_function, (void *) &data); + res = pthread_create(&reader, NULL, read_function, (void *) &data); + + pthread_join(writer, NULL); + pthread_join(reader, NULL); + + fprintf(stderr, "Buffer was %d times full and %d times empty.\n", + iso_ring_buffer_get_times_full(data.rbuf), + iso_ring_buffer_get_times_empty(data.rbuf)); + + free(data.rbuf); + return 0; +} diff --git a/demo/ecma119_tree.c b/demo/ecma119_tree.c new file mode 100644 index 0000000..e1c4dbe --- /dev/null +++ b/demo/ecma119_tree.c @@ -0,0 +1,136 @@ +/* + * Little program that imports a directory to iso image, generates the + * ecma119 low level tree and prints it. + * Note that this is not an API example, but a little program for test + * purposes. + */ + +#include "libisofs.h" +#include "ecma119.h" +#include "ecma119_tree.h" +#include "util.h" +#include "filesrc.h" +#include "node.h" +#include +#include +#include +#include +#include +#include + +static void +print_permissions(mode_t mode) +{ + char perm[10]; + + //TODO suid, sticky... + + perm[9] = '\0'; + perm[8] = mode & S_IXOTH ? 'x' : '-'; + perm[7] = mode & S_IWOTH ? 'w' : '-'; + perm[6] = mode & S_IROTH ? 'r' : '-'; + perm[5] = mode & S_IXGRP ? 'x' : '-'; + perm[4] = mode & S_IWGRP ? 'w' : '-'; + perm[3] = mode & S_IRGRP ? 'r' : '-'; + perm[2] = mode & S_IXUSR ? 'x' : '-'; + perm[1] = mode & S_IWUSR ? 'w' : '-'; + perm[0] = mode & S_IRUSR ? 'r' : '-'; + printf("[%s]",perm); +} + +static void +print_dir(Ecma119Node *dir, int level) +{ + int i; + char *sp = alloca(level * 2 + 1); + + for (i = 0; i < level * 2; i += 2) { + sp[i] = '|'; + sp[i+1] = ' '; + } + + sp[level * 2-1] = '-'; + sp[level * 2] = '\0'; + + for (i = 0; i < dir->info.dir->nchildren; i++) { + Ecma119Node *child = dir->info.dir->children[i]; + + if (child->type == ECMA119_DIR) { + printf("%s+[D] ", sp); + print_permissions(iso_node_get_permissions(child->node)); + printf(" %s\n", child->iso_name); + print_dir(child, level+1); + } else if (child->type == ECMA119_FILE) { + printf("%s-[F] ", sp); + print_permissions(iso_node_get_permissions(child->node)); + printf(" %s {%p}\n", child->iso_name, (void*)child->info.file); + } else if (child->type == ECMA119_SYMLINK) { + printf("%s-[L] ", sp); + print_permissions(iso_node_get_permissions(child->node)); + printf(" %s -> %s\n", child->iso_name, + ((IsoSymlink*)child->node)->dest); + } else if (child->type == ECMA119_SPECIAL) { + printf("%s-[S] ", sp); + print_permissions(iso_node_get_permissions(child->node)); + printf(" %s\n", child->iso_name); + } else if (child->type == ECMA119_PLACEHOLDER) { + printf("%s-[RD] ", sp); + print_permissions(iso_node_get_permissions(child->node)); + printf(" %s\n", child->iso_name); + } else { + printf("%s-[????] ", sp); + } + } +} + +int main(int argc, char **argv) +{ + int result; + IsoImage *image; + Ecma119Image *ecma119; + + if (argc != 2) { + printf ("You need to specify a valid path\n"); + return 1; + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + result = iso_image_new("volume_id", &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[1]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + ecma119 = calloc(1, sizeof(Ecma119Image)); + iso_rbtree_new(iso_file_src_cmp, &(ecma119->files)); + ecma119->iso_level = 1; + ecma119->rockridge = 1; + ecma119->image = image; + ecma119->input_charset = strdup("UTF-8"); + + /* create low level tree */ + result = ecma119_tree_create(ecma119); + if (result < 0) { + printf ("Error creating ecma-119 tree: %d\n", result); + return 1; + } + + printf("================= ECMA-119 TREE =================\n"); + print_dir(ecma119->root, 0); + printf("\n\n"); + + ecma119_node_free(ecma119->root); + iso_rbtree_destroy(ecma119->files, iso_file_src_free); + free(ecma119->input_charset); + free(ecma119); + iso_image_unref(image); + iso_finish(); + return 0; +} diff --git a/demo/iso.c b/demo/iso.c new file mode 100644 index 0000000..f3783b0 --- /dev/null +++ b/demo/iso.c @@ -0,0 +1,176 @@ +/* + * Little program to show how to create an iso image from a local + * directory. + */ + +#include "libisofs.h" +#include "libburn/libburn.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char * const optstring = "JRIL:b:hV:"; +extern char *optarg; +extern int optind; + +void usage(char **argv) +{ + printf("%s [OPTIONS] DIRECTORY OUTPUT\n", argv[0]); +} + +void help() +{ + printf( + "Options:\n" + " -J Add Joliet support\n" + " -R Add Rock Ridge support\n" + " -I Add ISO 9660:1999 support\n" + " -V label Volume Label\n" + " -L Set the ISO level (1 or 2)\n" + " -b file Specifies a boot image to add to image\n" + " -h Print this message\n" + ); +} + +int callback(IsoFileSource *src) +{ + char *path = iso_file_source_get_path(src); + printf("CALLBACK: %s\n", path); + free(path); + return 1; +} + +int main(int argc, char **argv) +{ + int result; + int c; + IsoImage *image; + struct burn_source *burn_src; + unsigned char buf[2048]; + FILE *fd; + IsoWriteOpts *opts; + char *volid = "VOLID"; + char *boot_img = NULL; + int rr = 0, j = 0, iso1999 = 0, level = 1; + + while ((c = getopt(argc, argv, optstring)) != -1) { + switch(c) { + case 'h': + usage(argv); + help(); + exit(0); + break; + case 'J': + j = 1; + break; + case 'R': + rr = 1; + break; + case 'I': + iso1999 = 1; + break; + case 'L': + level = atoi(optarg); + break; + case 'b': + boot_img = optarg; + break; + case 'V': + volid = optarg; + break; + case '?': + usage(argv); + exit(1); + break; + } + } + + if (argc < 2) { + printf ("Please pass directory from which to build ISO\n"); + usage(argv); + return 1; + } + if (argc < 3) { + printf ("Please supply output file\n"); + usage(argv); + return 1; + } + + fd = fopen(argv[optind+1], "w"); + if (!fd) { + err(1, "error opening output file"); + } + + result = iso_init(); + if (result < 0) { + printf ("Can't initialize libisofs\n"); + return 1; + } + iso_set_msgs_severities("NEVER", "ALL", ""); + + result = iso_image_new(volid, &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + iso_tree_set_follow_symlinks(image, 0); + iso_tree_set_ignore_hidden(image, 0); + iso_tree_set_ignore_special(image, 0); + iso_set_abort_severity("SORRY"); + /*iso_tree_set_report_callback(image, callback);*/ + + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[optind]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + if (boot_img) { + /* adds El-Torito boot info. Tunned for isolinux */ + ElToritoBootImage *bootimg; + result = iso_image_set_boot_image(image, boot_img, ELTORITO_NO_EMUL, + "/isolinux/boot.cat", &bootimg); + if (result < 0) { + printf ("Error adding boot image %d\n", result); + return 1; + } + el_torito_set_load_size(bootimg, 4); + el_torito_patch_isolinux_image(bootimg); + } + + result = iso_write_opts_new(&opts, 0); + if (result < 0) { + printf ("Cant create write opts, error %d\n", result); + return 1; + } + iso_write_opts_set_iso_level(opts, level); + iso_write_opts_set_rockridge(opts, rr); + iso_write_opts_set_joliet(opts, j); + iso_write_opts_set_iso1999(opts, iso1999); + + result = iso_image_create_burn_source(image, opts, &burn_src); + if (result < 0) { + printf ("Cant create image, error %d\n", result); + return 1; + } + + iso_write_opts_free(opts); + + while (burn_src->read_xt(burn_src, buf, 2048) == 2048) { + fwrite(buf, 1, 2048, fd); + } + fclose(fd); + burn_src->free_data(burn_src); + free(burn_src); + + iso_image_unref(image); + iso_finish(); + return 0; +} diff --git a/demo/iso_cat.c b/demo/iso_cat.c new file mode 100644 index 0000000..452d1b1 --- /dev/null +++ b/demo/iso_cat.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + + +#include +#include + +#include "libisofs.h" + +/* + * Little test program that extracts a file form a given ISO image. + * Outputs file contents to stdout! + */ + +int main(int argc, char **argv) +{ + int res; + IsoFilesystem *fs; + IsoFileSource *file; + struct stat info; + IsoDataSource *src; + IsoReadOpts *opts; + + if (argc != 3) { + fprintf(stderr, "Usage: isocat /path/to/image /path/to/file\n"); + return 1; + } + + res = iso_init(); + if (res < 0) { + fprintf(stderr, "Can't init libisofs\n"); + return 1; + } + + res = iso_data_source_new_from_file(argv[1], &src); + if (res < 0) { + fprintf(stderr, "Error creating data source\n"); + return 1; + } + + res = iso_read_opts_new(&opts, 0); + if (res < 0) { + fprintf(stderr, "Error creating read options\n"); + return 1; + } + res = iso_image_filesystem_new(src, opts, 1, &fs); + if (res < 0) { + fprintf(stderr, "Error creating filesystem\n"); + return 1; + } + iso_read_opts_free(opts); + + res = fs->get_by_path(fs, argv[2], &file); + if (res < 0) { + fprintf(stderr, "Can't get file, err = %d\n", res); + return 1; + } + + res = iso_file_source_lstat(file, &info); + if (res < 0) { + fprintf(stderr, "Can't stat file, err = %d\n", res); + return 1; + } + + if (S_ISDIR(info.st_mode)) { + fprintf(stderr, "Path refers to a directory!!\n"); + return 1; + } else { + char buf[1024]; + res = iso_file_source_open(file); + if (res < 0) { + fprintf(stderr, "Can't open file, err = %d\n", res); + return 1; + } + while ((res = iso_file_source_read(file, buf, 1024)) > 0) { + fwrite(buf, 1, res, stdout); + } + if (res < 0) { + fprintf(stderr, "Error reading, err = %d\n", res); + return 1; + } + iso_file_source_close(file); + } + + iso_file_source_unref(file); + iso_filesystem_unref(fs); + iso_data_source_unref(src); + iso_finish(); + return 0; +} diff --git a/demo/iso_grow.c b/demo/iso_grow.c new file mode 100644 index 0000000..951aec1 --- /dev/null +++ b/demo/iso_grow.c @@ -0,0 +1,257 @@ +/* + * Very simple program to show how to grow an iso image. + */ + +#include "libisofs.h" +#include "libburn/libburn.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static IsoDataSource *libburn_data_source_new(struct burn_drive *d); + +void usage(char **argv) +{ + printf("%s DISC DIRECTORY\n", argv[0]); +} + +int main(int argc, char **argv) +{ + int result; + IsoImage *image; + IsoDataSource *src; + struct burn_source *burn_src; + struct burn_drive_info *drives; + struct burn_drive *drive; + unsigned char buf[32 * 2048]; + IsoWriteOpts *opts; + int ret = 0; + IsoReadImageFeatures *features; + uint32_t ms_block; + IsoReadOpts *ropts; + + if (argc < 3) { + usage(argv); + return 1; + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + + /* create the image context */ + result = iso_image_new("volume_id", &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + iso_tree_set_follow_symlinks(image, 0); + iso_tree_set_ignore_hidden(image, 0); + + if (!burn_initialize()) { + err(1, "Can't init libburn"); + } + burn_msgs_set_severities("NEVER", "SORRY", "libburner : "); + + if (burn_drive_scan_and_grab(&drives, argv[1], 0) != 1) { + err(1, "Can't open device. Are you sure it is a valid drive?\n"); + } + + drive = drives[0].drive; + +#ifdef ISO_GROW_CHECK_MEDIA + { + /* some check before going on */ + enum burn_disc_status state; + int pno; + char name[80]; + + state = burn_disc_get_status(drive); + burn_disc_get_profile(drive, &pno, name); + + /* + * my drives report BURN_DISC_BLANK on a DVD+RW with data. + * is that correct? + */ + if ( (pno != 0x1a) /*|| (state != BURN_DISC_FULL)*/ ) { + printf("You need to insert a DVD+RW with some data.\n"); + printf("Profile: %x, state: %d.\n", pno, state); + ret = 1; + goto exit_cleanup; + } + } +#endif + + /* create the data source to accesss previous image */ + src = libburn_data_source_new(drive); + if (src == NULL) { + printf("Can't create data source.\n"); + ret = 1; + goto exit_cleanup; + } + + /* import previous image */ + ret = iso_read_opts_new(&ropts, 0); + if (ret < 0) { + fprintf(stderr, "Error creating read options\n"); + return 1; + } + result = iso_image_import(image, src, ropts, &features); + iso_data_source_unref(src); + if (result < 0) { + printf ("Error importing previous session %d\n", result); + return 1; + } + iso_read_opts_free(ropts); + + iso_tree_set_replace_mode(image, ISO_REPLACE_IF_NEWER); + + /* add new dir */ + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[2]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + /* generate a multisession image with new contents */ + result = iso_write_opts_new(&opts, 1); + if (result < 0) { + printf("Cant create write opts, error %d\n", result); + return 1; + } + + /* round up to 32kb aligment = 16 block */ + ms_block = ((iso_read_image_features_get_size(features) + 15) / 16 ) * 16; + iso_write_opts_set_ms_block(opts, ms_block); + iso_write_opts_set_appendable(opts, 1); + iso_write_opts_set_overwrite_buf(opts, buf); + + iso_read_image_features_destroy(features); + + result = iso_image_create_burn_source(image, opts, &burn_src); + if (result < 0) { + printf("Cant create image, error %d\n", result); + return 1; + } + + iso_write_opts_free(opts); + + /* a. write the new image */ + printf("Adding new data...\n"); + { + struct burn_disc *target_disc; + struct burn_session *session; + struct burn_write_opts *burn_options; + struct burn_track *track; + struct burn_progress progress; + char reasons[BURN_REASONS_LEN]; + + target_disc = burn_disc_create(); + session = burn_session_create(); + burn_disc_add_session(target_disc, session, BURN_POS_END); + + track = burn_track_create(); + burn_track_set_source(track, burn_src); + burn_session_add_track(session, track, BURN_POS_END); + + burn_options = burn_write_opts_new(drive); + burn_drive_set_speed(drive, 0, 0); + burn_write_opts_set_underrun_proof(burn_options, 1); + + /* mmm, check for 32K alignment? */ + burn_write_opts_set_start_byte(burn_options, ms_block * 2048); + + if (burn_write_opts_auto_write_type(burn_options, target_disc, + reasons, 0) == BURN_WRITE_NONE) { + printf("Failed to find a suitable write mode:\n%s\n", reasons); + ret = 1; + goto exit_cleanup; + } + + /* ok, write the new track */ + burn_disc_write(burn_options, target_disc); + burn_write_opts_free(burn_options); + + while (burn_drive_get_status(drive, NULL) == BURN_DRIVE_SPAWNING) + usleep(1002); + + while (burn_drive_get_status(drive, &progress) != BURN_DRIVE_IDLE) { + printf("Writing: sector %d of %d\n", progress.sector, progress.sectors); + sleep(1); + } + + } + + /* b. write the new vol desc */ + printf("Writing the new vol desc...\n"); + ret = burn_random_access_write(drive, 0, (char*)buf, 32*2048, 0); + if (ret != 1) { + printf("Ups, new vol desc write failed\n"); + } + + iso_image_unref(image); + +exit_cleanup:; + burn_drive_release(drives[0].drive, 0); + burn_finish(); + iso_finish(); + + exit(ret); +} + +static int +libburn_ds_read_block(IsoDataSource *src, uint32_t lba, uint8_t *buffer) +{ + struct burn_drive *d; + off_t data_count; + + d = (struct burn_drive*)src->data; + + if ( burn_read_data(d, (off_t) lba * (off_t) 2048, (char*)buffer, + 2048, &data_count, 0) < 0 ) { + return -1; /* error */ + } + + return 1; +} + +static +int libburn_ds_open(IsoDataSource *src) +{ + /* nothing to do, device is always opened */ + return 1; +} + +static +int libburn_ds_close(IsoDataSource *src) +{ + /* nothing to do, device is always opened */ + return 1; +} + +static void +libburn_ds_free_data(IsoDataSource *src) +{ + /* nothing to do */ +} + +static IsoDataSource * +libburn_data_source_new(struct burn_drive *d) +{ + IsoDataSource *ret; + + ret = malloc(sizeof(IsoDataSource)); + ret->version = 0; + ret->refcount = 1; + ret->read_block = libburn_ds_read_block; + ret->open = libburn_ds_open; + ret->close = libburn_ds_close; + ret->free_data = libburn_ds_free_data; + ret->data = d; + return ret; +} diff --git a/demo/iso_modify.c b/demo/iso_modify.c new file mode 100644 index 0000000..85404ce --- /dev/null +++ b/demo/iso_modify.c @@ -0,0 +1,109 @@ +/* + * Little program to show how to modify an iso image. + */ + +#include "libisofs.h" +#include "libburn/libburn.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +void usage(char **argv) +{ + printf("%s [OPTIONS] IMAGE DIRECTORY OUTPUT\n", argv[0]); +} + +int main(int argc, char **argv) +{ + int result; + IsoImage *image; + IsoDataSource *src; + struct burn_source *burn_src; + unsigned char buf[2048]; + FILE *fd; + IsoWriteOpts *opts; + IsoReadOpts *ropts; + + if (argc < 4) { + usage(argv); + return 1; + } + + fd = fopen(argv[3], "w"); + if (!fd) { + err(1, "error opening output file"); + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + + /* create the data source to accesss previous image */ + result = iso_data_source_new_from_file(argv[1], &src); + if (result < 0) { + printf ("Error creating data source\n"); + return 1; + } + + /* create the image context */ + result = iso_image_new("volume_id", &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + iso_tree_set_follow_symlinks(image, 0); + iso_tree_set_ignore_hidden(image, 0); + + /* import previous image */ + result = iso_read_opts_new(&ropts, 0); + if (result < 0) { + fprintf(stderr, "Error creating read options\n"); + return 1; + } + result = iso_image_import(image, src, ropts, NULL); + iso_read_opts_free(ropts); + iso_data_source_unref(src); + if (result < 0) { + printf ("Error importing previous session %d\n", result); + return 1; + } + + /* add new dir */ + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[2]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + /* generate a new image with both previous and added contents */ + result = iso_write_opts_new(&opts, 1); + if (result < 0) { + printf("Cant create write opts, error %d\n", result); + return 1; + } + /* for isolinux: iso_write_opts_set_allow_full_ascii(opts, 1); */ + + result = iso_image_create_burn_source(image, opts, &burn_src); + if (result < 0) { + printf ("Cant create image, error %d\n", result); + return 1; + } + + iso_write_opts_free(opts); + + while (burn_src->read_xt(burn_src, buf, 2048) == 2048) { + fwrite(buf, 1, 2048, fd); + } + fclose(fd); + burn_src->free_data(burn_src); + free(burn_src); + + iso_image_unref(image); + iso_finish(); + return 0; +} diff --git a/demo/iso_ms.c b/demo/iso_ms.c new file mode 100644 index 0000000..563c9d4 --- /dev/null +++ b/demo/iso_ms.c @@ -0,0 +1,114 @@ +/* + * Little program to show how to create a multisession iso image. + */ + +#include "libisofs.h" +#include "libburn/libburn.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +void usage(char **argv) +{ + printf("%s LSS NWA DISC DIRECTORY OUTPUT\n", argv[0]); +} + +int main(int argc, char **argv) +{ + int result; + IsoImage *image; + IsoDataSource *src; + struct burn_source *burn_src; + unsigned char buf[2048]; + FILE *fd; + IsoWriteOpts *opts; + IsoReadOpts *ropts; + uint32_t ms_block; + + if (argc < 6) { + usage(argv); + return 1; + } + + fd = fopen(argv[5], "w"); + if (!fd) { + err(1, "error opening output file"); + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + + /* create the data source to accesss previous image */ + result = iso_data_source_new_from_file(argv[3], &src); + if (result < 0) { + printf ("Error creating data source\n"); + return 1; + } + + /* create the image context */ + result = iso_image_new("volume_id", &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + iso_tree_set_follow_symlinks(image, 0); + iso_tree_set_ignore_hidden(image, 0); + + /* import previous image */ + result = iso_read_opts_new(&ropts, 0); + if (result < 0) { + fprintf(stderr, "Error creating read options\n"); + return 1; + } + iso_read_opts_set_start_block(ropts, atoi(argv[1])); + result = iso_image_import(image, src, ropts, NULL); + iso_read_opts_free(ropts); + iso_data_source_unref(src); + if (result < 0) { + printf ("Error importing previous session %d\n", result); + return 1; + } + + /* add new dir */ + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[4]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + /* generate a multisession image with new contents */ + result = iso_write_opts_new(&opts, 1); + if (result < 0) { + printf("Cant create write opts, error %d\n", result); + return 1; + } + + /* round up to 32kb aligment = 16 block */ + ms_block = atoi(argv[2]); + iso_write_opts_set_ms_block(opts, ms_block); + iso_write_opts_set_appendable(opts, 1); + + result = iso_image_create_burn_source(image, opts, &burn_src); + if (result < 0) { + printf ("Cant create image, error %d\n", result); + return 1; + } + iso_write_opts_free(opts); + + while (burn_src->read_xt(burn_src, buf, 2048) == 2048) { + fwrite(buf, 1, 2048, fd); + } + fclose(fd); + burn_src->free_data(burn_src); + free(burn_src); + + iso_image_unref(image); + iso_finish(); + return 0; +} diff --git a/demo/iso_read.c b/demo/iso_read.c new file mode 100644 index 0000000..de30717 --- /dev/null +++ b/demo/iso_read.c @@ -0,0 +1,167 @@ +/* + * Little program to output the contents of an iso image. + */ + + +#include +#include +#include +#include + +#include "libisofs.h" + +static void +print_permissions(mode_t mode) +{ + char perm[10]; + + //TODO suid, sticky... + + perm[9] = '\0'; + perm[8] = mode & S_IXOTH ? 'x' : '-'; + perm[7] = mode & S_IWOTH ? 'w' : '-'; + perm[6] = mode & S_IROTH ? 'r' : '-'; + perm[5] = mode & S_IXGRP ? 'x' : '-'; + perm[4] = mode & S_IWGRP ? 'w' : '-'; + perm[3] = mode & S_IRGRP ? 'r' : '-'; + perm[2] = mode & S_IXUSR ? 'x' : '-'; + perm[1] = mode & S_IWUSR ? 'w' : '-'; + perm[0] = mode & S_IRUSR ? 'r' : '-'; + printf(" %s ",perm); +} + +static void +print_type(mode_t mode) +{ + switch(mode & S_IFMT) { + case S_IFSOCK: printf("[S] "); break; + case S_IFLNK: printf("[L] "); break; + case S_IFREG: printf("[R] "); break; + case S_IFBLK: printf("[B] "); break; + case S_IFDIR: printf("[D] "); break; + case S_IFIFO: printf("[F] "); break; + } +} + +static void +print_file_src(IsoFileSource *file) +{ + struct stat info; + char *name; + iso_file_source_lstat(file, &info); + print_type(info.st_mode); + print_permissions(info.st_mode); + //printf(" {%ld,%ld} ", (long)info.st_dev, (long)info.st_ino); + name = iso_file_source_get_name(file); + printf(" %s", name); + free(name); + if (S_ISLNK(info.st_mode)) { + char buf[PATH_MAX]; + iso_file_source_readlink(file, buf, PATH_MAX); + printf(" -> %s\n", buf); + } + printf("\n"); +} + +static void +print_dir(IsoFileSource *dir, int level) +{ + int ret, i; + IsoFileSource *file; + struct stat info; + char *sp = alloca(level * 2 + 1); + + for (i = 0; i < level * 2; i += 2) { + sp[i] = '|'; + sp[i+1] = ' '; + } + + sp[level * 2-1] = '-'; + sp[level * 2] = '\0'; + + ret = iso_file_source_open(dir); + if (ret < 0) { + printf ("Can't open dir %d\n", ret); + } + while ((ret = iso_file_source_readdir(dir, &file)) == 1) { + printf("%s", sp); + print_file_src(file); + ret = iso_file_source_lstat(file, &info); + if (ret < 0) { + break; + } + if (S_ISDIR(info.st_mode)) { + print_dir(file, level + 1); + } + iso_file_source_unref(file); + } + iso_file_source_close(dir); + if (ret < 0) { + printf ("Can't print dir\n"); + } +} + +int main(int argc, char **argv) +{ + int result; + IsoImageFilesystem *fs; + IsoDataSource *src; + IsoFileSource *root; + IsoReadOpts *ropts; + + if (argc != 2) { + printf ("You need to specify a valid path\n"); + return 1; + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + + result = iso_data_source_new_from_file(argv[1], &src); + if (result < 0) { + printf ("Error creating data source\n"); + return 1; + } + + result = iso_read_opts_new(&ropts, 0); + if (result < 0) { + fprintf(stderr, "Error creating read options\n"); + return 1; + } + result = iso_image_filesystem_new(src, ropts, 1, &fs); + iso_read_opts_free(ropts); + if (result < 0) { + printf ("Error creating filesystem\n"); + return 1; + } + + printf("\nVOLUME INFORMATION\n"); + printf("==================\n\n"); + + printf("Vol. id: %s\n", iso_image_fs_get_volume_id(fs)); + printf("Publisher: %s\n", iso_image_fs_get_publisher_id(fs)); + printf("Data preparer: %s\n", iso_image_fs_get_data_preparer_id(fs)); + printf("System: %s\n", iso_image_fs_get_system_id(fs)); + printf("Application: %s\n", iso_image_fs_get_application_id(fs)); + printf("Copyright: %s\n", iso_image_fs_get_copyright_file_id(fs)); + printf("Abstract: %s\n", iso_image_fs_get_abstract_file_id(fs)); + printf("Biblio: %s\n", iso_image_fs_get_biblio_file_id(fs)); + + printf("\nDIRECTORY TREE\n"); + printf("==============\n"); + + result = fs->get_root(fs, &root); + if (result < 0) { + printf ("Can't get root %d\n", result); + return 1; + } + //print_file_src(root); + print_dir(root, 0); + iso_file_source_unref(root); + + fs->close(fs); + iso_filesystem_unref((IsoFilesystem*)fs); + iso_data_source_unref(src); + iso_finish(); + return 0; +} diff --git a/demo/lsl.c b/demo/lsl.c new file mode 100644 index 0000000..17659f7 --- /dev/null +++ b/demo/lsl.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "fsource.h" + +#include +#include +#include + +/* + * Little test program to test filesystem implementations. + * + */ + +static void +print_permissions(mode_t mode) +{ + char perm[10]; + + //TODO suid, sticky... + + perm[9] = '\0'; + perm[8] = mode & S_IXOTH ? 'x' : '-'; + perm[7] = mode & S_IWOTH ? 'w' : '-'; + perm[6] = mode & S_IROTH ? 'r' : '-'; + perm[5] = mode & S_IXGRP ? 'x' : '-'; + perm[4] = mode & S_IWGRP ? 'w' : '-'; + perm[3] = mode & S_IRGRP ? 'r' : '-'; + perm[2] = mode & S_IXUSR ? 'x' : '-'; + perm[1] = mode & S_IWUSR ? 'w' : '-'; + perm[0] = mode & S_IRUSR ? 'r' : '-'; + printf(" %s ",perm); +} + +static void +print_type(mode_t mode) +{ + switch(mode & S_IFMT) { + case S_IFSOCK: printf("[S] "); break; + case S_IFLNK: printf("[L] "); break; + case S_IFREG: printf("[R] "); break; + case S_IFBLK: printf("[B] "); break; + case S_IFDIR: printf("[D] "); break; + case S_IFIFO: printf("[F] "); break; + } +} + +static void +print_file_src(IsoFileSource *file) +{ + struct stat info; + char *name; + iso_file_source_lstat(file, &info); + print_type(info.st_mode); + print_permissions(info.st_mode); + printf(" {%ld,%ld} ", (long)info.st_dev, (long)info.st_ino); + name = iso_file_source_get_name(file); + printf(" %s", name); + free(name); + if (S_ISLNK(info.st_mode)) { + char buf[PATH_MAX]; + iso_file_source_readlink(file, buf, PATH_MAX); + printf(" -> %s\n", buf); + } + printf("\n"); +} + +int main(int argc, char **argv) +{ + int res; + IsoFilesystem *fs; + IsoFileSource *dir; + IsoFileSource *file; + struct stat info; + + if (argc != 2) { + fprintf(stderr, "Usage: lsl /path/to/file\n"); + return 1; + } + + /* create filesystem object */ + res = iso_local_filesystem_new(&fs); + if (res < 0) { + fprintf(stderr, "Can't get local fs object, err = %d\n", res); + return 1; + } + + res = fs->get_by_path(fs, argv[1], &dir); + if (res < 0) { + fprintf(stderr, "Can't get file, err = %d\n", res); + return 1; + } + + res = iso_file_source_lstat(dir, &info); + if (res < 0) { + fprintf(stderr, "Can't stat file, err = %d\n", res); + return 1; + } + + if (S_ISDIR(info.st_mode)) { + res = iso_file_source_open(dir); + if (res < 0) { + fprintf(stderr, "Can't open file, err = %d\n", res); + return 1; + } + + while (iso_file_source_readdir(dir, &file) == 1) { + print_file_src(file); + iso_file_source_unref(file); + } + + res = iso_file_source_close(dir); + if (res < 0) { + fprintf(stderr, "Can't close file, err = %d\n", res); + return 1; + } + } else { + print_file_src(dir); + } + + iso_file_source_unref(dir); + iso_filesystem_unref(fs); + return 0; +} diff --git a/demo/tree.c b/demo/tree.c new file mode 100644 index 0000000..6dddbb9 --- /dev/null +++ b/demo/tree.c @@ -0,0 +1,107 @@ +/* + * Little program that import a directory and prints the resulting iso tree. + */ + +#include "libisofs.h" +#include +#include +#include +#include +#include +#include + +static void +print_permissions(mode_t mode) +{ + char perm[10]; + + //TODO suid, sticky... + + perm[9] = '\0'; + perm[8] = mode & S_IXOTH ? 'x' : '-'; + perm[7] = mode & S_IWOTH ? 'w' : '-'; + perm[6] = mode & S_IROTH ? 'r' : '-'; + perm[5] = mode & S_IXGRP ? 'x' : '-'; + perm[4] = mode & S_IWGRP ? 'w' : '-'; + perm[3] = mode & S_IRGRP ? 'r' : '-'; + perm[2] = mode & S_IXUSR ? 'x' : '-'; + perm[1] = mode & S_IWUSR ? 'w' : '-'; + perm[0] = mode & S_IRUSR ? 'r' : '-'; + printf("[%s]",perm); +} + +static void +print_dir(IsoDir *dir, int level) +{ + int i; + IsoDirIter *iter; + IsoNode *node; + char *sp = alloca(level * 2 + 1); + + for (i = 0; i < level * 2; i += 2) { + sp[i] = '|'; + sp[i+1] = ' '; + } + + sp[level * 2-1] = '-'; + sp[level * 2] = '\0'; + + iso_dir_get_children(dir, &iter); + while (iso_dir_iter_next(iter, &node) == 1) { + + if (ISO_NODE_IS_DIR(node)) { + printf("%s+[D] ", sp); + print_permissions(iso_node_get_permissions(node)); + printf(" %s\n", iso_node_get_name(node)); + print_dir(ISO_DIR(node), level+1); + } else if (ISO_NODE_IS_FILE(node)) { + printf("%s-[F] ", sp); + print_permissions(iso_node_get_permissions(node)); + printf(" %s\n", iso_node_get_name(node) ); + } else if (ISO_NODE_IS_SYMLINK(node)) { + printf("%s-[L] ", sp); + print_permissions(iso_node_get_permissions(node)); + printf(" %s -> %s \n", iso_node_get_name(node), + iso_symlink_get_dest(ISO_SYMLINK(node)) ); + } else { + printf("%s-[C] ", sp); + print_permissions(iso_node_get_permissions(node)); + printf(" %s\n", iso_node_get_name(node) ); + } + } + iso_dir_iter_free(iter); +} + +int main(int argc, char **argv) +{ + int result; + IsoImage *image; + + if (argc != 2) { + printf ("You need to specify a valid path\n"); + return 1; + } + + iso_init(); + iso_set_msgs_severities("NEVER", "ALL", ""); + + result = iso_image_new("volume_id", &image); + if (result < 0) { + printf ("Error creating image\n"); + return 1; + } + + result = iso_tree_add_dir_rec(image, iso_image_get_root(image), argv[1]); + if (result < 0) { + printf ("Error adding directory %d\n", result); + return 1; + } + + printf("================= IMAGE =================\n"); + print_dir(iso_image_get_root(image), 0); + printf("\n\n"); + + iso_image_unref(image); + iso_finish(); + return 0; +} diff --git a/doc/Tutorial b/doc/Tutorial new file mode 100755 index 0000000..6de705b --- /dev/null +++ b/doc/Tutorial @@ -0,0 +1,506 @@ +=============================================================================== + LIBISOFS DEVELOPMENT TUTORIAL +=============================================================================== + +Creation date: 2008-Jan-27 +Author: Vreixo Formoso +_______________________________________________________________________________ + +This is a little tutorial of how to use libisofs library for application +development. + +Contents: +--------- + +1. Introduction + 1.1 Library initialization + 1.2 Image context + 1.3 Error reporting +2. Creating an image + 2.1 Image tree manipulation + 2.2 Set the write options + 2.3 Obtaining a burn_source +3. Image growing and modification + 3.1 Growing vs Modification + 3.2 Image import + 3.3 Generating a new image +4. Bootable images +5. Advanced features + + +------------------------------------------------------------------------------- +1. Introduction +------------------------------------------------------------------------------- + +[TODO some lines about refcounts] + +------------------------------------------------------------------------------- +1.1. Library initialization + +Before any usage of the library, you have to call + + iso_init() + +in the same way, when you have finished using the library, you should call + + iso_finish() + +to free all resources reserved by the library. + +------------------------------------------------------------------------------- +1.2. Image context + +Libisofs is image-oriented, the core of libisofs usage is the IsoImage object. +Thus, the first you need to do is to get your own IsoImage object: + + IsoImage *my_image; + iso_image_new("NEW DISC", &my_image); + +An IsoImage is a context for image creation. It holds the files that will be +added to image, other related information and several options to customize +the behavior of libisofs when working with such Image. i.e., an IsoImage is +a context for libisofs operations. As such, you can work with several image +contexts at a time. + +------------------------------------------------------------------------------- +1.3. Error reporting + +In libisofs error reporting is done in two ways: with the return value of +the functions and with the message queue. + +Error codes are negative numbers, defined in "libisofs.h" header. An +error code is associated with a given severity, either "DEBUG", "UPDATE", +"NOTE", "HINT", "WARNING", "SORRY", "FAILURE" and "FATAL". For the meaning +of each severity take a look at private header "libiso_msgs.h". Errors +reported by function return value are always "FAILURE" or "FATAL". Other kind +of errors are only reported with the message queue. You can get the severity +of any error message with iso_error_get_severity() function. + +First of all, most libisofs functions return an integer. If such integer is +a negative number, it means the function has returned an error. The error code +and its severity is encoded in the return value (take a look at error codes in +libisofs.h header). + +Additionally, libisofs reports most of its errors in a message queue. Error +messages on that queue can be printed directly to stderr or programmatically +retrieved. First of all, you should set the severity threshold over which an +error is printed or enqueued, with function: + + iso_set_msgs_severities() + +Errors enqueued can be retrieved with function: + + iso_obtain_msgs() + +Together with the code error, a text message and its severity, this function +also returns the image id. This is an identifier that uniquely identifies a +given image context. You can get the identifier of each IsoImage with the + + iso_image_get_msg_id() + +and that way distinguish what image has issued the message. + + +------------------------------------------------------------------------------- +2. Creating an Image +------------------------------------------------------------------------------- + +An image is built from a set of files that you want to store together in an +ISO-9660 volume. We call the "iso tree" to the file hierarchy that will be +written to image. The image context, IsoImage, holds that tree, together with +configuration options and other properties of the image, that provide info +about the volume (such as the identifier, author, etc...). + +All configuration options and volume properties are set by its corresponding +setters (iso_image_set_volset_id(), iso_image_set_publisher_id()...) + +To create an image, you have to follow the following steps: + +* Obtain the image context. + See "1.2 Image context" for details of how to obtain the IsoImage. +* Set the desired properties +* Prepare the iso tree with the files you want to add to image. + See "2.1 Image tree manipulation" for details +* Select the options for image generation. + See "2.2 Set the write options" +* Get the burn_source used to actually write the image. + + +------------------------------------------------------------------------------- +2.1 Image tree manipulation + +libisofs maintains in memory a file tree (usually called the iso tree), that +represents the files and directories that will be written later to image. You +are allowed to make whatever changes you want to that tree, just like you do +to any "real" filesystem, before actually write it to image. + +Unlike other ISO-9660 mastering tools, you have full control over the file +hierarchy that will be written to image, via the libisofs API. You can add +new files, create any file in image, change its name, attributes, etc The iso +tree behaves just like any other POSIX filesystem. + +The root of the iso tree is created automatically when the IsoImage is +allocated, and you can't replace it. To get a reference to it you can use the +function: + + iso_image_get_root() + +* Iso tree objects + +Each file in the image or iso tree is represented by an IsoNode instance. In +the same way a POSIX filesystem has several file types (regular files, +directories, symlinks...), the IsoNode has several subtypes: + + IsoNode + | + --------------------------------- + | | | | + IsoDir IsoFile IsoSymlink IsoSpecial + +where + + - IsoDir represents a directory + - IsoFile represents a regular file + - IsoSymlink represents a symbolic linke + - IsoSpecial represents any other POSIX file, i.e. block and character + devices, FIFOs, sockets. + +You can obtain the concrete type of an IsoNode with the iso_node_get_type() +function. + +Many libisofs functions take or return an IsoNode. Many others, however, +require an specific type. You can safety cast any subtype to an IsoNode +object. In the same way, after ensuring you are dealing with the correct +subtype, you can downcast a given IsoNode to the specific subtype. + + IsoDir *dir; + IsoNode *node; + + node = (IsoNode*) dir; + + if (iso_node_get_type(node) == LIBISO_DIR) { + dir = (IsoDir*) node; + ... + } + +or with the provided macros: + + IsoDir *dir; + IsoNode *node; + + node = ISO_NODE(dir); + + if (ISO_NODE_IS_DIR(node)) { + dir = ISO_DIR(node); + ... + } + +* Adding files to the image + +Files can be added to the image or iso tree either as new files or as files +from the filesystem. + +In the first case, files are created directly on the image. They do not +correspond to any file in the filesystem. Provided functions are: + + - iso_tree_add_new_dir() + - iso_tree_add_new_symlink() + - iso_tree_add_new_special() + +On the other side, you can add local files to the image, either with the + + iso_tree_add_node() + +or with + + iso_tree_add_dir_rec(). + +The first is intended to add a single file, while the last can be used to add, +recursively, a full directory (see below for details). + +It is important to note that libisofs doesn't store any kind of link between +the IsoNode and the filesystem file it was created from. The above functions +just initialize a newly created IsoNode with the attributes of a given file in +the filesystem. After that, you can move the original file, change its +attributes or even delete it. The IsoNode in the image tree remains with the +original attributes. One exception to this rule are the contents of a regular +file. Libisofs does not make any copy of those contents until they're actually +written to image. Thus, you shouldn't modify, move or delete regular files +after adding them to the IsoImage. + + +* Recursive directory addition. + +One common use case is to add a local directory to the image. While this can +be done with iso_tree_add_node(), handling the addition of directory children +in the application, libisofs provides a function suitable for this case: + + iso_tree_add_dir_rec() + +that takes care of adding all files inside a directory, recursing on directory +children. By default, this function adds all children. However, it is usual +that you don't want really this. For example, you may want to exclude some +kind of files (backup files, application sockets,...). Libisofs provides +several functions to customize the behavior of that function: + + - iso_tree_set_follow_symlinks() + - iso_tree_set_ignore_hidden() + - iso_tree_set_ignore_special() + - iso_tree_add_exclude() + +* Operations on iso tree + +[TODO briefly explain how to add node, change attributes, ...] + +* Replace mode + +[TODO] + +------------------------------------------------------------------------------- +2.2 Set the write options + +Once you have prepared the iso tree, it is time to select the options for the +image writing. + +These options affect the characteristics of the filesystem to create in the +image, but also can control how libisofs generates the image. + +First of all you have to get an instance of IsoWriteOpts, with the function + + iso_write_opts_new() + +The several options available can be classified in: + +- Extensions to add to the ISO-9660 image: + + iso_write_opts_set_rockridge() + iso_write_opts_set_joliet() + iso_write_opts_set_iso1999() + +RockRidge is highly recommended, in fact you should use it in all image. Joliet +is needed if you want to use your images in Windows system. Nowadays, +ISO-9660:1999 is no much useful, so in most cases you don't want such +extension. + +- ISO-9660 options: + + iso_write_opts_set_iso_level() + iso_write_opts_set_omit_version_numbers() + iso_write_opts_set_allow_deep_paths() + iso_write_opts_set_allow_longer_paths() + iso_write_opts_set_max_37_char_filenames() + iso_write_opts_set_no_force_dots() + iso_write_opts_set_allow_lowercase() + iso_write_opts_set_allow_full_ascii() + +These control the options for the ISO-9660 filesystem. In most cases you won't +care about them, as it is the RockRidge or Joliet extensions what determine the +properties of the files once the image is mounted. + +- File attributes options + + iso_write_opts_set_replace_mode() + iso_write_opts_set_default_dir_mode() + iso_write_opts_set_default_file_mode() + iso_write_opts_set_default_uid() + iso_write_opts_set_default_gid() + iso_write_opts_set_replace_timestamps() + iso_write_opts_set_default_timestamp() + iso_write_opts_set_always_gmt() + +They allow to set default attributes for files in image, despite of the real +attributes of the file on the local filesystem. + +------------------------------------------------------------------------------- +2.3 Obtaining a burn_source + +Finally, you get the burn_source used to write the image with the function: + + iso_image_create_burn_source() + +The returned burn_source is suitable for using with libburn, to directly burn +the image to a disc. Alternatively, you can use burn_source read() to get +the image contents (for example, to write them to a file, pipe...). + +Before creating the burn_source, libisofs computes the size of the image, so +the get_size() function of the burn_source always returns the final image +size. It also starts a writing thread. All the operations needed to generate +the image are done by this thread, including read the original files contents. +The image is writing to a FIFO buffer, from which the burn_source will read. +The size of the buffer can be set in advanced with a property of the +IsoWriteOpts struct: + + iso_write_opts_set_fifo_size() + +You can get the state of the buffer in any moment, with the function: + + iso_ring_buffer_get_status() + +You can also cancel the writer thread at any time, with the cancel() function +of the burn_source. + + +------------------------------------------------------------------------------- +3. Image growing and modification +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +3.1 Growing vs Modification + +Libisofs is not restricted only to create new images. It can also be used to +modify existing images. It supports two kind of image modifications, that we +have called image growing and image modification: + +Image modification consists in generating a new image, based on the contents +of an existing image. In this mode, libisofs takes an image, the users modifies +its contents (adding new files, removing files, changing their names...), and +finally libisofs generates a completely new image. + +On the other side, image growing is similar, with the difference that the new +image is dependent on the other, i.e., it refers to files of the other image. +Thus, it can't be mounted without the old image. The purpose of this kind of +images is to increment or add files to a multisession disc. The new image only +contains the new files. Old files are just references to the old image blocks. + +The advantage of the growing approach is that the generated image is smaller, +as only the new files are written. This mode is suitable when you just want to +add some files to a very big image, or when dealing with write-once media, such +as CD-R. Both the time and space needed for the modification is much less than +with normal image modify. + +The main problem of growing is that the new image needs to be recorded together +with the old image, in order to be mountable. The total size of the image +(old + new) is bigger (even much bigger) than a completely new image. So, if +you plan to distribute an image on Internet, or burn it to a disc, generate a +completely new image is usually a better alternative. + +To be able to mount a grown image, the OS needs to now you have appended new +data to the original image. In multisession media (such as CD-R), the new data +is appended as a new session, so the OS can identify this and mount the image +propertly. However, when dealing with non-multisession media (such as DVD+RW) +or plain .iso files, the new data is just appended at the end of the old image, +and the OS has no way to know that the appended data is in fact a "new +session". The method introduced by Andy Polyakov in growisofs can be used in +those cases. It consists in overwrite the volume descriptors of the old image +with a new ones that refer to the newly appended contents. + +------------------------------------------------------------------------------- +3.2 Image import + +The first thing you need to do in order to modify or grow an image is to import +it, with the function: + + iso_image_import() + +It takes several arguments. + +First, the image context, an IsoImage previously obtained with iso_image_new(). +In most cases you will want to use an empty image. However, if you have already +added files to the image, they will be removed and replaced with the contents +of the image being imported. + +The second parameter is an IsoDataSource instance. It abstracts the original +image, and it is used by libisofs to access its contents. You are free to +implement your own data source to access image contents. However, libisofs has +a simple implementation suitable for reading images on the local filesystem, +that can be used for import both .iso files and inserted media, via the block +device and POSIX functions. You can get it with + + iso_data_source_new_from_file() + +The third parameter of iso_image_import() is a pointer to an IsoReadOpts +struct. It holds the options for image reading. You get it with: + + iso_read_opts_new() + +and after calling iso_image_import() you should free it with + + iso_read_opts_free() + +Some options are related to select what extensions to read. Default options +are suitable for most users. + + iso_read_opts_set_no_rockridge() + iso_read_opts_set_no_joliet() + iso_read_opts_set_no_iso1999() + iso_read_opts_set_preferjoliet() + +If RockRidge extensions are not present, many files attributes can't be +obtained. In those cases libisofs uses default values. You have options to +configure what default values to use. + + iso_read_opts_set_default_uid() + iso_read_opts_set_default_gid() + iso_read_opts_set_default_permissions() + +If the original image has been created in another system with a different +charset, you may want to use: + + iso_read_opts_set_input_charset() + +to specify the encoding of the file names on image. + +Finally, to import multisession images, you should tell libisofs that it must +read the last session. For that, you must set the block where the last session +starts: + + iso_read_opts_set_start_block() + +The last parameter for iso_image_import(), optional, is a pointer that will +be filled with a library-allocated IsoReadImageFeatures, that lets you access +some information about the image: size, extensions used,... + +[TODO: explain that iso_image_import uses dir rec options] + +------------------------------------------------------------------------------- +3.3 Generating a new image + +After importing the image, the old image tree gets loaded. You are free to +make any changes to it: add new files, remove files, change names or +attributes... Refer to "2.1 Image tree manipulation" for details. + +When it is ready, you can now create the new image. The process is the same as +explained in "2.2 Set the write options" and "2.3 Obtaining a burn_source". +However, there are some write options that should be taken into account. + +First of all, you must select whether you want to grow or modify the image +(read "3.1 Growing vs Modification" for details). You must call + + iso_write_opts_set_appendable() + +An appendable image leads to image growing, and a non-appendable image leads +to a completelly new image (modification). An appendable image will be appended +after the old image (in a new session, for example). Thus, in those cases, the +first block of the image is not 0. You should set the correct lba of the first +block with: + + iso_write_opts_set_ms_block() + +That is usually the "Next Writable Address" on a multisession media, and a +value slightly greater than the old image size on .iso files or DVD+RW media. +You can obtain the old image size with the iso_read_image_features_get_size() +function. + +In this last case (i.e., on a non multisession media), you will need to +overwrite the volume descriptors of the old image with the new ones. To do +this you need: + +- Allocate a buffer of at least 64 KiBs. +- Initialize it with the first 64 KiBs of the original image. +- Pass the buffer to libisofs with the iso_write_opts_set_overwrite_buf() + option. +- After appending the new image, you have to overwrite the first 64 KiBs of + the original image with the new content of the buffer. + + +------------------------------------------------------------------------------- +4. Bootable images +------------------------------------------------------------------------------- + + + +------------------------------------------------------------------------------- +5. Advanced features +------------------------------------------------------------------------------- + + diff --git a/doc/Wiki b/doc/Wiki new file mode 100755 index 0000000..c9f6f8e --- /dev/null +++ b/doc/Wiki @@ -0,0 +1,32 @@ += libisofs = + +libisofs is a library to create an ISO-9660 filesystem, and supports extensions like RockRidge and Joliet. It is also a full featured ISO-9660 editor, allowing you to modify an ISO image or multisession disc, including file addition/removal, change of file names and attributes, and similar. + +The old libisofs.so.5 has been declarated deprecated and frozen, leaving it unmaintained. A full refactoring of the design has been done during the last months, and the next generation libisofs.so.6 of the library will be released in the following days. + +== Source Code == + +The code is maintained in a [http://bazaar-vcs.org/ Bazaar] repository at Launchpad (https://launchpad.net/libisofs/). You can download it with: + +{{{ +$ bzr branch lp:libisofs +}}} + + +To report any bug or suggest enchantments, [http://libburnia-project.org/register register] yourself and submit a new ticket. Bug and enchantments reports for nglibisofs can be found at http://libburnia-project.org/report/9. + +== Usage tutorial == + +Coming soon... For now check [http://codebrowse.launchpad.net/~mario-danic/libisofs/mainline/annotate/metalpain2002%40yahoo.es-20080201154704-xqyzc57vki97iv3y?file_id=tutorial-20080127170757-cwmomu7oz9eh7fcz-1 "doc/Tutorial"] in the source tree. + + +=== Applications === + +Comming soon: + +[http://libburnia-project.org/browser/libisoburn/trunk libisoburn]: + emulates ISO 9660 multi-session on overwriteable media, coordinates libisofs and libburn. + +[http://libburnia-project.org/browser/libisoburn/trunk/xorriso/xorriso_eng.html?format=raw xorriso]: + creates, loads, manipulates and writes ISO 9660 filesystem images with Rock Ridge extensions. + diff --git a/doc/devel/1. Overview b/doc/devel/1. Overview new file mode 100644 index 0000000..e69de29 diff --git a/doc/devel/2. Features b/doc/devel/2. Features new file mode 100644 index 0000000..89501e3 --- /dev/null +++ b/doc/devel/2. Features @@ -0,0 +1,193 @@ +FEATURES +======== + +Contents: + +2.0 Operations on image tree +2.1 ECMA-119 +2.2 Rock Ridge +2.3 Joliet +2.4 El-Torito +2.5 UDF +2.6 HFS/HFS+ +2.7 Others + + +=============================================================================== + +2.0 Operations on image tree +----------------------------- + +Basic: + - We HAVE TO Support addition of directories + - From filesystem + - From filesystem recursively + - New on image + - We HAVE TO support addition of files + - From local filesystem + - From previous/ms images + - We HAVE TO support addition of other POSIX file types + - From filesystem + - New on image + - Types: symlinks, block/char devices, fifos, sockets... + - We HAVE TO support modification of file names on image + - We HAVE TO support modification of POSIX attributes: + - Uid/Gid + - Permissions (we DON'T HAVE TO support full mode modification, + as we don't want a dir to be changed to a reg file!!) + - Timestamps + - We HAVE TO support deletion of nodes. + - We HAVE TO support iteration of directory tree. + - We WANT TO support direct getting (without iteration) of the number of + nodes in a directory. + +Extras: + - We WANT TO support on-the-fly modification of file contents, to + allow things like compression and encryption. + +Notes: many operations will need RR extensions to be actually reflected on +the image. + +=============================================================================== + +2.1 ECMA-119 +------------ + +Support for ECMA-119 (ISO-9660) specification. + +2.1.1 Creation +-------------- + +We HAVE TO support creation of new images. + + General: + - We HAVE TO support single volume images + - We DON'T NEED TO support multiple volume images. + It seems multiple volume images are not used. + - We HAVE TO support bootable volumes (see 2.4 in this doc) + Conformance: + - We HAVE TO support Level 1 restrictions (ECMA-119 10.1) + - We HAVE TO support Level 2 restrictions (ECMA-119 10.2) + Single Section files have a theoric size limit of 4GB (data length + is a 32-bit number, see ECMA-119 9.1.4). However I think I have + read that only files up to 2GB are supported. + - We MAY support full Level 3 (ECMA-119 10.3) + Multiple file sections are useful to support files higher than + level 2 limit. However, it seems it's a feature not supported in + most O.S. nowadays, so it's not very useful. + - We DON'T WANT TO support files recording in interleaved mode + (ECMA-119 6.4.3) + It seems a feature that it's not used. + - We DON'T WANT TO support associated files (ECMA-119 6.5.4) + What is that? Is it used? + - We DON'T WANT TO support Volume Partitions (ECMA-119 8.6) + What is that? Is it used? + - We DON'T WANT TO support extended attribute records (ECMA-119 9.5) + It seems an unused feature. RR is a better alternative. + - We DON'T NEED TO support file versions other than 1. + Restrictions: + - We HAVE TO provide a way to relax iso restrictions related to + filenames, allowing: + - Higher filename length, up to 37 chars (ECMA-119 7.5.1/7.6.3) + - Omit version number (ECMA-119 7.5.1) + - Directory hierarchy deeper than 8 levels / 255 path length + (ECMA-119 6.8.2.1) + - More characters in filenames, not only d-characters + + +2.2.2 Reading +------------- + + General + - We HAVE TO support the reading of iso images + - We DON'T NEED TO support reading of features we don't support in + creation (see 2.2.1 above) + - We HAVE TO support reading arbitray file contents inside image + +2.2.3 Modification/growing +-------------------------- + + General + - We HAVE TO support creation of new images from the contents of + an existing image + - We HAVE TO support multissession images + - We HAVE TO support growing of images + +=============================================================================== + +2.2 Rock Ridge +-------------- + +- We HAVE TO support ALL Rock Ridge features, with these exceptions: + - We DON'T NEED TO support SF System User Entry (RRIP 4.1.7), used to + encode sparse files. + - We MIGHT support BACKUP timestamp (RRIP 4.1.6) +- We HAVE TO support any charset in RR filenames, and not only POSIX portable + filename character set (RRIP 3.4.1). Namely, UTF-8 SHOULD BE the default for + RR filenames. +- We MIGHT support Linux specific ZF extension, to allow transparent + compression. + +=============================================================================== + +2.3 Joliet +---------- + +- We HAVE TO support ALL Joliet features, with these exceptions: + - We DON'T KNOW what to do with UCS-2 conformance level 1 and 2 (scape + sequences '%\@' and '%\C'). What's this??????? + - We DON'T KNOW what to do with CD-XA extensions. + + +=============================================================================== + +2.4 El-Torito +------------- + +- We HAVE TO El-Torito standard with a single boot image. +- We MAY support multiple boot images and boot entry selection. + - El Torito standard is not very clear about how to do that. +- We HAVE TO support both emulation and not emulation mode. +- We HAVE TO support 80x86 platform. We MAY support Power PC and Mac platforms. +- We HAVE TO provide improved support for isolinux boot images, namely patching + features. +- We HAVE TO support El-Torito in ms images. + + +=============================================================================== + +2.5 UDF +------- + + + +=============================================================================== + +2.6 HFS/HFS+ +------------ + + + + + +=============================================================================== + +2.7 Others +---------- + +- We HAVE TO support sorting of file contents on image +- We HAVE TO support inode caching to prevent the same file to be written + several times into the image +- We DON'T NEED TO support TRANS.TBL files +- We DON'T NEED TO support padding of images + - Padding should be part of the burning process + + + + + + + + + + diff --git a/doc/devel/3. Use Cases b/doc/devel/3. Use Cases new file mode 100644 index 0000000..e6e17f0 --- /dev/null +++ b/doc/devel/3. Use Cases @@ -0,0 +1,193 @@ +USE CASES FOR NG LIBISOFS +========================= + +3.1 General Operations +====================== + +3.1.1 Creation of a new image +----------------------------- + +Desc: Creation of a new ISO image from files on the local filesystem +Phases: + - User creates a new image context + - User get the root (empty) of the image + - User adds files to the image root (see 3.2.) + - User sets the options for the the new image (extension to use...) + - User gets a burn_source to write the image. + - The burn_source can be used by libburn to write the new image. + +3.1.2 Image growing (multisession) +---------------------------------- + +Desc: An existing image can be grown with new files. New content is added + incrementally. Suitable for multisession. Growing support for + overwritteable media. +Phases: + - Uses reads an existing image to get the image context. + - User get the root of the image + - User modifies the image tree (see 3.2.) + - User sets the options for the the new image (extension to use...) + A required option will be the nwa for the image. + Optionally it can pass a pointer to a 64K buffer, that will be filled + with suitable volume descriptors to be used with overwrieable media. + - User gets a burn_source to write the image. + - The burn_source can be used by libburn to write an image that should be + appended to the previous image. + + +3.1.3 Image modification +------------------------ + +Desc: Creation of a new image from the contents of a previous image. +Phases: + - Uses reads an existing image to get the image context. + - User get the root of the image + - User modifies the image tree (see 3.2.) + - User sets the options for the the new image (extension to use...) + - User gets a burn_source to write the image. + - The burn_source can be used by libburn to write the image to another + device or file. + +3.2 Tree operations +=================== + +3.2.1 Addition of contents +-------------------------- + + All addition operations take a parent dir. The functions check that the + node name is unique among all children. Image context options determine + what to do if a file with such name already exist. + + 3.2.1.1 Directories + -------------------- + - Creation of new directories in image, given a parent dir. and a name. + Attributes are initialized to default values + - Addition of a dir from the filesystem, given a parent. + Dir contents are not added. Name and other attributes taken from + the dir on filesystem + - Recursive addition of a dir, given a parent. Directory contents are + recursivelly added to image. + + 3.2.1.2 Regular files + ---------------------- + - Addition of a local filesystem file. Name, attributes and contents to + be written taken from the filesystem file. + - Addition of files from the previous image. Files are automatically + added to the tree when the image is read. Name and attrbs taken from + previous image. When the image has no RR extensions, unavailable atts + are initialized to default values. The contents are only written to + img if we choose image modification. + - Addition of filtered files. Name and atts taken from the local + filesystem. A filter (see 3.3) is applied to the file contents before + written to image. + - Addition of splitted files. Like local filesystem files, but the file + is splitted in several files on a given size. Suitable for big (> 2GB) + files. Name of the splitted files automatically generated. + + 3.2.1.3 Symbolic links + ---------------------- + + Simbolic links are only written to image if RR extensions are enabled. + + - Addition of a simbolic link from local filesystem. Name, atts and + dest of a path are taken from the simbolic link. + - Addition of new link on image to a path. Name and dest specified, + the destination is specified as a path. Attributes initialized to + default values. + - Addition of new link on image to another node. Name and dest + specified, the dest is set to a node previously added to image. + When written, the destination path is computed as the relative path + from the link to the destination node. Attributes initialized to + default values. + + 3.2.1.4 Special files (block devices, fifos...) + ----------------------------------------------- + + Special files are only written to image if RR extensions are enabled. + + - Addition of special files from filesystem. + - Creation of new special files on image. + + +3.2.2 Modification of contents +------------------------------ + + 3.2.2.1 Deletion of nodes + ------------------------- + + - Any node can be deleted. When a dir is remove, all its contents + are also removed. + + 3.2.2.2 Move + ------------ + + - Any node can be move to another dir.. + + 3.2.2.3 Rename + -------------- + + - You can change the name of any node + + 3.2.2.4 Change of POSIX attributes + ---------------------------------- + + - Following POSIX atts can be changed: owner (uid/gid), permissions, + timestamps. + +3.2.3 Bootable information +-------------------------- + + - Addition of a boot image to a volume. + - In most cases, the catalog and the boot image itself is added to the + iso tree. + - Alternatively, user can select to add a hidden images, i.e., images + that don't appear in the iso tree. + - Modification of boot image attributes: + - bootable flag + - load segment + - load size + - Automatic patching of isolinux images. User needs to set whether to apply + this. + - Reading of El-Torito info from multisession images. Modification of its + attributes. + - Removing of El-Torito images + + +3.2.4 Other operations +---------------------- + + 3.2.4.1 Set file sort weight + ----------------------------- + + - Any file can have a sort weight, that will determine the order in + which the files are written to image + + 3.2.4.2 Hidding of nodes + ------------------------ + + - Files can be hidden in the RR or Joliet tree + + +3.3 Filters +=========== + + [TODO] + + Support for: + - compression filter + - encryption filter + - external process filter + + + + + + + + + + + + + + diff --git a/doc/devel/4. Design b/doc/devel/4. Design new file mode 100644 index 0000000..e69de29 diff --git a/doc/devel/5. Implementation b/doc/devel/5. Implementation new file mode 100644 index 0000000..e69de29 diff --git a/doc/devel/README b/doc/devel/README new file mode 100644 index 0000000..75face4 --- /dev/null +++ b/doc/devel/README @@ -0,0 +1,7 @@ +Index +===== + +1. Overview +2. Features +3. Design +4. Implementation diff --git a/doc/devel/UML/BuilderSec.png b/doc/devel/UML/BuilderSec.png new file mode 100644 index 0000000000000000000000000000000000000000..a89203ee903b9abd36879bfe0d099c7f7d5f48eb GIT binary patch literal 25202 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 Wyv4nr=4OT5*VQsOUwGE>*8c&|P(C&Q literal 0 HcmV?d00001 diff --git a/doc/devel/UML/BuilderSec.violet b/doc/devel/UML/BuilderSec.violet new file mode 100644 index 0000000..c7e7fbf --- /dev/null +++ b/doc/devel/UML/BuilderSec.violet @@ -0,0 +1,821 @@ + + + + + + + + fs:Filesystem + + + + + + 160.0 + 73.0 + + + + + + + + + + file:FileSource + + + + + + + + + + + 192.0 + 209.0 + + + + + + + + 274.0 + 202.0 + + + + + + + + User + + + + + + 34.86475730998367 + 0.0 + + + + + + + + + + + + + b:TNBuilder + + + + + + + + + + + + + + + + + + + + + + + ftn:FileTN + + + + + + + + + + + + + + + + fs:FileStream + + + + + + + + + + + + + + + + + + + + + d:DirTreeNode + + + + + + + + + + + + + + + + + + + + 66.86475730998367 + 80.0 + + + + + + + + 539.756828460011 + 126.0 + + + + + + + + 651.0 + 0.0 + + + + + + + + 683.0 + 305.0 + + + + + + + + 571.756828460011 + 328.0 + + + + + + + + 306.0 + 351.0 + + + + + + + + 331.97135964975513 + 374.0 + + + + + + + + 363.97135964975513 + 457.0 + + + + + + + + 418.8259109283281 + 480.0 + + + + + + + + 363.97135964975513 + 563.0 + + + + + + + + 1. User wants to add a file to a dir in the iso node + + + + + + 143.89406091532933 + 16.868736840587744 + + + + + + + + 2. It creates the source filesystem and the + custom builder + + + + + + 317.51829970572646 + 74.92004824517142 + + + + + + + + 570.819415201306 + 142.7048538003265 + 0.0 + 0.0 + + + + + + + + + 570.819415201306 + 142.7048538003265 + + + + + + + + 218.81410916050066 + 114.16388304026121 + 0.0 + 0.0 + + + + + + + + + 218.81410916050066 + 114.16388304026121 + + + + + + + + 3. It gets the file from the filesystem +and add it to parent dir + + + + + + 379.1320632384976 + 217.4323774110454 + + + + + + + + 327.03195662574825 + 218.46075295682857 + 0.0 + 0.0 + + + + + + + + + 327.03195662574825 + 218.46075295682857 + + + + + + + + 4. The dir delegates in the builder. +5. The builder stat's the source file. In + this example it's a reg. file + + + + + + 767.038589176755 + 206.92203801047344 + + + + + + + + 694.4969551615891 + 312.7614712457156 + 0.0 + 0.0 + + + + + + + + + 694.4969551615891 + 312.7614712457156 + + + + + + + + 314.9148790283507 + 359.23720542189034 + 0.0 + 0.0 + + + + + + + + + 314.9148790283507 + 359.23720542189034 + + + + + + + + 6. The conversion is not needed, so +the builder just creates a FileTreeNode + + + + + + 762.2817607167442 + 335.3564064307673 + + + + + + + + 522.2869299335649 + 399.9594286575042 + 0.0 + 0.0 + + + + + + + + + 522.2869299335649 + 399.9594286575042 + + + + + + + + 7. Sets the attributes from source + + + + + + 774.1738318667714 + 413.8440760209469 + + + + + + + + 8 ...and a FileStream to read contents + from the FileSource + + + + + + 762.2817607167442 + 478.0612602310938 + + + + + + + + 534.9181953038541 + 453.1845675071054 + 0.0 + 0.0 + + + + + + + + + 534.9181953038541 + 453.1845675071054 + + + + + + + + 482.368075796364 + 524.8261757327898 + 0.0 + 0.0 + + + + + + + + + 482.368075796364 + 524.8261757327898 + + + + + + + + 9. Finally, the FileTreeNode is added to + the parent dir, and returned to the user + + + + + + 757.5249322567332 + 556.5489298212734 + + + + + + + + 689.7401267015781 + 614.8200784564067 + 0.0 + 0.0 + + + + + + + + + 689.7401267015781 + 614.8200784564067 + + + + + + + + 363.97135964975513 + 656.0 + + + + + + + + 10. The user can change any attribute + on the FileTreeNode + + + + + + 735.3910524340093 + 659.0235200658623 + + + + + + + + 373.3523804664971 + 666.0945878777277 + 0.0 + 0.0 + + + + + + + + + 373.3523804664971 + 666.0945878777277 + + + + + + + «create» + + + + + + + + + «create» + + + + + + + + + + + + file + + + + + + + + + add_file(file,b) + + + + + + + + + create_file(file) + + + + + + + + + lstat() + + + + + + + + + S_IFREG + + + + + + + + + «create» + + + + + + + + + set attributes + + + + + + + + + + + + + + + + + ftn + + + + + + + + + «create» + + + + + + + + + set stream (fs) + + + + + + + + + + + + + + + + + ftn + + + + + + + + + «create» + + + + + + + + + get(path) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + set_permission() + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/builder.violet b/doc/devel/UML/builder.violet new file mode 100644 index 0000000..3dba303 --- /dev/null +++ b/doc/devel/UML/builder.violet @@ -0,0 +1,884 @@ + + + + + + + + get_root() +get_from_path(char *) + + + + + «interface» +Filesystem + + + + + + 159.04005306497305 + 489.4913761627291 + + + + + + + + MountedFilesytem + + + + + + 56.38849919058573 + 630.9884605487425 + + + + + + + + IsoImage + + + + + + 258.8562868808994 + 766.3563832139356 + + + + + + + + lstat() +read() +close() +open() +readdir() + + + + + «interface» +SourceFile + + + + + + 481.55979910778467 + 464.84194569982117 + + + + + + + + TarFile + + + + + + 176.58261638364775 + 701.0593878047844 + + + + + + + + read() +size() +open() +close() + + + + + «interface» +Stream + + + + + + 779.894860994415 + 340.36024540554786 + + + + + + + + FdStream + + + + + + 907.9433913981195 + 505.6600343909271 + + + + + + + + FileStream + + + + + + 646.2536512193697 + 514.5953286599063 + + + + + + + + TransformStream + + + + + + 774.6238447615127 + 513.9203093177954 + + + + + + + + create_file() +create_symlink() +create_dir() + + + + + «interface» +TreeNodeBuilder + + + + + + 469.51180397870456 + 119.92057094444797 + + + + + + + + TreeNode + + + + + + 777.5164467644091 + 137.7586776694888 + + + + + + + + File + + + + + + 776.3272396494064 + 235.11044131455145 + + + + + + + + Dir + + + + + + 899.7797731623193 + 242.40651557378732 + + + + + + + + Symlink + + + + + + 658.5957352641371 + 237.4888555445569 + + + + + + + + «interface» +FileBuilder + + + + + + 68.74900622278733 + 236.29964842955417 + + + + + + + + «interface» +DirBuilder + + + + + + 190.04813195306485 + 236.2996484295542 + + + + + + + + «interface» +SymlinkBuilder + + + + + + 304.21201499332614 + 236.29964842955417 + + + + + + + + POSIX inspired interface to files on different filesystems. +open/close act as a opendir/closedir if the file is a dir, +I think we don't need different function to open a dir. + + + + + + 154.8805850420814 + 333.9382491299707 + + + + + + + + "Sources" for file contents + + + + + + 587.0127806828101 + 358.755499461917 + + + + + + + + CutOutStream + + + + + + 845.6997102991108 + 605.2834046956852 + + + + + + + + FilterStream + + + + + + 721.2489168102784 + 605.2834046956852 + + + + + + + + «interface» +Filter + + + + + + 715.5920625607861 + 705.6925676241749 + + + + + + + + Used for arbitray streams, not related to +filesystem high-level idea. Also used for +files like fifos, that can't be added directly as +regulat files via de Builder, because its size is +unknown. The need to be added as new_files +on image + + + + + + 906.5108934811542 + 328.0975464705584 + + + + + + + + Create the user-specified TreeNode from the +user-specified source. If the source type differs the +TreeNode type the use wants to create, it makes +the needed conversion, if possible. Each builder +implementation can do different conversions. + + + + + + 654.7808793787427 + 20.610173055266337 + + + + + + + + Together with the SourceFile encapsulates the +access to a given filesystem and abstracts it to +a POSIX interface. + + + + + + 20.610173055266422 + 403.050865276332 + + + + + + + + The TreeNodeBuilder can be created with +the combination of different interfaces for +each factory method + + + + + + 149.90663761154804 + 57.982756057296896 + + + + + + + + MountedSrc + + + + + + 373.3523804664971 + 634.9818895055197 + + + + + + + + TarSrc + + + + + + 479.4183976444791 + 695.7930726875627 + + + + + + + + IsoSrc + + + + + + 578.4133470105959 + 773.574818618083 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{create}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/builder.violet.png b/doc/devel/UML/builder.violet.png new file mode 100644 index 0000000000000000000000000000000000000000..6884fe9ab24b1af2531453fae6ce2c58e52402a5 GIT binary patch literal 34098 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
oOVz;_72ai1A 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 + + + + + + + + + «interface» +BurnSource + + + + + + libburn + + + + + 600.0 + 50.0 + + + + + + + + 612.4370214406964 + 81.3237865499184 + + + + + + + + Ecma119Source + + + + + + 604.1172144213822 + 242.59825146055505 + + + + + + + + WriterState + + + + + + 861.1034292438676 + 244.31119796826698 + + + + + + + + FilesWriterSt + + + + + + 984.2531292100068 + 359.95094883087904 + + + + + + + + VolDescWriterSt + + + + + + 717.2457054234224 + 357.4185959653686 + + + + + + + + DirInfoWriterSt + + + + + + 854.6043620021998 + 355.85097462036043 + + + + + + + + Ecma119Image + + + + + + 392.3294860655768 + 240.39714472372754 + + + + + + + + The context data for image burn sources, contains +references to the tree, creation options... + + + + + + 261.45257180386454 + 85.80450046553075 + + + + + + + + Ecma119Node + + + + + + 291.8219414851778 + 612.806815288254 + + + + + + + + init() +write_voldesc() +write_dir_info() + + + + + «interface» +ImageWriter + + + + + + 401.9520048709197 + 344.8700633507891 + + + + + + + + JolietNode + + + + + + 409.0872475609359 + 614.8200784564067 + + + + + + + + FileRegistry + + + + + + 718.2810974616434 + 459.0339463910502 + + + + + + + + Ecma119Writer + + + + + + 273.51763645062584 + 489.95333138112096 + + + + + + + + JolietWriter + + + + + + 404.3304191009253 + 485.1965029211101 + + + + + + + + ElToritoWriter + + + + + + 512.5482665661723 + 485.19650292111 + + + + + + + + size : off_t +block : uint32_t + + + + + IsoFile + + + + + + 720.659511691649 + 568.4410009713001 + + + + + + + + «interface» +Stream + + + + + + 909.7434429770816 + 580.3330721213274 + + + + + + + + ImageWriter goal is to encapsulate the output +of image blocks related to one "specification", +i.e. we will have an implementation for ECMA-119/RR, +other for Joliet... + +Note that having different implementations for things that +need to be written in the same block has no utility, i.e. RR +and ECMA-119 must share its Writer implementation. + +Note also that while this provides considerable encapsulation +the provided abstraction is really partial: In the relation +with WriterState the encapsulation is quite good, each +concrete state know what method to call here (notice that +this interface is also quite coupled with state). However, +with respect to Ecma119Image the abstration only refers +to implementation, as the Ecma119Image needs to know +about the ImageWriter implementations. This can't be +avoided, as Ecma119Image has to be responsible of the +instantation in the correct order and following the user +needs. + + + + + + + + 2.3784142300054896 + 160.54296052536733 + + + + + + + + The files are registered into the file registry, that will take care +about the written of content. + + + + + + 286.59891471565567 + 708.7674405416217 + + + + + + + + Each state will invoque property method in each of +the ImageWriters. Some writers can return without +outputting anything. It is possible that when dealing +with UDF or other specification we would need new +states and methods in ImageWriter + + + + + + 765.8493820617523 + 132.001989765302 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/burn_source.png b/doc/devel/UML/burn_source.png new file mode 100644 index 0000000000000000000000000000000000000000..5786ab29cc075491d6ee6f819d4624c1dede6ca2 GIT binary patch literal 35652 zcmZ_0bzD?k_XmoCC{ijVEfPuy5`!S6Al)$xjeyhuA`Mb1f;38Z=b^hnT4Ly-ySqE@ z8F`-f{k@-i?|(CA&)Ko|UTb~Vcdb1>3UcDO_sQ;~p`qbQO1x7-L&Fe9L%ZE@?>6`Z z;;P1ghV}(b^4(h%$FYrC>7sFopLy*s+H-!`_2hJ)z>|q=r=1zde1@d|lnmkd8inom z^K2`$S*wIqKX?H>S0LDStZ;Vxf`;{T+E_1&5bptg5Za8;oZZPCG_;*&iX$wdyJ!yt zy4Y`_q48G&G(Bi&nPlL&D!5LVXm60^6HZ=TX z_*Br3`RWVWw|s%Xt(*7Eg^`3vYycAt4T_G2R$vMxxURU!m_M||>lqC6D~yxRM`X2a z_X&AOEB@QRoeDy)pOJ*I|C&3{4`5IfY!V{3;LPZ`3-<5@>=$h(J6gfcf3L+x%kLfk zf1Xx&0$H#B@3`MNbsGgqH+lXu_zNh3Orqox@=sd^Dd$}kTr@OP(|dG)L2aEyk+yMH zaYw5J*Y)3dXl6$XERy~^C`t;brGjfki3asH`1=2=HC_|>+aj^>)=k$hpdJqkIB(wD zDZ9mo{`WDOB{}%%Z)ZDY<>tn!JpUj4p_)-516K@=_5VNeb08Xh5G5AWjD8bCaTzo^ zg25Ty>>B*vT?3oJ#s1x=OtQ!%P4LrSS^l5>%Ov~nK>v35KTAYS0(@_doCIyF4qf2i z)mL;EpKf1^Jb5sLr%Ay5Z!ZcEjN3^XNOP$diKt0{9lk+6xc8kGFyi}m3+iz*VZiIn zlP(nqc^}UIeNC?p82x5`Gczdq7l7u$Jy=$BH8Z$nH*3fwyZ!Z3E>M~8`0tN)yn&_V zZE-!1fS_%mYxC>DD@UAb>(M!va~{&Qz|j%wWPEBD)yXkI?)B{GH_ta%slKyVU4k56#-IKpL z+&7%>j?;F{Q%jK(?|bn*OZ++iBXQQ)kd}d1>eijv>8thf1AwQd9#_|){>~kWerSlC zoCGcu`dXCPUg%6PLHg*5V&2rv;hEp)5{S6*`@`e72FQ$Dfro{wf76fCjwwUK;aza^icTecc7!#8FB|?LGaW({TTNu(xQd?;WbJIx-qlQe% zNyQC_&*58hd#w3zRt|j?=KKumZ-D9bl#ic|dMYK`X1%Wb_9^cgeIeOeQ2F(mbxO-b zY=6AK73xR>m@~Uj*)6XttIw;o@KMYCR%QN7hQX68T{ez4Yo8>9`tjKbZL-Cbm<$S^ zgacUEE5&f-%xq>u|K<><+yX0OiByUuWW|JIYVsoEnfNb6cTAtrAYV(zLT$~IClv9V zmO+5#02x`|+CZ2c&8;Y*+f|6BqgaJ+!~~v!XxPeLYkOh*#L>t)YWqz;yhy zZ1Ph*L`|grtl97 zYLZBQ+Ri~~Uv^yx3}weM$-Zc3M8-pKtcSn>>BBnk1UDuBv5w9~(>&KL8J zs{3mY65(@<6vN#g9$fxuKMf}!V}QwI(XmzshnVOf9NiYYrRN)#da^5Zkbg!WW7GF! zD?(-YcU?P-W8Mj+xzRvV#!^GW1@eu&0CfNYSS+nV5o|p*(HF;=l1c${st{{;X;zh@ z$S4?UC1@&$9-$D=7QD6Z;p)u#hn-@pScYbahk-^eVWA<8xFzo)1p}G(jS|fSIXX{j zZHN8e5EQf1k>q|gf=Lx&M*lqZqc`r!j`A{+J)vczP>mHZvBF~v|`mRCy9NN}HlO`#Q{Cs=<#$RbHJ~!KSq=KvGptoU7!}HprjF<|B z@ zx-8iFcun<+Dg7tl>5768KUOGrd`(P#$-BbK?hC~5rNe6tUq>JJsa~p=eoU& z=vH|zuePMj8&&=5n`P;cvP1!w&qI<=ixzy}GFDLPSKSl<~l3B`I!i`GJ)Ftf8<}p&G^2nRH!Xm zjgx4WcT$L2p(c?h=A5BuN;jJF@1?3+loa+)GWm4=zVMG}E3wpYo|y__Nkw*Qa(1=6 z@G8^}c$dr&4(TrGj2#;@R~z}_U-=J-p;m(>GrAbg=Oyfi;mb%z zbG{FZ(TPtNl%uAV)JWvwh&baZ5ZUTawN(&-DzY`UqtE%~Mqe002Uuuc;$f}_;$EvP zA$bNKEg|W$bN}&hCP2E)o7`JQfdu!LQ{#kIds$Y;^isW_<3~e_Da;b-pRMcIMcdAHEZsB;ymL|&?+Zw*kMZ&{9`UWwbn3w)*xf8 zj?A^1%oLZ)SVPi20E_ps_#9xiAHMo?JE0|i!M zR3VFP5P3Z`$M@9OwEsS)5Lj&O&YI@hp!#&eM ztBums)9zQR)-};)lh3Ak{GB@8H`4CmQy9fu28QwQg;`Rse`FR#n#dc0-GTj~ELYzC zJ!37+nh#d^NL9%97*mgzSB6kT_@<4hmmzM5G-Z4v+D-``Gio-7q1T{Yuqjk zcus=`q*Bw0Ayr!82H~;b~2Xl&(~j;qu+l6%0Tg zBt+0!wMKKID>r9R)XVakx@F6@^jYscGA*-HO@6{jH_wI~^VFwWvK#bg<|8>SoYlhx z_c!rZ?{@{r+0Xbsnk(l$E4J>%g|a|FEr+G)C}tM1c%CySrd|5weCjhoHo=4OZJ%+6 zQ?q3)9XJ0#BliM(jA*rRp1!=3h-*0?Ja$PHz3X-Z=6kDoEKn{f)a5}R(q16U?1zk@nSq59=M z6y1%=Ohzl;mbyA1}bbEZt8@Xn)Hz z1KP40MW4K=8>m&SDmCa`@M1$fd$eGk;c`fg*fVUx~BgD&2G*$md&N$ZN>AWLNB6v!=r5wh}Y3|D@5Egt&58 zOG0Vs;qOMgn;Oo0_(HYLnq~5Yk!n#nFo^FiiRorJ6f#eH2NhoYRj$^)wnwgzd>CY) z=1w6qgFHQN(#*s3S}w~`$BcCq1e+`i0tQJF&PBrJ!{ujJN?ojfNh3ppt7CwNoMj4T z^_9SCJQGT_-XJ%`kaX33N$?>Uq@f_WQO1K55F@Lhi+|Al?PUZa&Pa;FHi~PQ2JhLX ziH(qOXY)RMtmt6ByO{SG0AN);Y<6Ni0uhAW z*|-ys9$C1qagJE}>#h@s-bcJ>u53Hvwy7JwgnzRukqe1o&Aq=!zQf7uE!*{VJTBu= z{DT~KyMkv6nKt}+P)Ps%_v&s8oz3gEhtG6bi&&*D44ZsL04zE8DB7 zw|}p@mxGJzuowk@xLL^jp@qi*8P+*Y0jMiUqV21^)@x=lirK}vEQ9@D_!Z_ zcEp+|Uu06VQqy+)tcy)lkk>?I?tSA#>Of;~JUvke>~5!4-je@3_PB8Jed;vnfX)}k zv<*-4exa$?<~`y2>)YSRm#A;1`WD#?FpP4&oN6i@#!<}pUWspXY%tPWuzT|3%;Zs-<5=q1l%uQZcVzmthdOxzwEeZ+rGZA-?)z1@QoBD*L}SC240a-dj4|E zh@B<&Na0SsuHdzapOc@`GW!BFtCZTwL(w9mv%fV$Ni^H%bxuUIbS-ulCHMIBw?z~! zfcE5Mt{?|^=wQDsW&_dDcCY#MzyM!ziuqAxjEB&$V*qu14NiR+WgUv zMwSd?ms?s$%gf)zb?DE!YRaZ-9_QNY_0{9lE+oD(9n@C5@KTo|#w(pzF}|y@MVuD@ zxxH1YCtJx>C~E=R3!8~l*-#|fcgkdPh+r|~L6TCj?@Dq$$QOt|_|obEgN8VpHVl7s zrV_5OG&mctSbJ2@*LUS`HQf~xN~y{?cF-7|WT*}E$i%%cV$?X9(=7jJOZ&o$3V73* zs|z1jhLyfG?T}(Opc++>88oN`-d)~Bt(4~*S^+7P6R)PvOQKS{$>g!TRp_r!lR;^M z!;zBrli9Y(BwN z(QsA|QU2+&oIFA!5*7k8%moxV!*NH}Nr)n|;2lFY6L+K#zN6B{nkbk7&ab!#SiP+I z2K#Ka8czC^o3-R{@AM!%&xI)WYdSqycb}!cvQcWP&syJFQ}Tz!#DIG1{7|Tc-fZKc zw6+%di|h_J-Xbz6EeU^@Q$nG%)ABL0A_rE3y%$YX9HH)J2d@A9mjRGHaC!d+~XS-z(9pkV1CZPxABm zPAInL#HI0_)?p9k2sQ0*I0gJ12?a(KbPj%q6RH@gJMH*N&L0 zW%>8KuM$^;;8zCWQqiFO0a^>KK2$g8m;Ep(z@HzrN}^~#NwEtUtL3@ANAXP0p_>sW zLIwtESpB@&^=bWeK0mqS+VJuVxAP3c;R%nv)G^aa#I@$=6OJGz-r&$eg_u#+SdH^O zmT;BuNaF@gJhyt6t3Hc^ESUJ1*FNX`%dK0VFI1UwhDL}@+s~CDxtpKq2e{+vN+PCP zi_b z-R})ic<=t4Yi{>(lM=+O_FYru2Pa3@meM0>_N^+m=Rj~@%$qbW{7dIkMfm8-}iL-qkU~6eYs7Oa$bMmU^Mo{!rg22NBp_| zmTSlkC6Gp@^Lqrwe)Q7HG`%m=@1!R{IsIAs&LLMvwcLPoN8J;#eP^AnnA9L#T#a+l zw^wTh9}5;qMo*(Ox?8qM$9~-3EL6HE`E)sqP3tx8CMq)G7*Bg5g7$p>hZ4k%Qr{&b z#zAiu=l5Ny3 z;x)PcJjo{GeeKv;%LT=bG0cw*m;v<7?XO78U-*I1S-IKAFMnAWuU-|xNEUeYbX;xS z;jx;6eDE=8MEw&n&nX%j%c67#_=+#v{Q+H1Le9z!!qIjTl6B>POWgK-E{Dj)v3!!2 z#~&|<0%jZoXCB=`LDBVWvbOg>R231Qb)Hr0R; znV|Bb=Xz9oO7L9;&st)A-2i9Oa{cLFO_vhRrc)b1#Y#s@*<|e8oY$IA$S@Y7s2kha zG?|Lr!2r^dnej_IE6LbJ@e_n4;Sa0sM}TZz#Itj$jx4LX$G8P$4Q2>$1DM2$kTvfw z-RIk_5`NU~^QJ3AB%kkM2c&QgIzeVt_P&J-yw=t&cIj4zwA5q)3=i;aR02NOIKT6U zA)C?91DqgmQokcYW@(N;FLj^9`hBdl&_ANkcuH|a+mmg@U3N#ab5kYYI{OPNTzhR` zjWkm|H0R?1ATI*SW9DpP?qI2yQ-Jr?o|a=ck(tY{e3C-DQi{ z;z&@9WMs;1OVf%JpF5cX@+gR8s0C_e2ZCE*dg}_9g0}Gipk-V@3=k2Fy^XuC=f+lM z&Way#e-}{CF4O6BB_Psea|O-X``-cHtcxNi zCY!p&zvz;WTTgF?BglcJ!lwS*78Qu|;5vTzt1TB6Fz5mqsX&yu9r1cqlnGS&>9~KX znH?~=q1RESUC-~v?0cBqdov&kveh64LjkiHvfHSL@vp51j|jM=;ViOcrOYGf+Lifi zSx`CU%X-Ayu|_G;jw)Gs4&upr)722%dGVVb&}e(#tLn-kmsa9&2d>7oFzqdb?B=(w zAfwKy>ZO^{^iaf;(0KvsZN=`iHM_JfHu6n-4W)z-mV4KWYuIz^AtT=pkZO5|^ijeELjWksisea5~uH4OdX>>h-PZu=BN3!U~RT-Ss z#hjTwurJ>o?=&i&Cv;-JFmcZw}2AhO77vFsVO4q{7899 z-|g^@SIab70}L<^Y#>^=>5r6(l?8~oaMl6azP+?HwI^6=N$6bOQcQvu6)K&!c?tKY|9(N@#Oqq~1z=Ra%Bt&lp!Na{jqxfJNGI17z-8z0 zt}8lt*t_G^)OV6>o28DPt1lB^=azJDfXD9?VA!E=en=4=wiGaP&p0^-vKZI#G&hEx z3n%Sew^{DkYTHzX{k={dszHO{td!Sz@{P~01fjB?UaXQ+1p%rD$jpRW-I5Nn)|#KI zdRdzuf?6WT{4M)u~#BIUJ;qfcCY>1lArT<{6jPeQ@0OdIj&n9J(r7n zlDVnTLV?eirG)q51ooe%Nn|-23EYYTWneQR96n~yC@cd z^IFWBnQZ7ZjYeNpOYBn4SX@(kiZx(69t%^ZNQ^RFd4t`t56_9=jO1`ztcl)|llUGW zjvUeD@J+zY>u*!0vsp$~5ZQYIPC5A;sAv#a!h+(?VsZ1*@B^=uG|(*`LQ_mvXmU+( zxit3}%q)n+s!xaBkiT}n-mG_%7UB;5sJJfebvgRJOb}0~P^%a-TC-Vb9!5&l){_C> zdH9vs51;|zYfu_|>rpE!$?cx}7Z}81B48YPW9%l}3Y9G(SShauTF+(UO1HXX*l*p_7v0xH0N~CpN$mU5wk1+h7p3M9O>I)ghCZLL z69?16>WIn)Wei7qD1kZU?q$Msf55F)T%4*`eg!sf%JAP(Wb7nM&RuNV7JuI%;@%2< zzZvkj&cTa9Zu^Ozv8Q>8C%>s7ITo*wm=gh;98-!*x_0J-*IUVyO_9|5RW)^OuA70D zwUMj%Vy7F?-v?jrnapGN2^}C$77$I+%j0&9Q#*cx5XDPd^Z5{o#6$x1iSZI5x_)Ko zBbD|esiEB&Y5A&OCL`5x+wmW$#gRLgUjkse+s~gH$P;&*9dq z32s_g{r4|Asjn}blOiG1Du56-9l8Hf`OjVU#I+{>$ju8*|4Y%1Vnmt|@u|zPI9^)V z{USY{(-YzYw;I z{B`K<$ba3PYOxh2Hby#(Kac-yr&yc#5?$YD&m*mNFniA+qC7IwC0+gbDKJB1PU3zR zcYQ2Bj9p~{ZA@1QV6g~m;0=iulId_;8ylD}%*%cJHC)nNvAKtf{7 zsq^0AlRUyhgWs|EoWV&Kx~bo$8=q0st7D#PvOB)>yqr5dvZ7#MJ{ZF{`eoOr1c;qv zUziLH%AG)Qz_bus;6qi?uMBRV{Noo+d6qA*<1QBE+*(5CZQYlxS3{Z_LbTM8E2r!o ze9Fkk(d^` z+{#X^g}$HwarFHHd}W3rxVS_v=n}Hx4lpL2e%9|2&1h^3b~1c z`!33u56;#pp~X;WiiBq8?7-*UE=PWWFZ(L8taI~uNsiycbYzy>zC@K9Dk8hCn&+y4 zGrvJfsH~wbn9=Le6^g&Qx*WlYMKEN4sI~WOoq<96;J4l$!}@n0Ik?;rxf?d0Nfs;m znrjOOWJZV0se*?r|4Nv_QRob zte#h|*If<+fzeO8vZ5_3pUWxa)tP$~Aj#hHqJs&G8>d2;Ice}ESA8p8=&>l=0BDVc z4dAw)@9oj79s3zk+##$s15^SAEe&vkh=1H3p7uh`1uat*((o*I)$9J8{Gkq;$Kiz+c08mTKNs6rx20%%n?I9U2j?u( z5(-82O*W_FOi2=yq6E0>1lb~TZVIb!Gwa+n8P-XIe|VUX?*jd!gSZ=32{5=dKi-EE zpS(0615QK%{`}TlxG%d(&e!l<*Vydpgb#z27{64!@wQc~18;kD!{O43soNr6FZGM>7$#O}G#r`BQxYAQN)?#&hhD+?o+Q=0n>6lPWjN=DUkKXw`Raa;azs&%sV- zMpY0>oMGogj>e*f%MHZ=w10mIa-z`v2jBDQK++r9{8VYSLn0RNy3>Zj;;gh_YX#+u zsYc`5(eme`k_sb?)?<pZyecR&oY|ubw0j$bcLz=hoEQ<5AVRV!hH4w;YISPW*-AYF* zU?GfDF$((2@N!@8wcbNR^UIDFl4D!ih&Vq+4t}_40v+cBc7XvjrCX(4>{Cm9h7WTo zkj@igJn95dq!UT{E6)h}UY=VOBs0HM0%`=+ifjExEeG!V^V0Ti*mY?!%kmAq=`3Dm zB)sHB?bMf6tO7%}aOYbp(BjLT<7JqI6BlZm8mutT-b|qbMgvLPQF;rH!AD>n71~G^ ziuMAhK0^R+a>awYAx34d$`_hCrXFzZzlv)<(kz;A5*N zeM79wxgT9zONN^Z%#)f+Q)3KMen3;3reA3&&$^XOT37BBNFcpQHIZKAnNLuZ_Rc3S zFqXm3XKOd&sLM3~wAimb$D+n}YxZL&j0R!^TD4+&jY=Cfk0BaIM<}Wt?RQFcbkMfD zyPNBIZOvj@0s0bkMuf-3u4YAp5Q*-)5<(i#T?7Bj2aEQk5lKO!8|JY%`83v~_iOH-6`p7Sf@u zB_-P1bZJjOrtS>ii`rZ@8R7yc13uu={E$l{nU-D{6e*k{Vo+Zq_X{ zDqZBo#s0;i$ihh(YhO#y_C-dgE`HpGPd$b_Ogpw%ktB;c=I*O1s}@XM3)`F~54dgs zn->pfgp$i?&~#wZOX;Vk-K(Ao%1Vrfox6Qu=8Lki(97bg_sK8u<88FUu?&sRt4FI2 z<|86z9UL1?VxLzE8bLV4O7?vfaxSfvZDS=cL{5Emm370yYJ-y^kFig zbc+0KmQnWndJmxNt2IwFj5bUnUVNKeb7O-V^2_G{Z^?pxyXV#iXh)3$<9DG=>jWkt zXcM==>ZtU~4nZ9K-RfYPVij4!b{1$z+XLI&10kc6wpK0A`%u~_32y}dvXrDk27Dsw z;hFcE-}~XK8jVizHc)GFe*SNNEI>Lnoidnria*u9C=$%i7xULUbB$}O`~v)vp#uRH z#_S7~06tC)@$dEbTE^R%3D=D8-44}*gkD`w4-M0$LvGLf%Z?s z!mIBKF6T-F(so2gCq_Bjxh8mC1Vic7de zOcVA?IYZ`Pqe7Dj?1Fj|nl}E=xQ&W?=6{}*ET_c%RHZz+igYSjZT+Qw_nXm=8U_8) z&dt%jwQtF3vcjv`J{o=QI*$!`!nN)A)np%zed2a^y_VyjX?AK({&_6?VjS@Yw$j#r&%F>5&8N&R={Fqoae0|GZTw%y#$L zEMa`U9i_Lu0(M&Us;>ZLE~^>V(c9IM)WO-@Srv%qTCYK5fB#>}?+d0Q`_{fVe?7Wb z0T>)6zvbayzwJqko14i#klv?v6RO62k2DLXx%JT2Wji-Vcat!T&jUs>s>}l)GA43JO#Xt z*|!ZeD0A^DNKAUY@_;JJn25pcT0`_Wi8=On?8SitN)1!PvSXgm0lGI}B%j<`r0~ z>?JPre`J>lZwy1=9W|ID8BB3YBrUr%&}G%l5#_;Q9HKPZLDFY&8KsE;)&NlAE~sK^ z{mmn+L(XylZrP$!q$I3lhYI;CaH7KnDizd6nZ=42T}dvnBD5qCOfTAPr)td{20aMr#}%Z0pU5D4iB-yn_pNQm}2r`Zl3&1yK}6oJC%Cu%R3=n zr2faOAh_bX{V8v~V6a7Zw|giAT>(-Pw7c*{wXODzbGm>~7{Is*0|W5iqQtLu&F@Ik zx%po{)vb4v<4mN)51*DT_pgaic1AxgXYgrf$-P+5o{6cfUvL#TiAmFuSt(Q%5<%|# zJKf&+<3#uv+b(78^4RHzrdw(6-Y-;6G}ulTHWDoyZkjBzLtIFGvdRgPj44Qi8be`X}o{o`LWzMA>0dLbHj zd-^l`0JT47uf;KAbHXxHPh}aqVc%W@W}LC!xGcRX?U=O}zR2jjP4Imwxq?^d$ZZHp z?yVs=8bUe&EX}C|$jw$5ydUvBVUa|KY-l#D3`>|Rg{iY!Iy1;sk1V; zJ$VsdnLXuCMj6vHi+7q`1*(VEcd+d1)GP^oRWCBhW?cQB-57Ju!7Q@+ZF0Gio$hQ! zip%F-Q;aU|oo63{vnZ39`Q?!xXnP4gi9cpUTI6PGFcQCqhj(#WxSBlfaMz2!mHVy# zdWp1m?p1&hM5uhRWggydUy_DHB0XCVkMDkmMEkGWBm&VAz7+zz?&UJ=clg~e9na2> z4F+C3NFa6KdsmBb3daD;^)y~QIhoKId@Sld_&AdRafb=tw3u0fAcU1S6t|&+1ZU-M z<|qNk`c_5i5$~+hSHq?D^^2L|9_^Y27O` zvJ5s`oN0>j+MFUt6T#Zf@{ia^?3W_Q4~F>)khJ?!Mg^fz*>F;Aq%C%@9s7@ZS#9dbd~-<7=!%e-c?cp0O%3TE{)$)>U#+^B{0h597jC)6?d~q$k=lhJGH(BwX4om4E$Xe6R}=HcKNHq!U8*F zGF<0)i#S|eOA>?5Q=z8 zcYQUldvGapcA}ZN0jHVf>WBoJJ0aOjSWq)A#8L%81%{vn`8(d#4F$RgqVDOUZo{n_ zc$)`KX}#C+rl1NUgfA2V7|qMF?sBkQPbiGQ`5y|5WDyM%qw2 z9f;_xDnC^7;Q2M29lgm_Nu9pVo%Z@t8=hdQx!Hr~cPf~@d8$civ8OYZKDPHZTav8# z2|T%sYwF3gVP*U)U}sN0;rmf|TgbF-cL|D$McL9(f522%dA8vDtf4)xm5ZnLRlLjI zIlZe%O@E7udrx~8q2H&A!-FZR94APSrQ=i^42aH!R4T|UN=CW*NNI-o@n&oWX=bgF;>!w)F1H?jcO3ET#lNKuTj}uTpte%|rd6IX zW6bRyK{G1P`7W82hkhhtf$*@q1a2v=OSJfd3wv^5bO~v7?`U{aB8e<4y`*@5BTgaW zL$)vUOz6cGR%vBl$ORtNLEkgh63C z2JD<^_-ltD!>ifqnfXQ@vaE9Njrh3u6-DOzVEZnv5QBLhDvzm!;eplXcHNHAQM(8G zT$Vk`2f&YK|MvPy0kSU`8zew8?7}l_t+@WyH>yxR{#r18vklB}O)U6t)!jLVFO>)^ z*N}}ZXdiTKT$OzAR_E4i$%i+KK~kvGPY7$*W_^IsAiy*tI)L~{+nMP|X*WMw-Z`kx zNpg5Y^K-)J(Rh?+y$)Wv?$P1L@9P~E=}-yn*h|9*T}Sf53fVC5y9P#uEE$PDyN}hsv)fxXD-<(ivvfd5-*p ztu#7)bY^yT*iv-pG>Ehdo!?h|sK80kD#Vg&7{rlqDri(6EpB3c=0?>|W-_Z5A0mML z+|snvBIpucmSdc!5azQaTlKc9&tKUFNfst(%V|C($v>xD5=#s$-Ga(iC+U?BRMvEU zsBvz zYs5PrM#{vLWE(yP_0@&A#wt-Ro=08%8@l(`h4gg;`j!3^z7Zxda!aM*%{d$X>VNb; z;^v;3hTE2{3HQIP+~d4dFG4&m6RX~JK0-(M70^KUb68$}6uyFAbwS@`S4YK_E@MIq zeqqXsCc>%Y{)yDo3#zDm|PQ)V~{I6LM~lB8Rm3T7}YyTC+%Bc8@H6XS?3jjJ)o1w`lRu z$?Jz*cP{oRh?QQhi2dA9jcQWM{1ZS^u#mZYX{RCcS%w+B)gbNFU;$M&zgX1N1WBw{)JMA~9+Oqzvn?3ECt(Wt>a2manYe2!>o*RWl9kN*=IB*CNOeepBuv(MlgS5Y$t#j|T+Vx5ED7`nc*iCF~^OR{7a z$}8EshYF`cCDI=5c`A^4E`-tHqm{*An@DYrCOcNVu40?#2Tk4qG#eKW&*8qgW5XPG zVnE}xi92TrqK~Pzu1|@`**mvU@q(m^ET8adlbPPm#CfSU~ifUtBTTWJWJ5LwZ!POJ3K!nbAoU z5UK)JCv|N^i(1b~hBAW16Yntd#c4zwoO5LvkiFn^a%&|skz9)=2u~ZK2-?r#OA)-2 zoYR}8`I{GbQ7l%c5?5#)VezKacK)gM?tKHN$_)zD+^-CeX#$&Uy{^3py zUJzpg3l2(*Rd8>$MSze!?(E+7{Ie`P4a%G2h$04NNzs8NC$I=$@Lnz++XGaX_Yc62 zJNvV3pn^MdCmH_b$p401oXVbm`yX)kOw zFV3P3Gu`2(G&|?Mb+ry@`7|B~y@C=-;U;#DUw?ym-i-&UJeH~-vHmW1R#wL{mNM7C zUK(2bo~X-jO$RWtTpvq<$M3sdWS0JCxd!0Pt!H(P*==srpRJxXOz>itUHfr+Zx~2( zYi?XQ`Ph$;SdWNY-tdO~M&5l#eo}4=Ap-(q&S(9mQTSk5@>p^Z2T9d#5V+6S_I~?E zB<%ajgJp854rhlz?jE_JE{=u$b9X#)q@VNWF{8n!+dknX-L}i*C)&6DI*~U%i?^M_TQldJqMGM zDUwXBA5FKa_Y-5K!sWB!6VRw@6SL(U!}iSeBE+R<95lB@tXig+Bqpu;W}KXBLAL^U z^1u3t1x4^swNfl`$s;jzFVCr4I$JIrYt~H6gXSA&7j!sB;tkoC!c=7AdxVh^P}2{B zYa)+pj%v0W^~A~%lO%cBa3e0~V)e0>x5(dAQ*A*i%KuThB!IMvEmk?%OsM_1{!r(3 zl0#!|PO{Cnj{(oa3PPUw)o|%Z1o8U8>7gMD#k~nLucX66@#u8asiAIObA1>`w?jenu4 z)FbZk90sD-V`|&V?5&95_^3$gzsv_zw%%K{frKK0g!x5DTGwro;drL%$oXWY)gmKd zw}X)R;G-!1+5qX0JAMw5^PUVaC|BsC;+p5-aJcJc&fbJSOTyAv!?#tg;t%u|hsHyS z1|_%ntgG)-*+9gREXWOu@#q%QKsF?JOn?8Mn0=|6Tdetj~z2Do-N4g#$d~avpQgnfQ_#>@&i#qp(L+^a$MPRtxSd|4Odb?Wp1mAb z`!r%%2xGD00ZZjS2L4kjUsW>=W0!rp=IfIt>ONcV!nC!`yLE5WP-$fJrL?f0EOXIA z5BqOI0NwR`X@gNrW^x@wsAIVsDD03(lJOg#f6&Mzw_u?M#(afFy+Hz^NLlQ$k!pm>M zb+h|UaJsy&q@!4(Y$1`s^RUL%%Lv(sfVTaNxgNDb_Ip-R=yycVPY80en>(>!gi@VJ z#$r*^W9Ov1-T?d1*+T6%lG|v^0!d*6u|l?&L?fV&DfM2>_!7wYD(fS=y?3y zD4R?k$B41xb5qF|l~^6&3X0T4po?J*0d@@_`{EGLUq{D$f6Q8sN3qE&MY=F8X9=_K9wqL0r3jncAVvA z{o#jWv{~3Grv9VdG3C2GXnf$2a+kXi98yoo5w{Xus!g-!P0GK!Y-v{``lgoZ@@+@&2dcu4^tBR7Z$>+tOxUm&uac6ILZ}saKNqE z*-Xxqs56XEuRv+u9sk+g80+ZL85Ya3z7HjK>GnRqY;sTO4IO}J{dt+5(6Nk>i>iQ+ zHMzn?xkH2jgK>5Xmf+;4Dn=fF>FM7F+0hru2a6KR++dpGo>rBf(D{3k#)Gk)lA}NL zURU3n`MBf?g!rFP7qpeMY9ait_lDV-Y72bSS%uPRf2`(*4Dc?z6_AzbRLtwTw74F8 zp7xhAY&!MS_!RMqQJdNFzQ8kmI@SH+&OCsO=2|LCdQ-VGep*V3TG}lI%5}9qjsFibCDFQ7=}MiWhak+dGMZU zHSA)z3q8~zv{o1yDz#}rwVoB7t(*@>odAT9uEb*kG_-I9h4Yvjk(kW}0t}YIMx*OzaH*#qll)*O z`jYuFwdrxF3U5%|XGpQZgdP)kA2~g-0MS!W;CV7v3X=bB7Z zdQX~=zI@~ju+xOX5r0;pF4PLb3LYqNapL1XN0 zrZc5YzjC0Vhe8!1@ppBUg!D&)pa7Z-X9A(039n#v@kE2QSZUsiOK!zRVH@u)TV6Z2 zi%yBTGJMQSODkKQbHdfSJ?~tsqMCLO*T86!STtaHS@@Mq^0Ehn}b#q^r9#l4>!3TjqC%*(#I zdN>-N5F^T4+bn)!ImYDJgM{oFn0;X-ShV;_7QPxj$L>(GyxRN2)P;Gu6=c@Bh&~)H z)3(d-6b))=InQE@{LtT{E%{_)y2I@Lqj{}08KQ5LCs9+$!NS7k9K9@TBC@564vBal|I5ulr51g{;rQh2c)M?KFGi#1op0j3ZzeNloo@$Q zX9lQS6j&IiV&C7<$jafaS4E&h@cT*^B3?TeuQ*Mm)^6oE=hiH7s7Lk%>RJZ+(P}pi z+HM|dp1V(Lw~)^M3#bJ!*SfF?4{%^kkD@Abe9N4!fcO~vM*;DJ1%nMqDo(Y&eU6^VU@Urf9CK8m@u2r)bps2)z@2v_AN#urnFG zvP(wZe?Y8L_n4ayO(3``i+j;4_Z*MD;Eu2y5Z!yq=swm3JWO}x@*6L0_lrvv5KT!l z{c0t-si`%gHa7akO`5z_*(x}-=KH7^qvj@5Aj(>h62U8Vn))gltN}yHF5UUkXiKe( z==J()+T|sKJ3AmShtv9sP5~X{k4HCTHG>q}cl+~G3nTTu|@BthIK&_of4Ip*pi`UPMG|TI00b1HD78 z4|1GQkIrzuV9^n2M@=DtrG!xK`z8q=$tKc@cZ&x~=|^4^63N zL7YWCtyuysdr+qDM^fGo8D>5Ol1I{Knk_xrzSp(xCPsX7Pt;~It@Sml6kSNLC-4#j0dcO2(e5*I&Gm)4oxI+Iy&tgkbjf+JI&unm1F&TQ z4lTID-P1y68H(RyCy&m;>Uzlplc5Z~8W8(LL%CzD`Qyzj0nZ&Y0SJ#V1v?p&s7_bG z#rfSDr;@QWX`NL~mom{rZ2bYjp%7H&EC^`U?IfEi#Hc;R-7trVQvPFUlG}ricjo&z zcXwO_PA(j1or$nrk^d4zw$5{MvdOL?v$rek$NW>Ep0T3Qxg#Aus*v!ApbntG6sAol zcTUNx)ub$!Fh4EYSl7?^?R0g%a@ez;Y&4aFuW+z{WD8ZB=xV`;oxQ~*S-j!0e={Ak zy&7BgCqiO){paz))@EFMvd-nq#ABy}+{>cUTk$WFv6uh30cm4%ZMs@FW_nq>>a-}Q`3!7s0eBNwa(i)= z34BpdZM05wDU_;IEs#mtGTj)70DvnB50hR>O9n+CJNXMH7aZ3*c`gbsS$9O6ysFNXG=a@r z+vs49@OWg2pkyah>3BL9+5_`j9`eL}QuwLYtZBYYo)mMDPXDMTHf=t$^+3%Jn_4*? zn(zzk0KCv8zi*mALQw1#8>NUyzB^R*(yF?;vc)KcvdXypNmv3IYN9NG(K)~UJMp}Mb6`S(!t}wp-x{Xe8xiU0C8LarCqHZ8J4)}`ZU}5=I>-46V&6v+ z)*X>{8_b!L7nO+V!OJ`!RCl{rJm|b|I~_AYSIeK9`e1;?jDLF=8_1FdXy z!?Qccz?i^%{*5X)3U>wmSb|LBYcNSh5+G!NAkX`yYr+cWv8FJ(Ei#}CW z@AC964!j+)gMbNOYj`h>x-Q7Cby#v!b2XrPE5Lji)mTZ6YK*2c;e-MI{B$E`?STS< zhdCkRZ-}T+KDJdwQzz=x>KEQEYfU+QsZN~T2kaY80eWky^mFJ*X@I;095DY33@v4> z6cW)86s^1vReSYe!E;MR#N%3Ghg9lxpEMWC)7)u;9UNJ$ukiiopfDY$Z`F4jf<8|n zazhlplq`^?w6En(TY0CG6xtk$9O7NWix@oJ_A0wo(&2~ZS%I+`=fDEcF6`0Kh}Km- z!E#I#m4)nr1fF`uN?AlCt3-&C=MYBvG)wW_(dVYM&#KGSjS;6u6csg-}=9QHgGbGlKez2>nKqkJSBf$3O9i^K-a~mQaf2*!_GF#;W|U_ zvKGm*aO>i-)FV|8EC(ZME#4A=X~Hk!rEtiI&6_}$2k7S-w<;g>3IFz~-shKRb0qc^ z)%JK1h7O$$i21Ot8muISo3JpLODit4Syd+OYhY4WQblZRzugvzsAu(>+}WLM`0hhq zA|*sRLSrOoN}Y_zfjg|3m%Qng;aXW`kDC}u=L)DTqkQpMr9e4_ne1oGwCT*(ZKvkn z$B8A*O&v5!(hl#cnBj-`%lfJZ$IZ!x2Djx>l7%8|hLt~}M+xw|vcr>oISMDjxR#+D z;%q>JGm4CIQHqa^xOU3j+$tAMy;QBPhzt0|KStWCZ*WB0V$f5LcM-V$wTm_w%k>bz z_kmr0d1X?yg4EpE-*xu)klb0f$HgAlz91gJ+9aMvtogD9a0M`~dQabz-qbPQROQx~ z!gdAvKfG8LksOv5`{0n8<3_WUn@ef$ogLhHQ^@S1F^ikc%)sPp zHhbE#KKRY`vcDtxE%p*f#hkD+(1$uQZ(0bYOn>3(qtr&<$<5N~ZIT>|wJ+tjG?uX( zBN8-KP7&^TXlun0l8Yo{-RF5K`@s6C>5K$^_<3svy5N!t@e^F1G1ChP3$rGfq9K{neo_(3jGtQ)J>>NiToE zvIffgF{14J9oce-Kr}r({TwoI`2!6~w)K)~@|HHstAq{g5SCT`F8}SaX#8{SrObT8 zk6Q-`!P&OKp3y{~$*p6AKvZWa6l@7{FD zq}qC(Mo@+*nw&J9g=+t+PrH)Et+vm)`@5yDY+#ZJH_a6gUZS#`dac5M%@`$#T%U2f zz*L@3d=mlU)@?+O%ndZm=ezl7Ty!`gvD!4Aa~n9nLD?+xBB7>Z{As4Uzzt< zW|6+!4=oz~wa|yT&AI55qBglao`AM{TOH1bCXHTh5N!1>qz}Pxlg_|q+8KfHuK$+# z%{!4i?Z@k5sQ-Uigl?K$rJL*FPS0=3|HFd(vYVO2H2XnJ1x2Q#=t` zzYv?c^Uf&yjmlw;P|hnBk9QO*Q3AUytb4f^+OK9cJHXw0*UxstO}d-~l{5hCqQz#| zt6=5Mk2>gX4+PkmDsh$&GbOBU6D#jHt|%XR)*pILJ+&dOp}zHfJ>298E$LN_Vxr+Z zvfoGX6*iI-Fm#{%A2 z=SPNeS;O0fek+1afPY5SWt7-FL-VXlJlW(E$H{N);2d3UaDDP|uwhQOd(O_@oq0s7S+81)L`FlVU2XiQNY3>@hw@Q-!fyt9>at-msF-Fvy~X)I`(gkJLl znd~!alxt}j=hU=u0j`EYL9TO7=LB!YC3Bu!h(;2DGyWi>ZG`)?YI%X-ot}KEKc8A9 zJ;>JO>6m6ENPh?O+J&DY%~;O-i!|e=`YL0>xoF*|fJuh~qw+*i`?mB5_1#yy55k2- z*zyb2er&U*Idfl=nfn35V5O6;^6iI5s~Zt7^Gb)AAUi3Ssc(;J8MRB{z#@2nzL(&b zr?{^21qwmKXttTz9Lpj&V7C-$n-Q!pUN!}IOn(eLF<+NugY<#x%Z0m97Ax!=2A zdG2gGa^G2kg7bHrp6V2(oz+@&IkhVq#1IShI^z_m<&%z94{OUmikm(A0Y5IPHEHU@r7WAs&~6W` zR;6B^VeY>QfEL4+lUuCJ7zF1~-Vc(aGy66a5vTj7kVUU*2O;~b!z;E!zy`D)T4~}w zD_->nI9>~1Kf0KYvTk&~%;nTj2k=kZBlXd5+A_6#0;O6w)6q-2DE*~E<5;XssT-0U z%Hugz&eew~+&jkXrr>=?aFj1A<|x5Joqg+v5%bRn$*hOVE14PU@6Du9%FlNv&(cj+tFjKTa2uUKElEwBNvW5c-POOOoE8Qz{3QjDsc5`i_S{^eRW@B{Xg;PYQSIY**jdkVp;c6i)rDEp*OO-^U0 zJCk0yK6^2?=w?Lw_=RJgY~{~`6C2%V8V6GpcVkN2XvOm)!KwS%=m7D#X>{yy-i+22RvC*Nz9qT_RV38##wCSulnk5Hw~b|ebc0s3mDS5m8=7dFKs9W2I{th?7ze#O$i<{oXfjd&P_rdkR+vU&6J?O#+|V*>rpBQX(PYpO}^D%gH(UdKAk>o;_4_+veS2(dNGStuM|&S zQK&o+XZx)4sY2^0amr>F`>WYi;0|Mxe^A0CHuaYZrqD?qr5qW~z0khmGZ?tO+NU~Y zHFi+Q_K#nFBTXQQ@(n}uQ99<^;ywbHkI%H6f=cV3>E>U|lWCuSOAjqCI{I?OGn6Qo)*kza*Lup=??qzTtY3S z(?8MbCL&Wc8J)1nS~ns&xbzhFN=I$!vGW}6doOSJxvHdRz0Qc*0$kF*r=PZTQstv2 zRzs$H5|_`%evU?mf*BaU*=j*o%dA`@Dy)(0NiJ}B9QgXzZK67? zU0bwC11eS5`&HsA3j*Ry6cN@<(XeJEG>12&ok?0 z$gX!e1%sqNRx9lYjP6(CTUIGZoFz7X zy7#%<5FroWhihq<_PWQbQJ;*(3gfIX;kcK!2dapPmK>&0%RwymO>vEh0$2lhtN%nI zXjDx%JK(;igI7fc;|dma$_`8IQZScPe(4g*uh?3@E*u+YC0?|xa_scxFAl?X4?0tr zIb!7ZrElft3m0CZ`wN4H+(^kqA{In;aFFlZWXwM+%j2XmeUDf07*0xR!6g4$h{4=L zBSdrgP$*v7)<)_}-+LBs6Z`B4mKvI@rx`v4+@V)jGiHd~__mkia5BSJkNC@rbU#Gfn{Uh-oZnLsgN z?r?B4O>zDDjK_{{)A)6=(`2Rwa4_~wA=_=Wul5B!#u(L-duYImG@Lk4R34Gkqpa~#=@l;P6L&{iP%MF=jiygOSn0^d(GK`p3(q@Kwcrz#@db%- z>hLaA&5k4=Z7l0jEGbAdDZgs=Al7Y$ih*Iu%m!;%UT`62KC}GCHMzserZZE~RxJ$;rmiN+ zNtd4>SLT|u4>qKbi&`p9Re4USJ8x}zv=Zc~g?zxH-SEj-c&2lgnN`6%FzFS5XWY&j@A0&A8gGkted5#MMPO<^Z89^8>fX*Kgt2Ek<0Z6dQDvfqIhEZ{hpZOLn;i~rdtT(BYi=ma@Hwr-7y5_kia}$UDb80us zeNhN+jA@bV4f}`dNhgFT!m_O^j9?k$Bc+6(BY7DQt2eDc(A2to;xs%TJfND)aOGrw zx4>vfAZeVaJFZ{~IMTq=rK%2WvRwCJfF>ZJH0+a+iBV;1NV@-?@;aYIc~^mH6jI>7 zX1zQLORI8GPltS%h$jBl-+I7S#k=cRj3t7g|1n{eTbn=4GtR%|9}g~iAlR2w3zclE z(rh-_IiQN76%lkX8t#UXv)ac<L3R49>Wa9# zm`LLn<=hcf*f+CpAa9u$*(u4>sI4XGO598wd5caFajasKJ3ORH3XYOk%NEb`R9Kn} zj@F*~=GFjYkUyw#%;(qG+>hgcA6)_UV+)P(2QnK@8^Xk4nW0ols!GGT54zK8#ed6v z7;bHCj(s~79jox&&bWC#zF|8-z2ohSaHYU|>9ei~$&r+{J^9?g(yMoHP9+9L1zeeN z#)ekUlcjm~7`Oc_HZGp-?Q$0?dKOE>n~Lt?z2oS?J52P!Viu$~su1eB1-o&{w+JYaT^tDWu=JG({cDwqLt5Zf+?|IsY zeT}_$|Iw2C`5-mtu9J+E(B`b2tKrJ#Cp-ebvj=-fKWo#?)T>Xq_-T9$S4!z*618}d z{>b+8vA}twyI~-0`vN*wJJII@xhA%)!tXJzP6b7N!@SIM9e9FheNvFuR*P9}xi?;M zu^Xp4M0{l9Qj;vz#8`7A*rMR<7k{k$yeX)>--;saHg(UVKJ2J%%9X{X+>`V2l_miy zL_7j(*->Y!Svib1-gADd$j(VWk0duo;d-pZ@TmllPu$mFgp_?ik>=QNmRNXfa%{0* zRmYwYMKxPEI`Tjj_bh`W3mxt$5Edy_iR<4*V^dE;PIkJRhJW7TOtIEPshq{8){*xM z_J*nfDCMgZB}1x>mGnbIe^+4SDDKYG@A9M~k5tQo@i`T$x`D#6!h-rBEhLVQd$tJ~ z__=81MR#T;IHnzw!EgV#*61)o{ktmr&fPPpLK5e~9PF2T_*S!2!Vi1=yU4`mzfzgP zveMt7lItt^)$$wF@QlGMr|BVpVimb^7-uASyIwfA*>=4Nnc|G7|D1!pGBpSc&OOvr zj*W0r(;O}!Y&Ff9oj88;!ov&z2%qBmm}N^wQ^*_9DK;Yh5D=n&_UiVJ}}tP8F#O`mVeLBX2eXBpXlt zN2(8uY|G)fT$*k3AklQQXt-PSt6S>b(67=XT}8FcTUTV5*3n>Nv{Jm|(H$l82DpiT@uzzqWg53<_e887u>0sY8c@s$-{7m>C8L|>mO7n-Hlc?D8R)So z6fH92qQeF9>rdWexk9PK7K0bXI^ra7YXzQ8W(wv1waSN2%7P3>`r;664c$OHKfjw_ z4zbNIEEft}Xn4y=?t71a zb~Qk(7r-&gmXLt}^$%taqU7Vzultj?aYA-@Qu*Z+VTfNO3_u(Z=HyOr}{d)E7v8+tB>Ju)}`zD+=@ zxWo&~l1}P?5Ue}g$j;UE$ieM;>kk^2HTgKmEg;lS%fpUrb-@n02+^*eUydrwdgF|^B-CO)&sm-OR@7IXUANj^A&qCT9J zlF<$9JWnoC*3`;YbM;n15&%&~yI)*IswqZZ@>Z%4fg3SI1$Ju3J2!T#kLDcp7dy;r zWh(hthEd$dTq#?Y^O0*E_ga2i#;HdR`cMH`fbYd|@9sulU-Is4FaeuX_148Y_&g67 zsJVRQi(nf@jy0f`t@y$1>lTB9Gt;-s-h3pe>O6qFNY^)rV&B+Osexms1+O*-J5h&RWF1 zsQRFmbqS!hc$|~@I&YT1nSxd$4CE8IMGkW!I$b^<;IyRqhm6K*cm`S$NSU@XpIin< zyZ;>Q7V6e`?rmmBvVh?6LvNO?^QS-`v%BBL)copF;e6VDeX7r*{<1fe;vC8W7Z>cO+Fy&iXblcB1B=%ZnWDTYbL@ zg^wudx zHI~R>{M|)f*xLDSr5B(Yud08pda8TaFdH;Xq<(I#gA&5UfLWdKk}^S(Nf1utL-+lF zn)0fe%WMe624QRWAe^x^R#-ydRdKje!VqCM?cYxl@7mN=m^^oQ@2&pM zn8+}=3)Ujw(b7n1vvP~dtlQ-~Qjx-M zpHK^(p_~s=s0A_96cT{0$XN%XGEFlrW{s6AAlm(u7wFjhOp#>O-Kr|>qKI}KQ7Z}f8y3&|*0exYc0+kMMX-hXuUJukRe;X3| zN~#4vnk8Z=GNfP}l0KC*GI^W-G{e&waPb}$FOlMpvyz(rWW2k)&R(0}X@Yh(mZpDp~d|F|2xZ_*aE`dbYjLRM5?ifU* zUlJ(eZwR5I2fe;VPH6ec7)>B!ySl2{oYk*tAamoZ;8(f(=kBm5NvC4x@0m<@%t-wj zrQ1K%NGIOi-(D=`J3E%GcTiNp58CA@1Yi2G(MZKgcz0aDrphes>Tsezs{-6>g1W+b z8e)#t>Ju$2>krPo)*s_v!+PUlH?-i73YHG#`n8;LORkzF0JLA&n)$Ycv4NBYV8pUB zH18Y=e;2OC8rySpSM3u=v~@4zV*mZSfp(-kKX^DehZNZk?Y4e)FP%~WClUUHk>a?u zE3xe_XbY^(B{QdH0q{N0rM0Gf=F>MDiAyRHfX4Z%K^-{B*o}G&fjxi%PK@|_Y1Dbl$1L)S8rLC3bz+?IHvnhNo~_E^ zr**={T6TY{U6%Q+yK&`jtxk-Dpw@4--$Ef=6JYfKnN?fR$bX9beF7B$l!PQq?V&L@ z)&h{;Sn`?I{;x`=*)ROdnww4CzaN0MfDCu=0l`UV+U;x#pr>^Q*Pxakv;6<70fJxp z|E|Hn*v;m&tyeUEEI_tt$CI&^k&>ikXU4AYYiJ7xgJ`^tZ&g)XS8vEM;}L2~B1$yK zlRd4>P7;(6aBns+y8X#kjAKC(W_ONR^p?EeYwAZW4$-1?~1SRzWN}=Oo(E zrY2pdeAsuqpjCf6s(OJor{XaC(@}AolNCytW|G&!sTeC=5YDK@JeTvVL{I-v2C@gB z*8rM>+8!S@8HDi{Ktm$LsZjw6GxQj`RYJ!f!Z!QT@)^+W%S(duG>*M z>50#xizc;^fu@?O>oeDQ|BLUn4lL`QoEwEOd+$+&?G*(Np-(}p6@B*ZfpNoV)EQHVFC!|sc60_;I8lT(fdrM-$PQMV8LC9HO9utsC6PZN1f z8Z%LyWD?8af9#H5JA#hMsn@;mQOuLjGG>$vL}P$1lFh(4yCbG{qs{iwn_AzJtqL9y z<3)-NY;B2A$|I^%cl)-@ohVU-I=Y=loB`PKuh?>7pk81&VN;*uOz`L!ZDJ6|W8NnB zP+LkT^YJE4Z+gnmV@+iQ4x~UnU9Nk-qkv!-sql+$vi2lR28wa|bscvmTwjb5fHL>y zGvqkCyUX6|YyLUd@|A(PHJ@Y8f3k-^@pUx)8*1a9#Y7r9=~WZag_Ag}0|(k74N)*#*G~S&J7CbdMcmhxW*t$XHFeu{y;)w!KY~&6D1po(5_(?zzIEzz+VXmh%W#FeYiJVHH zyB`4sh$AFC*q(scMz05qNf2f*p5Y6o{=3dvW_P(U*UY}K}|>v6JsTtf{bWsBo5 zF;vQ03tXJZ=@2|4T&*ML=6e0=gcH4&-}HhxZ!u20Q@5vKf?2-_1Y?0^oNp`vgUnBJP24q_ zB(#&?%exShON2{g)~+Z!k!m9M`>X5HKl8RitV68^E6y%|lubEbx!(>D@Y`ixNx(t5cPjE6@O z7J-(-*5c~A0QTc4yg;&&51KvDQ+rM zGN+79=mW_Jn-?yDo-f?grfP%2oHvKz2V1X?uWpy0y)!c!|L{8PZ`0$Gd1a(2Py^s>$@fZ+7{{p*j}^o-S7v*-F@ZoQgCG5!J9aBYk8z5p$$*70|eDz#~g%)u(~ zuRVfXq_f`dPc7G0jGA!qr4<Xa#6}e z7PAs6^VUH(@FKT6G!;Ta)+=Qw(F&8}E4V4CNpR(srvhb#VynI`}c2$}K1N;;BnPr`q9F8W>Bk{T01I`SXiMSx>JuRP)e z-q@kAlF2=3NF5$e7T0H<5p3LF5i~K9W}Ro-zj-qEWa#`^6n#0%kvZBm z)v;`+ibN}jivQwi3~>gF$VR3s(A4YoHNJgF`LK>$kj1J?HqeC}t3xl3nWv~x4AaCv=#c%0CtP!$fz=e81= zkp6ys|Cx)CO1t!SDLvaOL%R0AgQb!lM66PpSg8dFZt`{}7tDNEU}4nckKyf%hPyh2 zUgyP==uP>E87p|sA07BW!wY=j9h=zY1x%>IN{>ZJjDqDKv@!qP%iuEF9hG=yOgatc zI+{AlGf?19DDtO-I3&?E@n&G`#@t4K43cZw`0Di9rp#=Jqry%KRr%(0F}F4jQz7Hw zbjtu(KPwR@Keoh@INRl%sP>i6`CwW4=uNW>R9~4`lv4?%FnhK5dO9X+rNN%~)Xf5i z>tFzth3HdajdV2H)*Fc8RJzK=XezeIa3P2ItYD$azFU8_kQQ2PGc>*A$(uoS?z+2e z9ETlo;pKu~J$JPBuyeV`B&5VV3gg+m@+42TURu3D)l+4lMP*BV`X0t4@U3-{fZ|+wc+2T&@gkeDk#BI~mIEduSA@ZD!TB zZ4)=TNYq!7`6Y<4R_2!Y=fGY@7kBro_n}M6K+_39b|0ew9Xpk<%?coUbPTw0US3oj?iXGnZ)qMq z&NYtYs_Ni;nKH*fD}1})nM#Pn^CY#toy498qnFk1h-{vG$?dYAj$L74a1D*Abxjy5 z9VwMT=mnbxo5#50hcp70-QB8WD~E7JX?DX;Eh4s;mBd} zm~gP|?@EDIA|vdQgVgBG{Mlzv9a$;xaW`|vY^He5zM5Y(+E@bDR~ba(ZYp$Q7No_q zYIZ5`ItFztS67K_KnZ6b#-K9<4Fg4*`4|VES|s+TQPHcluN-t-x;FqfF2FLwM* z@mEZY$NStb`{j`glH+b|Oy0S?e|gZ$N80%__sYvoYK3-}S_-^AoR2ZvKMF@1y|A9w z-|X}qMq2!BQ=PD~>_x$`<8)nz&?$QI~< zOQt6|QBnOTIA8D3bNkPgWS8v7=`-iuay(vZxmTRLZh={3B)5eXF+hUNTo-$G1DBmB{LaWRH>O%Cf)hSd>A< zTfsOEfqJ!!GNQ61jCHvUkt_ST$GS`gyvePzU}@vf78YV={BjWu)Q? zE9fa*>H4Sir~S6ewX1UP;Zj5f{uDf`-lb^#6D=?NN-F4;8Dg%UmqMJ1KQNS6ElXhT!FbU%BdHGRH%-$GLVHm53pZi z>{)To5K4hapjSK35gCgBX8e<91hTjMRhoNd&h|NmWSVzpRnJMVR`(;h_bNaPR0aNl zh*Kx?&(qJmk%Syy(6`bqH?6+mRYS#tklZ_WoeUN_(kI6$$GpmprXzMcE4z%p+28xL z>Q+%4_a<^*OKbokA);!hF=1*0gsBwxD`@5x&I?Jnd++{6rQ0#T?#!AmhGdDvBJl>! zYt?oo1ijG65FBpGtHC|g!*N6N_A`Xn>PE{8MDv~<9b?PSQnn(Wx6tQN=zv05GXGZU z3rge@m0IBPxi_4B(2w1FQs9UBZ#tdDwRcXR(2uc-7IelL&A-RacVAxby9A!>eI4fQ zQX&VUAbf_n0ff@5-uVuQ1@M(#@)e$8v}dW=2K)Oe9E0Z2g{8n10;Lh*v1r(1ECCRH z;&-G${u#h>!3%8S1aj&r92EJ4PJv&LLr~v_DUsjqN`be@Az+{gs950@HlLmV;w1!q zn;%#9i3pZKlmgFq|5iEh86hAGSTTrc|4;dAb4tpAhQy~Y&?Ed&7<7z1(Z9cF;?W!w zr{s_$&E*qFLO|{rxSxAU3sA{}eL^QxT?loHN`X^fGY)|WG5liViWNB6K)cQ`I-rg& znSwH~45L6?39@kbpU)|6MvX=?jAZ6*8lnOP`3Y~}V8O)(N(5GxZ{TMEF|Z?>L3H~y zTXB;pUeJL6kX*B_H!|RwxTF-=dmc0m#>+rhqP^bE4dez2E0O2n+#~Ur{_59cXTUG7 z1@(!upo+&tYc>%1E4}Tn$1X!p5+)RhA~>o6h1SSY zyMZuaWVrFW_v`&-&V%zEP^+G@^|Ono?STO%y=1l>DG=BApnu7){4vY?p}hx5=kZWP z#UR9!*N1c9Zyh0&661VRBeSB~0 z$X(2*cMW<8dhY`d)WlH9p$sC3P#|$M`5d|81;8cDlpK1J6#Do_Z|y)8#J-v$w%qjE zC!W*)Z;ymWqtCK|GO!Cl^rtO={raS!Aa~jXue;&3FjZq>(i^xs+l<-xlOxq$p5+SJ zyi>*b!IQm<#0U415mJTNP;3S73= zPag4>95l969wF-K>Mvydd!(iNg#Ki4x3RBnwxzUJ{CM|Z+=@nSVZ~xHPRiIZStPvJ zElTSmU#zO|`Z1-M4maD_(2$)u;9iXvM|MS8m7}Ch7C{;n!JYbOo^#Tluh1>`;f^zt zU2B%Fa_66LnUT~KIH10QK_9!hKRS-xnmJgV8$UTdrl&&ak7uUuUk_q>B8`O!|8L<(f0A#LczWK%+#%IgS@)s`ypLkJ2drw zeySVo-7C4F(R(tZ8-m&&*bz;xZoBrS%jGkCL2Rzu?ok!X?%v*3?lD2mZt6gsEt<-N zXj&+0ay__aVq^L4eVm(z$MDV>VAG?J_|473{o<3E#F~uboyCJ(E77jvzLnCfT4&mnz zW0y@G4Mcr<`zRRo-R3z8AW1IIuSz9joUGE>f?wDQ2V_-j1q2;gMSg!oY8dmTf4;je zYiBC5_U2~uw7`g}1NM?RP^mH*?EFA({dl4sHs6wf#`R#myDs~=*@CoSm`jAq(PUJk z@M?s2HsO^c5~D62ewQ`wDK%LE#ejH8#Qtwhj&TVZao7EpMkd`ya}k|%&3!yOc;U!n z-}CvIFCSlJGfKg1W{vM;uzzO=YdO&v83;!*4FaXgdk_6{qPY9)V8Nz<;BQ{E>*`rjXbVW5@I0_62tAyEU-AZ$+ zKFG&JbB?o6i?14{J?MSJ%FBeC&hm|=s%}W;sT|vjr0RN1huKnfDl>9BbiH5Jw#|YA zV^Tp4OVXVeuee1~D;$S@d$+20fcwQC)c)2M3vo#Mqu))hm2XS2^4p|358%XGO6Db- z?GgFPA0yRkNI5=yN4s@!kGJFy(O&;D?eh7&Op7L*XCO)FYna$W+`A*%(X(%5%p{vd zU9>xA{%Md+25Xh^z%+Mwk`-l_)kjsiE#cfa-Eqfdwy#C)#nNnJ;yRuy($oE#Kjat$ zcn`~MjWou!@2oPI9I;$>FqmL4i50nD0x3rRJY%wAv*FETre00TO#dz4`(Dx#sXqus zCc~xp-c0bCh)pq86A%AGw@yPTkh*eMbEM(z`P}r643vG;&N`=4nHRFKqmlmpzFAR8 z^Eoaz4-6|LmK!(|OsX{EA6s$y5A^8&dS`wY$6=?vl|5ImhMUA)x77~LF$D$_NjUNh zk-<(TO5*Xx$u9ZJ*vkTZ>b#sT2I|r}rc_L$n9!XxwqDf;n>DAJ+=}NbqI3CCv_bOg z{=dGI;>UXFy6tj}uDdJf%_**TCii&9$LpR52n*+7^tK+<=W&MwZOuh2pbl3WW2MAq zg?Anx-cP<@%5D9#@JE>+A&UjJPtQBUNSAmZ@vDdAWClM)ytdQvo`LGseP5}Pc#kjZ zRePKIVRo-BFa(#y+k#tj*Y-ZvC;yJ75JJS7qWAZw#07g-)L!X))Ul5%6yZ5vO`}?P z&Dqr|6<@KS7I-RzHR}vkaN}mg`sD2=0t{N==zS})pk{G4+aNXHqP)z7gFBC2R|-d> zlkjk1ZHw)>PB+)Xs3GkLG!K~1uy3SZAdb??<^70NIe;LuJ;fSkfM?ocU0Mk+!Tc9}|*P(9qj^*j{CZ33(ywN69fAAjp! zPQ~1)d!3V+;Xk#$hz?Rtzh@jglk{il@?K)O*Q+&#woVCF`D!G!G-C2abdbbsgiT}7 zh?XiU;ZP-I)Sa_BxVDx6hLtig9D#iRUws<*(w+O}Gv?uke& zqlm*ich4=H+i!uG_>Wyhg8i|oT^0$xR%iJRfWpSl*&_rR{_0HL3^LjD|5hJpy7_-o zE$AK4B{2CY1w#M)GdrCYH|NE{IHaK|YT~^J0rjg~QsD4GvlmcT=mV%Q4EA8+#8u@? VL!|wOR^VH}q$T9UbDrwG{XaLw+CcyS literal 0 HcmV?d00001 diff --git a/doc/devel/UML/eltorito.violet b/doc/devel/UML/eltorito.violet new file mode 100644 index 0000000..b8a7d01 --- /dev/null +++ b/doc/devel/UML/eltorito.violet @@ -0,0 +1,552 @@ + + + + + + + + Volume + + + + + + 479.2699858975891 + 226.94112549695433 + + + + + + + + block : uint32_t + + + + + ElToritoCatalog + + + + + + 472.58578643762684 + 344.73506473629425 + + + + + + + + bootable : bool +type : enum +partition_type : enum +load_seg : uint16 +load_size : uint16 +patch_isolinux : bool +block: uint32_t + + + + + BootImage + + + + + + 470.4142135623731 + 487.3919189857866 + + + + + + + + In a future we can support several boot +images + + + + + + 251.63542622468316 + 429.69343417595167 + + + + + + + + img : boolean + + + + + BootNode + + + + + + 193.07106781186545 + 334.49242404917493 + + + + + + + + + + TreeNode + + + + + + iso_tree + + + + + 180.0 + 40.0 + + + + + + + + 193.0 + 69.0 + + + + + + + + The img field is an implementation detail, used +to distinguish between the catalog node and the image +node. This is needed when the image is written. + + + + + + 57.81118318204312 + 584.0458146424488 + + + + + + + + The support for growing or modify El-Torito images +is really hard to implement. The reason: when the +image is hidden, we don't know its size, so the best we +can do is just refer to the old image. When modify, all +we can do may be wrong. + + + + + + 748.978906441031 + 574.8973495522459 + + + + + + + + The block in both Catalog and BootImage is needed +for multissession images + + + + + + 629.3242465083424 + 441.1316647878586 + + + + + + + + File + + + + + + 188.09040379562163 + 172.5340546095176 + + + + + + + + CatalogStream + + + + + + 851.105100475371 + 283.5127233261827 + + + + + + + + FileStream + + + + + + 743.4055403867466 + 284.4253525880894 + + + + + + + + TransformStream + + + + + + 958.5987801403015 + 279.8322618091961 + + + + + + + + «interface» +Stream + + + + + + 847.6728065449973 + 157.05765855361264 + + + + + + + + IsoLinuxPatch + + + + + + 968.73629022557 + 384.6660889654818 + + + + + + + + Generates the content of the catalog on-the-fly + + + + + + 517.6021638285529 + 107.48023074035522 + + + + + + + + To apply the needed patch to isolinux +images + + + + + + 923.4814562296309 + 509.1168824543143 + + + + + + + + + + + + + 1 image + + + + + + + + + + + + + + + + + + + + + + + 0..1 boot_cat + + + + + + + + + + + + + + + 0..1 node + + + + + + + + + + + + + + + + + + + + 0..1 node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/eltorito.violet.png b/doc/devel/UML/eltorito.violet.png new file mode 100644 index 0000000000000000000000000000000000000000..adc195c25ed2dab2a308a7c84e068f0b5ba26ee5 GIT binary patch literal 24861 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 literal 0 HcmV?d00001 diff --git a/doc/devel/UML/iso_tree.violet b/doc/devel/UML/iso_tree.violet new file mode 100644 index 0000000..a3fa226 --- /dev/null +++ b/doc/devel/UML/iso_tree.violet @@ -0,0 +1,748 @@ + + + + + + + + volume_id : char* +publisher_id : char* +data_preparer_id : char* +system_id : char* +application_id : char* +copyright_file_id : char* +abstract_file_id : char* +biblio_file_id : char* + + + + + Volume + + + + + + 1160.4799402311673 + 240.649943764645 + + + + + + + + sort_weight : int +block : uint32_t + + + + + File + + + + + + 687.5479565719912 + 269.2931470368318 + + + + + + + + name : char * +attribs : struct stat +hidden : enum + + + + + TreeNode + + + + + + 706.83671056434 + 108.4726745515399 + + + + + + + + add(XXX) +remove(Node) +children() + + + + + Directory + + + + + + 986.1687535943008 + 267.29314703683184 + + + + + + + + dest : char* + + + + + Symlink + + + + + + 571.9364350336367 + 273.31078127658077 + + + + + + + + Special + + + + + + 813.0651280884073 + 272.20749521231266 + + + + + + + + name : char* + + + + + <<static>>new(id) +<<static>>read(src, opts) +create() +grow() + + + + + Image + + + + + + 1149.1980515339465 + 455.5218613006981 + + + + + + + + In addition to the dest as a path, it could +be a good idea to have a ref to tree node. +That way we can compute the dest on creation +time, and thus links to files on image are also valid +after moving or renaming those files + + + + + + 322.02220861890066 + 362.2044136147912 + + + + + + + + Image is a context for the creation of images. Its "static" +methods, new() and read() are used to create a new +image context, either from scratch or from an existing +image (for example, a ms disc). The methods create() and +grow() return an BurnSource suitable for libburn. +create() writes a full image, grow() only add to the image +the new files, thus it is suitable for a new session + + + + + + + 1212.7956394939486 + 697.0920982847697 + + + + + + + + Ecma119Source + + + + + + 1423.5617211564486 + 483.61244144432396 + + + + + + + + + + «interface» +BurnSource + + + + + + Libburn + + + + + 1420.0 + 280.0 + + + + + + + + 1431.4906533445824 + 311.35760744838467 + + + + + + + + Class diagram for the public tree. Note that getters and setters are not shown, +to improve readability. Note also that not all the attributes will have public getters +or/and setters. +El-Torito related information is shown in another diagram. +We don't show the several functions in Dir to manage the tree. + + + + + + 290.59037712396525 + 9.859316379054544 + + + + + + + + «interface» +DataSource + + + + + + 1192.781692587207 + 608.8954677283948 + + + + + + + + + + «interface» +Filters + + + + + + filters + + + + + 260.0 + 710.0 + + + + + + + + 265.45434264405947 + 743.9994422711634 + + + + + + + + TransformStream + + + + + + 486.9335577265969 + 640.636302316303 + + + + + + + + CutOutStream + + + + + + 555.9916340674516 + 750.220757440409 + + + + + + + + get_size() +read() +open() +close() +is_repeatable() + + + + + «interface» +Stream + + + + + + 688.5487814157467 + 437.25152600545294 + + + + + + + + FdStream + + + + + + 680.6673668471356 + 637.245696021424 + + + + + + + + FileStream + + + + + + 828.9404615480411 + 642.40096597045 + + + + + + + + FilteredStream + + + + + + 428.449880813367 + 747.5389646099015 + + + + + + + + «interface» +SourceFile + + + + + + 1000.6667341519202 + 639.0812755928229 + + + + + + + + For files, we need to know whethe they come +from a previous session. That's the purpose of +the block field + + + + + + 818.829652614022 + 414.36457377531684 + + + + + + + + + + + + + 1 volume + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {create} + + + + + + + + + + + + 0..1 + + + + + + + + + + + + + + + + + + * children + + + + + + + + + + + + + + + + + + 1 root + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 parent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 src + + + + + + + + + + + + diff --git a/doc/devel/UML/iso_tree.violet.png b/doc/devel/UML/iso_tree.violet.png new file mode 100644 index 0000000000000000000000000000000000000000..d445b4fe0232237f34f353ecb3015f3e60866161 GIT binary patch literal 38189 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 literal 0 HcmV?d00001 diff --git a/doc/devel/UML/nglibisofs.violet b/doc/devel/UML/nglibisofs.violet new file mode 100644 index 0000000..fd6a247 --- /dev/null +++ b/doc/devel/UML/nglibisofs.violet @@ -0,0 +1,1059 @@ + + + + + + + + addFilter(Filter) + + + + + Image + + + + + + 1110.8240579870107 + 412.38305701571016 + + + + + + + + Volume + + + + + + 746.3823937606093 + 414.32909259072375 + + + + + + + + TreeNode + + + + + + 625.8940609153292 + 195.78347904495376 + + + + + + + + File + + + + + + 497.8940609153292 + 296.78347904495376 + + + + + + + + dest + + + + + Symlink + + + + + + 620.8940609153292 + 296.78347904495376 + + + + + + + + Directory + + + + + + 754.8940609153292 + 301.78347904495376 + + + + + + + + «interface» + +FileSource + + + + + + 567.7128002861876 + 674.7337520453865 + + + + + + + + FilterFileSource + + + + + + 404.71280028618764 + 774.7337520453866 + + + + + + + + «interface» +Filter + + + + + + 254.78439895092293 + 770.0234642237775 + + + + + + + + LocalFileSource + + + + + + 556.36965453568 + 774.2484706711477 + + + + + + + + PreviousImageSource + + + + + + 709.0702627569951 + 767.6450499937722 + + + + + + + + ExternalAppFilter + + + + + + 110.5229299160843 + 923.5005293319075 + + + + + + + + EncryptionFilter + + + + + + 239.49694258624206 + 916.3652866418913 + + + + + + + + CompressionFilter + + + + + + 371.4989323515441 + 917.5544937568939 + + + + + + + + SplittedFile + + + + + + 287.78812183065844 + 413.84407602094683 + + + + + + + + Special + + + + + + 386.0027189390628 + 302.28361365806154 + + + + + + + + DataSource + + + + + + 731.4111571155465 + 901.4839567978983 + + + + + + + + «interface» +BurnSource + + + + + + 1335.6119487863941 + 398.7730655710078 + + + + + + + + ... + + + + + + 1477.8641028983907 + 783.298732945339 + + + + + + + + PVDWriter + + + + + + 1198.964607961779 + 781.9555871948312 + + + + + + + + WriterState + + + + + + 1336.8762962072105 + 661.712946507712 + + + + + + + + + + Libburn + + + + + 1330.0 + 370.0 + + + + + + + + Ecma119Image + + + + + + 1095.4640655004926 + 540.6103494032706 + + + + + + + + Ecma119Source + + + + + + 1326.9200188086058 + 535.6052987693873 + + + + + + + + Ecma119Node + + + + + + 881.981844994835 + 577.6636135370297 + + + + + + + + DirectoryInfoWriter + + + + + + 1316.10674358551 + 783.9555871948312 + + + + + + + + LocalFile + + + + + + 396.0059692959062 + 407.89804044593336 + + + + + + + + FilteredFile + + + + + + 505.41302387615644 + 412.6548689059442 + + + + + + + + PrevImgFile + + + + + + 617.1984926864122 + 410.2764546759387 + + + + + + + + Ecma119File + + + + + + 743.3186217548513 + 674.795410727119 + + + + + + + + Ecma119Symlink + + + + + + 874.2922359743702 + 669.9419255760395 + + + + + + + + Ecma119Dir + + + + + + 1037.9535936976897 + 672.3203398060449 + + + + + + + + FileSourceRegistry + + + + + + 550.129075763134 + 551.543289325507 + + + + + + + + Base object for all interaction with user. +Represents a context for image creation +and manipulation. + + + + + + 1042.275395468971 + 308.29855659733465 + + + + + + + + Registry to ensure the same file is only +written once to the image + + + + + + 271.5290039756343 + 521.8448045156721 + + + + + + + + The context data for image burn sources, contains +references to the tree, creation options... + + + + + + 770.7463914933367 + 497.80317395532944 + + + + + + + + A filter to be applied to file contents. A single filter +can be used to several files. + + + + + + 151.32085117392114 + 668.9230150024738 + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + * children + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 root + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [create] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/stream.violet b/doc/devel/UML/stream.violet new file mode 100644 index 0000000..d488940 --- /dev/null +++ b/doc/devel/UML/stream.violet @@ -0,0 +1,492 @@ + + + + + + + + TransformStream + + + + + + 374.71280028618764 + 246.7337520453866 + + + + + + + + get_size() +read() +open() +close() +is_repeatable() + + + + + «interface» +Stream + + + + + + 576.3280239753375 + 43.34897573453627 + + + + + + + + FileStream + + + + + + 741.9465965652432 + 246.8166228690261 + + + + + + + + + + CompressionFilter + + + + + + + + + EncryptionFilter + + + + + + + + + ExtAppFilter + + + + + + + + + filter(in, out) + + + + + «interface» +Filter + + + + + + Filters + + + + + 270.0 + 480.0 + + + + + + + + A Stream to read data from an abstract +file represented by a SourceFile + + + + + + 781.6101730552666 + 137.2161620284267 + + + + + + + + A stream to get data from an arbitrary file +descritor. size must be know in advance. + + + + + + 580.8730162779191 + 392.3137084989848 + + + + + + + + fd : int +size : off_t + + + + + FdStream + + + + + + 565.61818228198 + 253.24264068711926 + + + + + + + + 281.2426406871193 + 620.6274169979695 + + + + + + + + 429.51546936508925 + 624.9910026589843 + + + + + + + + 568.2426406871186 + 624.6274169979695 + + + + + + + + A Filter do a tranformation on a stream of data. +The main difference with TransformSources is that +a Filter can be applied to several sources. +NOTES: +- filter() method still to define +- A filter_changes_size() method can be useful + + + + + + + 724.6274169979696 + 510.3015151901651 + + + + + + + + FilteredStream + + + + + + 439.0 + 357.0 + + + + + + + + size : off_t +lba: off_t + + + + + CutOutStream + + + + + + 321.0 + 358.0 + + + + + + + + This can be implemented as a Filter, but +it has no sense to have the same cut out +filter to several sources, so this is a better +place. + + + + + + 67.0 + 276.0 + + + + + + + + A stream that applies some transformation +to the contents of another stream. + + + + + + 122.0 + 183.0 + + + + + + + + 437.57046683437824 + 509.23933115391503 + + + + + + + + «interface» +SourceFile + + + + + + 920.6530291048848 + 248.90158697766475 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/UML/stream.violet.png b/doc/devel/UML/stream.violet.png new file mode 100644 index 0000000000000000000000000000000000000000..f5215a50ae42b31bb7da329d10df18921a868c7c GIT binary patch literal 21694 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/devel/cookbook/ISO 9660-1999 b/doc/devel/cookbook/ISO 9660-1999 new file mode 100644 index 0000000..4b813b7 --- /dev/null +++ b/doc/devel/cookbook/ISO 9660-1999 @@ -0,0 +1,119 @@ +=============================================================================== + ISO/IEC 9660:1999 Cookbook +=============================================================================== + +Creation date: 2008-Jan-14 +Author: Vreixo Formoso +_______________________________________________________________________________ + +Contents: +--------- + +1. References +2. General +3. Features +4. Implementation +5. Known implementation bugs and specification ambiguities/problems + + +------------------------------------------------------------------------------- +1. References: + +ISO/IEC DIS 9660:1999(E) "Information processing. Volume and file structure of + CD­-ROM for Information Interchange" + + +------------------------------------------------------------------------------- +2. General + +ISO 9660:1999, also known as ISO-9660 version 2 is an update of the old +ISO 9660:1988 standard for writing data images for CD. + +In the same way Joliet does, it is based on a Secondary Volume Descriptor (that +is called Enhanced Volume Descriptor), that provides a second tree where the +new file information is recorded. + +------------------------------------------------------------------------------- +3. Features + +It makes some improvements with respect to ECMA-119, mainly related to relax +the constraints imposed by its predecessor. + +- It removes the limit to the deep of the directory hierarchy (6.8.2.1). +However, it still keep a limit to the path length, of 255 characters as in +ECMA-119. + +- File names don't need the version number (;1) anymore, and the "." and ";", +used as SEPARATORS for extension and version number, have no special meaning +now. + +- The file name max length is incremented to 207 bytes. + +- The file name is not restricted to d-characters. + +------------------------------------------------------------------------------- +4. Implementation + +ISO 9660:1999 is very similar to old ISO 9660:1988 (ECMA-119). It needs two +tree hierarchies: one, identified by the Primary Volume Descriptor, is recorded +in the same way that an ECMA-119 structure. + +The second structure is identified by a Enhanced Volume Descriptor (8.5). The +structure is exactly like defined in ECMA-119, with the exceptions named above. + +Thus, to write an ISO 9660:1999: + +- First 16 blocks are set to 0. +- Block 16 identifies a PVD (8.4), associated with a directory structure +written following ECMA-119. +- It is needed a Enhanced Volume descriptor to describe the additional +structure. It is much like a SVD, with version number set to 2 to identify +this new version. +- We can also write boot records (El-Torito) and additional SVD (Joliet). +- We write a Volume Descriptor Set Terminator (8.3) +- We write directory structure and path tables (L and M) for both ECMA-119 +tree and enhanced tree. Path table record and directory record format is +the same in both structures. However, ECMA-119 is constrained by the usual +restrictions. +- And write the contents of the files. + +Interchange levels 1, 2 and 3 are also defined. For PVD tree, they have the +same meaning as in ECMA-119. For EVD tree, in levels 1 and 2 files are +restricted to one file section (i.e., 4 GB filesize limit). In level 3 we can +have more than one section per file. Level 1 does not impose other +restrictions than that in the EVD tree. + +It seems that both El-Torito and Joliet can coexist in a ISO 9660:1999 image. +However, Joliet has no utility at all in this kind of images, as it has no +benefit over ISO 9660:1999, and it is more restrictive in filename length. + +------------------------------------------------------------------------------- +5. Known implementation bugs and specification ambiguities/problems + +- While the specification clearly states that the tree speficied by the Primary +Volume Descriptor should remain compatible with ISO-9660 (ECMA-119), i.e., it +should be constrained by ECMA-119 restrictions, some image generation +applications out there just make both Primary and Enhanced Volume Descriptors +to point to the same directory structure. That is a specification violation, as +for a) the directory hierarchy specified in the Primary Volume Descriptor +doesn't follow the restrictions specified in the specs, and b) the same +directories are part of two different hiearchies (6.8.3 "A directory shall not +be a part of more than one Directory Hierarchy."). +Thus, we should keep two trees as we do with Joliet. Or are there strong +reasons against this? + +- It's not very clear what characters are allowed for files and dir names. For +the tree identified in the Enhanced Volume Descriptor, it seems that a +"sequence of characters rather than d-characters or d1-characters" is allowed. +It also seems that the charset is determined by the escape sequence in the +EVD. Anyway, leaving escape sequence to 0 and use any user-specified sequence +(such as UTF-8) seems a good solution and is what many other applications do. +Linux correctly mounts the images in this case. + +- It is not clear if RR extensions are allowed in the tree identified by the +Enhanced Volume Descriptor. However, it seems not a good idea. With 207 bytes +filenames and XA extensions, there is no place for RR entries in the directory +records of the enhanced tree. In my opinion, RR extension should be attached to +the ECMA-119 tree that must also be written to image. + + diff --git a/doc/doxygen.conf.in b/doc/doxygen.conf.in new file mode 100644 index 0000000..fe1864f --- /dev/null +++ b/doc/doxygen.conf.in @@ -0,0 +1,1298 @@ +# Doxyfile 1.5.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file that +# follow. The default is UTF-8 which is also the encoding used for all text before +# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into +# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list +# of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PACKAGE_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, +# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, +# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, +# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = @top_srcdir@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = YES + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be extracted +# and appear in the documentation as a namespace called 'anonymous_namespace{file}', +# where file will be replaced with the base name of the file that contains the anonymous +# namespace. By default anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text " + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = libisofs \ + doc + +# This tag can be used to specify the character encoding of the source files that +# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default +# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. +# See http://www.gnu.org/software/libiconv for the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = libisofs.h \ + comments + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the output. +# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, +# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = test + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH +# then you must also enable this option. If you don't then doxygen will produce +# a warning and turn it on anyway + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = OB \ + OTK \ + _ + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = doc/html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 200 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = DOXYGEN + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to +# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to +# specify the directory where the mscgen tool resides. If left empty the tool is assumed to +# be found in the default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = NO + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the number +# of direct children of the root node in a graph is already larger than +# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/libisofs-1.pc.in b/libisofs-1.pc.in new file mode 100644 index 0000000..4cf18c1 --- /dev/null +++ b/libisofs-1.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libisofs +Description: ISO9660 filesystem creation library +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lisofs +Cflags: -I${includedir}/libisofs diff --git a/libisofs/buffer.c b/libisofs/buffer.c new file mode 100644 index 0000000..c59bad1 --- /dev/null +++ b/libisofs/buffer.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * Synchronized ring buffer, works with a writer thread and a read thread. + * + * TODO #00010 : optimize ring buffer + * - write/read at the end of buffer requires a second mutex_lock, even if + * there's enought space/data at the beginning + * - pre-buffer for writes < BLOCK_SIZE + * + */ + +#include "buffer.h" +#include "libburn/libburn.h" +#include "ecma119.h" + +#include +#include + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +struct iso_ring_buffer +{ + uint8_t *buf; + + /* + * Max number of bytes in buffer + */ + size_t cap; + + /* + * Number of bytes available. + */ + size_t size; + + /* position for reading and writing, offset from buf */ + size_t rpos; + size_t wpos; + + /* + * flags to report if read or writer threads ends execution + * 0 not finished, 1 finished ok, 2 finish with error + */ + unsigned int rend :2; + unsigned int wend :2; + + /* just for statistical purposes */ + unsigned int times_full; + unsigned int times_empty; + + pthread_mutex_t mutex; + pthread_cond_t empty; + pthread_cond_t full; +}; + +/** + * Create a new buffer. + * + * The created buffer should be freed with iso_ring_buffer_free() + * + * @param size + * Number of blocks in buffer. You should supply a number >= 32, otherwise + * size will be ignored and 32 will be used by default, which leads to a + * 64 KiB buffer. + * @return + * 1 success, < 0 error + */ +int iso_ring_buffer_new(size_t size, IsoRingBuffer **rbuf) +{ + IsoRingBuffer *buffer; + + if (rbuf == NULL) { + return ISO_NULL_POINTER; + } + + buffer = malloc(sizeof(IsoRingBuffer)); + if (buffer == NULL) { + return ISO_OUT_OF_MEM; + } + + buffer->cap = (size > 32 ? size : 32) * BLOCK_SIZE; + buffer->buf = malloc(buffer->cap); + if (buffer->buf == NULL) { + free(buffer); + return ISO_OUT_OF_MEM; + } + + buffer->size = 0; + buffer->wpos = 0; + buffer->rpos = 0; + + buffer->times_full = 0; + buffer->times_empty = 0; + + buffer->rend = buffer->wend = 0; + + /* init mutex and waiting queues */ + pthread_mutex_init(&buffer->mutex, NULL); + pthread_cond_init(&buffer->empty, NULL); + pthread_cond_init(&buffer->full, NULL); + + *rbuf = buffer; + return ISO_SUCCESS; +} + +void iso_ring_buffer_free(IsoRingBuffer *buf) +{ + if (buf == NULL) { + return; + } + free(buf->buf); + pthread_mutex_destroy(&buf->mutex); + pthread_cond_destroy(&buf->empty); + pthread_cond_destroy(&buf->full); + free(buf); +} + +/** + * Write count bytes into buffer. It blocks until all bytes where written or + * reader close the buffer. + * + * @param buf + * the buffer + * @param data + * pointer to a memory region of at least coun bytes, from which data + * will be read. + * @param + * Number of bytes to write + * @return + * 1 succes, 0 read finished, < 0 error + */ +int iso_ring_buffer_write(IsoRingBuffer *buf, uint8_t *data, size_t count) +{ + size_t len; + int bytes_write = 0; + + if (buf == NULL || data == NULL) { + return ISO_NULL_POINTER; + } + + while (bytes_write < count) { + + pthread_mutex_lock(&buf->mutex); + + while (buf->size == buf->cap) { + + /* + * Note. There's only a writer, so we have no race conditions. + * Thus, the while(buf->size == buf->cap) is used here + * only to propertly detect the reader has been cancelled + */ + + if (buf->rend) { + /* the read procces has been finished */ + pthread_mutex_unlock(&buf->mutex); + return 0; + } + buf->times_full++; + /* wait until space available */ + pthread_cond_wait(&buf->full, &buf->mutex); + } + + len = MIN(count - bytes_write, buf->cap - buf->size); + if (buf->wpos + len > buf->cap) { + len = buf->cap - buf->wpos; + } + memcpy(buf->buf + buf->wpos, data + bytes_write, len); + buf->wpos = (buf->wpos + len) % (buf->cap); + bytes_write += len; + buf->size += len; + + /* wake up reader */ + pthread_cond_signal(&buf->empty); + pthread_mutex_unlock(&buf->mutex); + } + return ISO_SUCCESS; +} + +/** + * Read count bytes from the buffer into dest. It blocks until the desired + * bytes has been read. If the writer finishes before outputting enought + * bytes, 0 (EOF) is returned, the number of bytes already read remains + * unknown. + * + * @return + * 1 success, 0 EOF, < 0 error + */ +int iso_ring_buffer_read(IsoRingBuffer *buf, uint8_t *dest, size_t count) +{ + size_t len; + int bytes_read = 0; + + if (buf == NULL || dest == NULL) { + return ISO_NULL_POINTER; + } + + while (bytes_read < count) { + pthread_mutex_lock(&buf->mutex); + + while (buf->size == 0) { + /* + * Note. There's only a reader, so we have no race conditions. + * Thus, the while(buf->size == 0) is used here just to ensure + * a reader detects the EOF propertly if the writer has been + * canceled while the reader was waiting + */ + + if (buf->wend) { + /* the writer procces has been finished */ + pthread_mutex_unlock(&buf->mutex); + return 0; /* EOF */ + } + buf->times_empty++; + /* wait until data available */ + pthread_cond_wait(&buf->empty, &buf->mutex); + } + + len = MIN(count - bytes_read, buf->size); + if (buf->rpos + len > buf->cap) { + len = buf->cap - buf->rpos; + } + memcpy(dest + bytes_read, buf->buf + buf->rpos, len); + buf->rpos = (buf->rpos + len) % (buf->cap); + bytes_read += len; + buf->size -= len; + + /* wake up the writer */ + pthread_cond_signal(&buf->full); + pthread_mutex_unlock(&buf->mutex); + } + return ISO_SUCCESS; +} + +void iso_ring_buffer_writer_close(IsoRingBuffer *buf, int error) +{ + pthread_mutex_lock(&buf->mutex); + buf->wend = error ? 2 : 1; + + /* ensure no reader is waiting */ + pthread_cond_signal(&buf->empty); + pthread_mutex_unlock(&buf->mutex); +} + +void iso_ring_buffer_reader_close(IsoRingBuffer *buf, int error) +{ + pthread_mutex_lock(&buf->mutex); + + if (buf->rend) { + /* reader already closed */ + pthread_mutex_unlock(&buf->mutex); + return; + } + + buf->rend = error ? 2 : 1; + + /* ensure no writer is waiting */ + pthread_cond_signal(&buf->full); + pthread_mutex_unlock(&buf->mutex); +} + +/** + * Get the times the buffer was full. + */ +unsigned int iso_ring_buffer_get_times_full(IsoRingBuffer *buf) +{ + return buf->times_full; +} + +/** + * Get the times the buffer was empty. + */ +unsigned int iso_ring_buffer_get_times_empty(IsoRingBuffer *buf) +{ + return buf->times_empty; +} + + +/** + * Get the status of the buffer used by a burn_source. + * + * @param b + * A burn_source previously obtained with + * iso_image_create_burn_source(). + * @param size + * Will be filled with the total size of the buffer, in bytes + * @param free_bytes + * Will be filled with the bytes currently available in buffer + * @return + * < 0 error, > 0 state: + * 1="active" : input and consumption are active + * 2="ending" : input has ended without error + * 3="failing" : input had error and ended, + * 5="abandoned" : consumption has ended prematurely + * 6="ended" : consumption has ended without input error + * 7="aborted" : consumption has ended after input error + */ +int iso_ring_buffer_get_status(struct burn_source *b, size_t *size, + size_t *free_bytes) +{ + int ret; + IsoRingBuffer *buf; + if (b == NULL) { + return ISO_NULL_POINTER; + } + buf = ((Ecma119Image*)(b->data))->buffer; + + /* get mutex */ + pthread_mutex_lock(&buf->mutex); + if (size) { + *size = buf->cap; + } + if (free_bytes) { + *free_bytes = buf->cap - buf->size; + } + + ret = (buf->rend ? 4 : 0) + (buf->wend + 1); + + pthread_mutex_unlock(&buf->mutex); + return ret; +} diff --git a/libisofs/buffer.h b/libisofs/buffer.h new file mode 100644 index 0000000..c98e06c --- /dev/null +++ b/libisofs/buffer.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_BUFFER_H_ +#define LIBISO_BUFFER_H_ + +#include +#include + +#define BLOCK_SIZE 2048 + +typedef struct iso_ring_buffer IsoRingBuffer; + +/** + * Create a new buffer. + * + * The created buffer should be freed with iso_ring_buffer_free() + * + * @param size + * Number of blocks in buffer. You should supply a number >= 32, otherwise + * size will be ignored and 32 will be used by default, which leads to a + * 64 KiB buffer. + * @return + * 1 success, < 0 error + */ +int iso_ring_buffer_new(size_t size, IsoRingBuffer **rbuf); + +/** + * Free a given buffer + */ +void iso_ring_buffer_free(IsoRingBuffer *buf); + +/** + * Write count bytes into buffer. It blocks until all bytes where written or + * reader close the buffer. + * + * @param buf + * the buffer + * @param data + * pointer to a memory region of at least coun bytes, from which data + * will be read. + * @param + * Number of bytes to write + * @return + * 1 succes, 0 read finished, < 0 error + */ +int iso_ring_buffer_write(IsoRingBuffer *buf, uint8_t *data, size_t count); + +/** + * Read count bytes from the buffer into dest. It blocks until the desired + * bytes has been read. If the writer finishes before outputting enought + * bytes, 0 (EOF) is returned, the number of bytes already read remains + * unknown. + * + * @return + * 1 success, 0 EOF, < 0 error + */ +int iso_ring_buffer_read(IsoRingBuffer *buf, uint8_t *dest, size_t count); + +/** + * Close the buffer (to be called by the writer). + * You have to explicity close the buffer when you don't have more data to + * write, otherwise reader will be waiting forever. + * + * @param error + * Writer has finished prematurely due to an error + */ +void iso_ring_buffer_writer_close(IsoRingBuffer *buf, int error); + +/** + * Close the buffer (to be called by the reader). + * If for any reason you don't want to read more data, you need to call this + * to let the writer thread finish. + * + * @param error + * Reader has finished prematurely due to an error + */ +void iso_ring_buffer_reader_close(IsoRingBuffer *buf, int error); + +/** + * Get the times the buffer was full. + */ +unsigned int iso_ring_buffer_get_times_full(IsoRingBuffer *buf); + +/** + * Get the times the buffer was empty. + */ +unsigned int iso_ring_buffer_get_times_empty(IsoRingBuffer *buf); + +#endif /*LIBISO_BUFFER_H_*/ diff --git a/libisofs/builder.c b/libisofs/builder.c new file mode 100644 index 0000000..9dbea87 --- /dev/null +++ b/libisofs/builder.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "builder.h" +#include "node.h" +#include "fsource.h" + +#include +#include +#include + +void iso_node_builder_ref(IsoNodeBuilder *builder) +{ + ++builder->refcount; +} + +void iso_node_builder_unref(IsoNodeBuilder *builder) +{ + if (--builder->refcount == 0) { + /* free private data */ + builder->free(builder); + free(builder); + } +} + +static +int default_create_file(IsoNodeBuilder *builder, IsoImage *image, + IsoFileSource *src, IsoFile **file) +{ + int ret; + struct stat info; + IsoStream *stream; + IsoFile *node; + char *name; + + if (builder == NULL || src == NULL || file == NULL) { + return ISO_NULL_POINTER; + } + + ret = iso_file_source_stat(src, &info); + if (ret < 0) { + return ret; + } + + /* this will fail if src is a dir, is not accessible... */ + ret = iso_file_source_stream_new(src, &stream); + if (ret < 0) { + return ret; + } + + name = iso_file_source_get_name(src); + ret = iso_node_new_file(name, stream, &node); + if (ret < 0) { + /* the stream has taken our ref to src, so we need to add one */ + iso_file_source_ref(src); + iso_stream_unref(stream); + free(name); + return ret; + } + + /* fill node fields */ + iso_node_set_permissions((IsoNode*)node, info.st_mode); + iso_node_set_uid((IsoNode*)node, info.st_uid); + iso_node_set_gid((IsoNode*)node, info.st_gid); + iso_node_set_atime((IsoNode*)node, info.st_atime); + iso_node_set_mtime((IsoNode*)node, info.st_mtime); + iso_node_set_ctime((IsoNode*)node, info.st_ctime); + iso_node_set_uid((IsoNode*)node, info.st_uid); + + *file = node; + return ISO_SUCCESS; +} + +static +int default_create_node(IsoNodeBuilder *builder, IsoImage *image, + IsoFileSource *src, IsoNode **node) +{ + int ret; + struct stat info; + IsoNode *new; + char *name; + + if (builder == NULL || src == NULL || node == NULL) { + return ISO_NULL_POINTER; + } + + /* get info about source */ + if (iso_tree_get_follow_symlinks(image)) { + ret = iso_file_source_stat(src, &info); + } else { + ret = iso_file_source_lstat(src, &info); + } + if (ret < 0) { + return ret; + } + + name = iso_file_source_get_name(src); + new = NULL; + + switch (info.st_mode & S_IFMT) { + case S_IFREG: + { + /* source is a regular file */ + IsoStream *stream; + IsoFile *file; + ret = iso_file_source_stream_new(src, &stream); + if (ret < 0) { + break; + } + /* take a ref to the src, as stream has taken our ref */ + iso_file_source_ref(src); + + /* create the file */ + ret = iso_node_new_file(name, stream, &file); + if (ret < 0) { + iso_stream_unref(stream); + } + new = (IsoNode*) file; + } + break; + case S_IFDIR: + { + /* source is a directory */ + IsoDir *dir; + ret = iso_node_new_dir(name, &dir); + new = (IsoNode*)dir; + } + break; + case S_IFLNK: + { + /* source is a symbolic link */ + char dest[PATH_MAX]; + IsoSymlink *link; + + ret = iso_file_source_readlink(src, dest, PATH_MAX); + if (ret < 0) { + break; + } + ret = iso_node_new_symlink(name, strdup(dest), &link); + new = (IsoNode*) link; + } + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + { + /* source is an special file */ + IsoSpecial *special; + ret = iso_node_new_special(name, info.st_mode, info.st_rdev, + &special); + new = (IsoNode*) special; + } + break; + } + + if (ret < 0) { + free(name); + return ret; + } + + /* fill fields */ + iso_node_set_permissions(new, info.st_mode); + iso_node_set_uid(new, info.st_uid); + iso_node_set_gid(new, info.st_gid); + iso_node_set_atime(new, info.st_atime); + iso_node_set_mtime(new, info.st_mtime); + iso_node_set_ctime(new, info.st_ctime); + iso_node_set_uid(new, info.st_uid); + + *node = new; + return ISO_SUCCESS; +} + +static +void default_free(IsoNodeBuilder *builder) +{ + return; +} + +int iso_node_basic_builder_new(IsoNodeBuilder **builder) +{ + IsoNodeBuilder *b; + + if (builder == NULL) { + return ISO_NULL_POINTER; + } + + b = malloc(sizeof(IsoNodeBuilder)); + if (b == NULL) { + return ISO_OUT_OF_MEM; + } + + b->refcount = 1; + b->create_file_data = NULL; + b->create_node_data = NULL; + b->create_file = default_create_file; + b->create_node = default_create_node; + b->free = default_free; + + *builder = b; + return ISO_SUCCESS; +} diff --git a/libisofs/builder.h b/libisofs/builder.h new file mode 100644 index 0000000..e0d3ff1 --- /dev/null +++ b/libisofs/builder.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_BUILDER_H_ +#define LIBISO_BUILDER_H_ + +/* + * Definitions for IsoNode builders. + */ + +/* + * Some functions here will be moved to libisofs.h when we expose + * Builder. + */ + +#include "libisofs.h" +#include "fsource.h" + +typedef struct Iso_Node_Builder IsoNodeBuilder; + +struct Iso_Node_Builder +{ + + /** + * Create a new IsoFile from an IsoFileSource. Name, permissions + * and other attributes are taken from src, but a regular file will + * always be created, even if src is another kind of file. + * + * In that case, if the implementation can't do the conversion, it + * should fail propertly. + * + * On sucess, the ref. to src will be owned by file, so you musn't + * unref it. + * + * @return + * 1 on success, < 0 on error + */ + int (*create_file)(IsoNodeBuilder *builder, IsoImage *image, + IsoFileSource *src, IsoFile **file); + + /** + * Create a new IsoNode from a IsoFileSource. The type of the node to be + * created is determined from the type of the file source. Name, + * permissions and other attributes are taken from source file. + * + * Note that the src is never unref, so you need to free it. + * + * @return + * 1 on success, < 0 on error + */ + int (*create_node)(IsoNodeBuilder *builder, IsoImage *image, + IsoFileSource *src, IsoNode **node); + + /** + * Free implementation specific data. Should never be called by user. + * Use iso_node_builder_unref() instead. + */ + void (*free)(IsoNodeBuilder *builder); + + int refcount; + void *create_file_data; + void *create_node_data; +}; + +void iso_node_builder_ref(IsoNodeBuilder *builder); +void iso_node_builder_unref(IsoNodeBuilder *builder); + +/** + * Create a new basic builder ... + * + * @return + * 1 success, < 0 error + */ +int iso_node_basic_builder_new(IsoNodeBuilder **builder); + +#endif /*LIBISO_BUILDER_H_*/ diff --git a/libisofs/data_source.c b/libisofs/data_source.c new file mode 100644 index 0000000..98515c6 --- /dev/null +++ b/libisofs/data_source.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +/** + * Private data for File IsoDataSource + */ +struct file_data_src +{ + char *path; + int fd; +}; + +/** + * Increments the reference counting of the given IsoDataSource. + */ +void iso_data_source_ref(IsoDataSource *src) +{ + src->refcount++; +} + +/** + * Decrements the reference counting of the given IsoDataSource, freeing it + * if refcount reach 0. + */ +void iso_data_source_unref(IsoDataSource *src) +{ + if (--src->refcount == 0) { + src->free_data(src); + free(src); + } +} + +static +int ds_open(IsoDataSource *src) +{ + int fd; + struct file_data_src *data; + + if (src == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (struct file_data_src*) src->data; + if (data->fd != -1) { + return ISO_FILE_ALREADY_OPENNED; + } + + fd = open(data->path, O_RDONLY); + if (fd == -1) { + return ISO_FILE_ERROR; + } + + data->fd = fd; + return ISO_SUCCESS; +} + +static +int ds_close(IsoDataSource *src) +{ + int ret; + struct file_data_src *data; + + if (src == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (struct file_data_src*) src->data; + if (data->fd == -1) { + return ISO_FILE_NOT_OPENNED; + } + + /* close can fail if fd is not valid, but that should never happen */ + ret = close(data->fd); + + /* in any case we mark file as closed */ + data->fd = -1; + return ret == 0 ? ISO_SUCCESS : ISO_FILE_ERROR; +} + +static int ds_read_block(IsoDataSource *src, uint32_t lba, uint8_t *buffer) +{ + struct file_data_src *data; + + if (src == NULL || src->data == NULL || buffer == NULL) { + return ISO_NULL_POINTER; + } + + data = (struct file_data_src*) src->data; + if (data->fd == -1) { + return ISO_FILE_NOT_OPENNED; + } + + /* goes to requested block */ + if (lseek(data->fd, (off_t)lba * (off_t)2048, SEEK_SET) == (off_t) -1) { + return ISO_FILE_SEEK_ERROR; + } + + /* TODO #00008 : guard against partial reads. */ + if (read(data->fd, buffer, 2048) != 2048) { + return ISO_FILE_READ_ERROR; + } + + return ISO_SUCCESS; +} + +static +void ds_free_data(IsoDataSource *src) +{ + struct file_data_src *data; + + data = (struct file_data_src*)src->data; + + /* close the file if needed */ + if (data->fd != -1) { + close(data->fd); + } + free(data->path); + free(data); +} + +/** + * Create a new IsoDataSource from a local file. This is suitable for + * accessing regular .iso images, or to acces drives via its block device + * and standard POSIX I/O calls. + * + * @param path + * The path of the file + * @param src + * Will be filled with the pointer to the newly created data source. + * @return + * 1 on success, < 0 on error. + */ +int iso_data_source_new_from_file(const char *path, IsoDataSource **src) +{ + int ret; + struct file_data_src *data; + IsoDataSource *ds; + + if (path == NULL || src == NULL) { + return ISO_NULL_POINTER; + } + + /* ensure we have read access to the file */ + ret = iso_eaccess(path); + if (ret < 0) { + return ret; + } + + data = malloc(sizeof(struct file_data_src)); + if (data == NULL) { + return ISO_OUT_OF_MEM; + } + + ds = malloc(sizeof(IsoDataSource)); + if (ds == NULL) { + free(data); + return ISO_OUT_OF_MEM; + } + + /* fill data fields */ + data->path = strdup(path); + if (data->path == NULL) { + free(data); + free(ds); + return ISO_OUT_OF_MEM; + } + + data->fd = -1; + ds->version = 0; + ds->refcount = 1; + ds->data = data; + + ds->open = ds_open; + ds->close = ds_close; + ds->read_block = ds_read_block; + ds->free_data = ds_free_data; + + *src = ds; + return ISO_SUCCESS; +} diff --git a/libisofs/ecma119.c b/libisofs/ecma119.c new file mode 100644 index 0000000..e49ad8c --- /dev/null +++ b/libisofs/ecma119.c @@ -0,0 +1,1579 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "ecma119.h" +#include "joliet.h" +#include "iso1999.h" +#include "eltorito.h" +#include "ecma119_tree.h" +#include "filesrc.h" +#include "image.h" +#include "writer.h" +#include "messages.h" +#include "rockridge.h" +#include "util.h" + +#include "libburn/libburn.h" + +#include +#include +#include +#include +#include + +/* + * TODO #00011 : guard against bad path table usage with more than 65535 dirs + * image with more than 65535 directories have path_table related problems + * due to 16 bits parent id. Note that this problem only affects to folders + * that are parent of another folder. + */ + +static +void ecma119_image_free(Ecma119Image *t) +{ + size_t i; + + ecma119_node_free(t->root); + iso_image_unref(t->image); + iso_rbtree_destroy(t->files, iso_file_src_free); + iso_ring_buffer_free(t->buffer); + + for (i = 0; i < t->nwriters; ++i) { + IsoImageWriter *writer = t->writers[i]; + writer->free_data(writer); + free(writer); + } + free(t->input_charset); + free(t->output_charset); + free(t->writers); + free(t); +} + +/** + * Check if we should add version number ";" to the given node name. + */ +static +int need_version_number(Ecma119Image *t, Ecma119Node *n) +{ + if (t->omit_version_numbers) { + return 0; + } + if (n->type == ECMA119_DIR || n->type == ECMA119_PLACEHOLDER) { + return 0; + } else { + return 1; + } +} + +/** + * Compute the size of a directory entry for a single node + */ +static +size_t calc_dirent_len(Ecma119Image *t, Ecma119Node *n) +{ + int ret = n->iso_name ? strlen(n->iso_name) + 33 : 34; + if (need_version_number(t, n)) { + ret += 2; /* take into account version numbers */ + } + if (ret % 2) + ret++; + return ret; +} + +/** + * Computes the total size of all directory entries of a single dir, + * acording to ECMA-119 6.8.1.1 + * + * This also take into account the size needed for RR entries and + * SUSP continuation areas (SUSP, 5.1). + * + * @param ce + * Will be filled with the size needed for Continuation Areas + * @return + * The size needed for all dir entries of the given dir, without + * taking into account the continuation areas. + */ +static +size_t calc_dir_size(Ecma119Image *t, Ecma119Node *dir, size_t *ce) +{ + size_t i, len; + size_t ce_len = 0; + + /* size of "." and ".." entries */ + len = 34 + 34; + if (t->rockridge) { + len += rrip_calc_len(t, dir, 1, 255 - 34, &ce_len); + *ce += ce_len; + len += rrip_calc_len(t, dir, 2, 255 - 34, &ce_len); + *ce += ce_len; + } + + for (i = 0; i < dir->info.dir->nchildren; ++i) { + size_t remaining; + Ecma119Node *child = dir->info.dir->children[i]; + size_t dirent_len = calc_dirent_len(t, child); + if (t->rockridge) { + dirent_len += rrip_calc_len(t, child, 0, 255 - dirent_len, &ce_len); + *ce += ce_len; + } + remaining = BLOCK_SIZE - (len % BLOCK_SIZE); + if (dirent_len > remaining) { + /* child directory entry doesn't fit on block */ + len += remaining + dirent_len; + } else { + len += dirent_len; + } + } + + /* + * The size of a dir is always a multiple of block size, as we must add + * the size of the unused space after the last directory record + * (ECMA-119, 6.8.1.3) + */ + len = ROUND_UP(len, BLOCK_SIZE); + + /* cache the len */ + dir->info.dir->len = len; + return len; +} + +static +void calc_dir_pos(Ecma119Image *t, Ecma119Node *dir) +{ + size_t i, len; + size_t ce_len = 0; + + t->ndirs++; + dir->info.dir->block = t->curblock; + len = calc_dir_size(t, dir, &ce_len); + t->curblock += DIV_UP(len, BLOCK_SIZE); + if (t->rockridge) { + t->curblock += DIV_UP(ce_len, BLOCK_SIZE); + } + for (i = 0; i < dir->info.dir->nchildren; i++) { + Ecma119Node *child = dir->info.dir->children[i]; + if (child->type == ECMA119_DIR) { + calc_dir_pos(t, child); + } + } +} + +/** + * Compute the length of the path table, in bytes. + */ +static +uint32_t calc_path_table_size(Ecma119Node *dir) +{ + uint32_t size; + size_t i; + + /* size of path table for this entry */ + size = 8; + size += dir->iso_name ? strlen(dir->iso_name) : 1; + size += (size % 2); + + /* and recurse */ + for (i = 0; i < dir->info.dir->nchildren; i++) { + Ecma119Node *child = dir->info.dir->children[i]; + if (child->type == ECMA119_DIR) { + size += calc_path_table_size(child); + } + } + return size; +} + +static +int ecma119_writer_compute_data_blocks(IsoImageWriter *writer) +{ + Ecma119Image *target; + uint32_t path_table_size; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + target = writer->target; + + /* compute position of directories */ + iso_msg_debug(target->image->id, "Computing position of dir structure"); + target->ndirs = 0; + calc_dir_pos(target, target->root); + + /* compute length of pathlist */ + iso_msg_debug(target->image->id, "Computing length of pathlist"); + path_table_size = calc_path_table_size(target->root); + + /* compute location for path tables */ + target->l_path_table_pos = target->curblock; + target->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + target->m_path_table_pos = target->curblock; + target->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + target->path_table_size = path_table_size; + + return ISO_SUCCESS; +} + +/** + * Write a single directory record (ECMA-119, 9.1) + * + * @param file_id + * if >= 0, we use it instead of the filename (for "." and ".." entries). + * @param len_fi + * Computed length of the file identifier. Total size of the directory + * entry will be len + 33 + padding if needed (ECMA-119, 9.1.12) + * @param info + * SUSP entries for the given directory record. It will be NULL for the + * root directory record in the PVD (ECMA-119, 8.4.18) (in order to + * distinguish it from the "." entry in the root directory) + */ +static +void write_one_dir_record(Ecma119Image *t, Ecma119Node *node, int file_id, + uint8_t *buf, size_t len_fi, struct susp_info *info) +{ + uint32_t len; + uint32_t block; + uint8_t len_dr; /*< size of dir entry without SUSP fields */ + uint8_t *name = (file_id >= 0) ? (uint8_t*)&file_id + : (uint8_t*)node->iso_name; + + struct ecma119_dir_record *rec = (struct ecma119_dir_record*)buf; + + len_dr = 33 + len_fi + (len_fi % 2 ? 0 : 1); + + memcpy(rec->file_id, name, len_fi); + + if (need_version_number(t, node)) { + len_dr += 2; + rec->file_id[len_fi++] = ';'; + rec->file_id[len_fi++] = '1'; + } + + if (node->type == ECMA119_DIR) { + /* use the cached length */ + len = node->info.dir->len; + block = node->info.dir->block; + } else if (node->type == ECMA119_FILE) { + len = iso_file_src_get_size(node->info.file); + block = node->info.file->block; + } else { + /* + * for nodes other than files and dirs, we set both + * len and block to 0 + */ + len = 0; + block = 0; + } + + /* + * For ".." entry we need to write the parent info! + */ + if (file_id == 1 && node->parent) + node = node->parent; + + rec->len_dr[0] = len_dr + (info != NULL ? info->suf_len : 0); + iso_bb(rec->block, block, 4); + iso_bb(rec->length, len, 4); + iso_datetime_7(rec->recording_time, t->now, t->always_gmt); + rec->flags[0] = (node->type == ECMA119_DIR) ? 2 : 0; + iso_bb(rec->vol_seq_number, 1, 2); + rec->len_fi[0] = len_fi; + + /* and finally write the SUSP fields */ + if (info != NULL) { + rrip_write_susp_fields(t, info, buf + len_dr); + } +} + +static +char *get_relaxed_vol_id(Ecma119Image *t, const char *name) +{ + int ret; + if (name == NULL) { + return NULL; + } + if (strcmp(t->input_charset, t->output_charset)) { + /* charset conversion needed */ + char *str; + ret = strconv(name, t->input_charset, t->output_charset, &str); + if (ret == ISO_SUCCESS) { + return str; + } + iso_msg_submit(t->image->id, ISO_FILENAME_WRONG_CHARSET, ret, + "Charset conversion error. Can't convert %s from %s to %s", + name, t->input_charset, t->output_charset); + } + return strdup(name); +} + +/** + * Write the Primary Volume Descriptor (ECMA-119, 8.4) + */ +static +int ecma119_writer_write_vol_desc(IsoImageWriter *writer) +{ + IsoImage *image; + Ecma119Image *t; + struct ecma119_pri_vol_desc vol; + + char *vol_id, *pub_id, *data_id, *volset_id; + char *system_id, *application_id, *copyright_file_id; + char *abstract_file_id, *biblio_file_id; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + t = writer->target; + image = t->image; + + iso_msg_debug(image->id, "Write Primary Volume Descriptor"); + + memset(&vol, 0, sizeof(struct ecma119_pri_vol_desc)); + + if (t->relaxed_vol_atts) { + vol_id = get_relaxed_vol_id(t, image->volume_id); + volset_id = get_relaxed_vol_id(t, image->volset_id); + } else { + str2d_char(t->input_charset, image->volume_id, &vol_id); + str2d_char(t->input_charset, image->volset_id, &volset_id); + } + str2a_char(t->input_charset, image->publisher_id, &pub_id); + str2a_char(t->input_charset, image->data_preparer_id, &data_id); + str2a_char(t->input_charset, image->system_id, &system_id); + str2a_char(t->input_charset, image->application_id, &application_id); + str2d_char(t->input_charset, image->copyright_file_id, ©right_file_id); + str2d_char(t->input_charset, image->abstract_file_id, &abstract_file_id); + str2d_char(t->input_charset, image->biblio_file_id, &biblio_file_id); + + vol.vol_desc_type[0] = 1; + memcpy(vol.std_identifier, "CD001", 5); + vol.vol_desc_version[0] = 1; + strncpy_pad((char*)vol.system_id, system_id, 32); + strncpy_pad((char*)vol.volume_id, vol_id, 32); + iso_bb(vol.vol_space_size, t->vol_space_size, 4); + iso_bb(vol.vol_set_size, 1, 2); + iso_bb(vol.vol_seq_number, 1, 2); + iso_bb(vol.block_size, BLOCK_SIZE, 2); + iso_bb(vol.path_table_size, t->path_table_size, 4); + iso_lsb(vol.l_path_table_pos, t->l_path_table_pos, 4); + iso_msb(vol.m_path_table_pos, t->m_path_table_pos, 4); + + write_one_dir_record(t, t->root, 0, vol.root_dir_record, 1, NULL); + + strncpy_pad((char*)vol.vol_set_id, volset_id, 128); + strncpy_pad((char*)vol.publisher_id, pub_id, 128); + strncpy_pad((char*)vol.data_prep_id, data_id, 128); + + strncpy_pad((char*)vol.application_id, application_id, 128); + strncpy_pad((char*)vol.copyright_file_id, copyright_file_id, 37); + strncpy_pad((char*)vol.abstract_file_id, abstract_file_id, 37); + strncpy_pad((char*)vol.bibliographic_file_id, biblio_file_id, 37); + + iso_datetime_17(vol.vol_creation_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_modification_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_effective_time, t->now, t->always_gmt); + vol.file_structure_version[0] = 1; + + free(vol_id); + free(volset_id); + free(pub_id); + free(data_id); + free(system_id); + free(application_id); + free(copyright_file_id); + free(abstract_file_id); + free(biblio_file_id); + + /* Finally write the Volume Descriptor */ + return iso_write(t, &vol, sizeof(struct ecma119_pri_vol_desc)); +} + +static +int write_one_dir(Ecma119Image *t, Ecma119Node *dir) +{ + int ret; + uint8_t buffer[BLOCK_SIZE]; + size_t i; + size_t fi_len, len; + struct susp_info info; + + /* buf will point to current write position on buffer */ + uint8_t *buf = buffer; + + /* initialize buffer with 0s */ + memset(buffer, 0, BLOCK_SIZE); + + /* + * set susp_info to 0's, this way code for both plain ECMA-119 and + * RR is very similar + */ + memset(&info, 0, sizeof(struct susp_info)); + if (t->rockridge) { + /* initialize the ce_block, it might be needed */ + info.ce_block = dir->info.dir->block + DIV_UP(dir->info.dir->len, + BLOCK_SIZE); + } + + /* write the "." and ".." entries first */ + if (t->rockridge) { + ret = rrip_get_susp_fields(t, dir, 1, 255 - 32, &info); + if (ret < 0) { + return ret; + } + } + len = 34 + info.suf_len; + write_one_dir_record(t, dir, 0, buf, 1, &info); + buf += len; + + if (t->rockridge) { + ret = rrip_get_susp_fields(t, dir, 2, 255 - 32, &info); + if (ret < 0) { + return ret; + } + } + len = 34 + info.suf_len; + write_one_dir_record(t, dir, 1, buf, 1, &info); + buf += len; + + for (i = 0; i < dir->info.dir->nchildren; i++) { + Ecma119Node *child = dir->info.dir->children[i]; + + /* compute len of directory entry */ + fi_len = strlen(child->iso_name); + len = fi_len + 33 + (fi_len % 2 ? 0 : 1); + if (need_version_number(t, child)) { + len += 2; + } + + /* get the SUSP fields if rockridge is enabled */ + if (t->rockridge) { + ret = rrip_get_susp_fields(t, child, 0, 255 - len, &info); + if (ret < 0) { + return ret; + } + len += info.suf_len; + } + + if ( (buf + len - buffer) > BLOCK_SIZE) { + /* dir doesn't fit in current block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + if (ret < 0) { + return ret; + } + memset(buffer, 0, BLOCK_SIZE); + buf = buffer; + } + /* write the directory entry in any case */ + write_one_dir_record(t, child, -1, buf, fi_len, &info); + buf += len; + } + + /* write the last block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + if (ret < 0) { + return ret; + } + + /* write the Continuation Area if needed */ + if (info.ce_len > 0) { + ret = rrip_write_ce_fields(t, &info); + } + + return ret; +} + +static +int write_dirs(Ecma119Image *t, Ecma119Node *root) +{ + int ret; + size_t i; + + /* write all directory entries for this dir */ + ret = write_one_dir(t, root); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < root->info.dir->nchildren; i++) { + Ecma119Node *child = root->info.dir->children[i]; + if (child->type == ECMA119_DIR) { + ret = write_dirs(t, child); + if (ret < 0) { + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int write_path_table(Ecma119Image *t, Ecma119Node **pathlist, int l_type) +{ + size_t i, len; + uint8_t buf[64]; /* 64 is just a convenient size larger enought */ + struct ecma119_path_table_record *rec; + void (*write_int)(uint8_t*, uint32_t, int); + Ecma119Node *dir; + uint32_t path_table_size; + int parent = 0; + int ret= ISO_SUCCESS; + + path_table_size = 0; + write_int = l_type ? iso_lsb : iso_msb; + + for (i = 0; i < t->ndirs; i++) { + dir = pathlist[i]; + + /* find the index of the parent in the table */ + while ((i) && pathlist[parent] != dir->parent) { + parent++; + } + + /* write the Path Table Record (ECMA-119, 9.4) */ + memset(buf, 0, 64); + rec = (struct ecma119_path_table_record*) buf; + rec->len_di[0] = dir->parent ? (uint8_t) strlen(dir->iso_name) : 1; + rec->len_xa[0] = 0; + write_int(rec->block, dir->info.dir->block, 4); + write_int(rec->parent, parent + 1, 2); + if (dir->parent) { + memcpy(rec->dir_id, dir->iso_name, rec->len_di[0]); + } + len = 8 + rec->len_di[0] + (rec->len_di[0] % 2); + ret = iso_write(t, buf, len); + if (ret < 0) { + /* error */ + return ret; + } + path_table_size += len; + } + + /* we need to fill the last block with zeros */ + path_table_size %= BLOCK_SIZE; + if (path_table_size) { + uint8_t zeros[BLOCK_SIZE]; + len = BLOCK_SIZE - path_table_size; + memset(zeros, 0, len); + ret = iso_write(t, zeros, len); + } + return ret; +} + +static +int write_path_tables(Ecma119Image *t) +{ + int ret; + size_t i, j, cur; + Ecma119Node **pathlist; + + iso_msg_debug(t->image->id, "Writing ISO Path tables"); + + /* allocate temporal pathlist */ + pathlist = malloc(sizeof(void*) * t->ndirs); + if (pathlist == NULL) { + return ISO_OUT_OF_MEM; + } + pathlist[0] = t->root; + cur = 1; + + for (i = 0; i < t->ndirs; i++) { + Ecma119Node *dir = pathlist[i]; + for (j = 0; j < dir->info.dir->nchildren; j++) { + Ecma119Node *child = dir->info.dir->children[j]; + if (child->type == ECMA119_DIR) { + pathlist[cur++] = child; + } + } + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 1); + if (ret < 0) { + goto write_path_tables_exit; + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 0); + + write_path_tables_exit: ; + free(pathlist); + return ret; +} + +/** + * Write both the directory structure (ECMA-119, 6.8) and the L and M + * Path Tables (ECMA-119, 6.9). + */ +static +int ecma119_writer_write_data(IsoImageWriter *writer) +{ + int ret; + Ecma119Image *t; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + t = writer->target; + + /* first of all, we write the directory structure */ + ret = write_dirs(t, t->root); + if (ret < 0) { + return ret; + } + + /* and write the path tables */ + ret = write_path_tables(t); + + return ret; +} + +static +int ecma119_writer_free_data(IsoImageWriter *writer) +{ + /* nothing to do */ + return ISO_SUCCESS; +} + +int ecma119_writer_create(Ecma119Image *target) +{ + int ret; + IsoImageWriter *writer; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + writer->compute_data_blocks = ecma119_writer_compute_data_blocks; + writer->write_vol_desc = ecma119_writer_write_vol_desc; + writer->write_data = ecma119_writer_write_data; + writer->free_data = ecma119_writer_free_data; + writer->data = NULL; + writer->target = target; + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + + iso_msg_debug(target->image->id, "Creating low level ECMA-119 tree..."); + ret = ecma119_tree_create(target); + if (ret < 0) { + return ret; + } + + /* we need the volume descriptor */ + target->curblock++; + return ISO_SUCCESS; +} + +/** compute how many padding bytes are needed */ +static +int pad_writer_compute_data_blocks(IsoImageWriter *writer) +{ + Ecma119Image *target; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + target = writer->target; + if (target->curblock < 32) { + target->pad_blocks = 32 - target->curblock; + target->curblock = 32; + } + return ISO_SUCCESS; +} + +static +int pad_writer_write_vol_desc(IsoImageWriter *writer) +{ + /* nothing to do */ + return ISO_SUCCESS; +} +static +int pad_writer_write_data(IsoImageWriter *writer) +{ + int ret; + Ecma119Image *t; + uint32_t pad[BLOCK_SIZE]; + size_t i; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + t = writer->target; + + if (t->pad_blocks == 0) { + return ISO_SUCCESS; + } + + memset(pad, 0, BLOCK_SIZE); + for (i = 0; i < t->pad_blocks; ++i) { + ret = iso_write(t, pad, BLOCK_SIZE); + if (ret < 0) { + return ret; + } + } + + return ISO_SUCCESS; +} + +static +int pad_writer_free_data(IsoImageWriter *writer) +{ + /* nothing to do */ + return ISO_SUCCESS; +} + +static +int pad_writer_create(Ecma119Image *target) +{ + IsoImageWriter *writer; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + writer->compute_data_blocks = pad_writer_compute_data_blocks; + writer->write_vol_desc = pad_writer_write_vol_desc; + writer->write_data = pad_writer_write_data; + writer->free_data = pad_writer_free_data; + writer->data = NULL; + writer->target = target; + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + return ISO_SUCCESS; +} + +static +void *write_function(void *arg) +{ + int res; + size_t i; + uint8_t buf[BLOCK_SIZE]; + IsoImageWriter *writer; + + Ecma119Image *target = (Ecma119Image*)arg; + iso_msg_debug(target->image->id, "Starting image writing..."); + + target->bytes_written = (off_t) 0; + target->percent_written = 0; + + /* Write System Area, 16 blocks of zeros (ECMA-119, 6.2.1) */ + memset(buf, 0, BLOCK_SIZE); + for (i = 0; i < 16; ++i) { + res = iso_write(target, buf, BLOCK_SIZE); + if (res < 0) { + goto write_error; + } + } + + /* write volume descriptors, one per writer */ + iso_msg_debug(target->image->id, "Write volume descriptors"); + for (i = 0; i < target->nwriters; ++i) { + writer = target->writers[i]; + res = writer->write_vol_desc(writer); + if (res < 0) { + goto write_error; + } + } + + /* write Volume Descriptor Set Terminator (ECMA-119, 8.3) */ + { + struct ecma119_vol_desc_terminator *vol; + vol = (struct ecma119_vol_desc_terminator *) buf; + + vol->vol_desc_type[0] = 255; + memcpy(vol->std_identifier, "CD001", 5); + vol->vol_desc_version[0] = 1; + + res = iso_write(target, buf, BLOCK_SIZE); + if (res < 0) { + goto write_error; + } + } + + /* write data for each writer */ + for (i = 0; i < target->nwriters; ++i) { + writer = target->writers[i]; + res = writer->write_data(writer); + if (res < 0) { + goto write_error; + } + } + + iso_ring_buffer_writer_close(target->buffer, 0); + pthread_exit(NULL); + + write_error: ; + if (res == ISO_CANCELED) { + /* canceled */ + iso_msg_submit(target->image->id, ISO_IMAGE_WRITE_CANCELED, 0, NULL); + } else { + /* image write error */ + iso_msg_submit(target->image->id, ISO_WRITE_ERROR, res, + "Image write error"); + } + iso_ring_buffer_writer_close(target->buffer, 1); + pthread_exit(NULL); +} + +static +int ecma119_image_new(IsoImage *src, IsoWriteOpts *opts, Ecma119Image **img) +{ + int ret, i, voldesc_size, nwriters; + Ecma119Image *target; + + /* 1. Allocate target and copy opts there */ + target = calloc(1, sizeof(Ecma119Image)); + if (target == NULL) { + return ISO_OUT_OF_MEM; + } + + /* create the tree for file caching */ + ret = iso_rbtree_new(iso_file_src_cmp, &(target->files)); + if (ret < 0) { + free(target); + return ret; + } + + target->image = src; + iso_image_ref(src); + + target->iso_level = opts->level; + target->rockridge = opts->rockridge; + target->joliet = opts->joliet; + target->iso1999 = opts->iso1999; + target->always_gmt = opts->always_gmt; + target->ino = 0; + target->omit_version_numbers = opts->omit_version_numbers + | opts->max_37_char_filenames; + target->allow_deep_paths = opts->allow_deep_paths; + target->allow_longer_paths = opts->allow_longer_paths; + target->max_37_char_filenames = opts->max_37_char_filenames; + target->no_force_dots = opts->no_force_dots; + target->allow_lowercase = opts->allow_lowercase; + target->allow_full_ascii = opts->allow_full_ascii; + target->relaxed_vol_atts = opts->relaxed_vol_atts; + target->joliet_longer_paths = opts->joliet_longer_paths; + target->sort_files = opts->sort_files; + + target->replace_uid = opts->replace_uid ? 1 : 0; + target->replace_gid = opts->replace_gid ? 1 : 0; + target->replace_dir_mode = opts->replace_dir_mode ? 1 : 0; + target->replace_file_mode = opts->replace_file_mode ? 1 : 0; + + target->uid = opts->replace_uid == 2 ? opts->uid : 0; + target->gid = opts->replace_gid == 2 ? opts->gid : 0; + target->dir_mode = opts->replace_dir_mode == 2 ? opts->dir_mode : 0555; + target->file_mode = opts->replace_file_mode == 2 ? opts->file_mode : 0444; + + target->now = time(NULL); + target->ms_block = opts->ms_block; + target->appendable = opts->appendable; + + target->replace_timestamps = opts->replace_timestamps ? 1 : 0; + target->timestamp = opts->replace_timestamps == 2 ? + opts->timestamp : target->now; + + /* el-torito? */ + target->eltorito = (src->bootcat == NULL ? 0 : 1); + target->catalog = src->bootcat; + + /* default to locale charset */ + setlocale(LC_CTYPE, ""); + target->input_charset = strdup(nl_langinfo(CODESET)); + if (target->input_charset == NULL) { + iso_image_unref(src); + free(target); + return ISO_OUT_OF_MEM; + } + + if (opts->output_charset != NULL) { + target->output_charset = strdup(opts->output_charset); + } else { + target->output_charset = strdup(target->input_charset); + } + if (target->output_charset == NULL) { + iso_image_unref(src); + free(target); + return ISO_OUT_OF_MEM; + } + + /* + * 2. Based on those options, create needed writers: iso, joliet... + * Each writer inits its structures and stores needed info into + * target. + * If the writer needs an volume descriptor, it increments image + * current block. + * Finally, create Writer for files. + */ + target->curblock = target->ms_block + 16; + + /* the number of writers is dependent of the extensions */ + nwriters = 1 + 1 + 1; /* ECMA-119 + padding + files */ + + if (target->eltorito) { + nwriters++; + } + if (target->joliet) { + nwriters++; + } + if (target->iso1999) { + nwriters++; + } + + target->writers = malloc(nwriters * sizeof(void*)); + if (target->writers == NULL) { + iso_image_unref(src); + free(target); + return ISO_OUT_OF_MEM; + } + + /* create writer for ECMA-119 structure */ + ret = ecma119_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + + /* create writer for El-Torito */ + if (target->eltorito) { + ret = eltorito_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + } + + /* create writer for Joliet structure */ + if (target->joliet) { + ret = joliet_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + } + + /* create writer for ISO 9660:1999 structure */ + if (target->iso1999) { + ret = iso1999_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + } + + voldesc_size = target->curblock - target->ms_block - 16; + + /* Volume Descriptor Set Terminator */ + target->curblock++; + + /* + * Create the writer for possible padding to ensure that in case of image + * growing we can safety overwrite the first 64 KiB of image. + */ + ret = pad_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + + /* create writer for file contents */ + ret = iso_file_src_writer_create(target); + if (ret < 0) { + goto target_cleanup; + } + + /* + * 3. + * Call compute_data_blocks() in each Writer. + * That function computes the size needed by its structures and + * increments image current block propertly. + */ + for (i = 0; i < target->nwriters; ++i) { + IsoImageWriter *writer = target->writers[i]; + ret = writer->compute_data_blocks(writer); + if (ret < 0) { + goto target_cleanup; + } + } + + /* create the ring buffer */ + ret = iso_ring_buffer_new(opts->fifo_size, &target->buffer); + if (ret < 0) { + goto target_cleanup; + } + + /* check if we need to provide a copy of volume descriptors */ + if (opts->overwrite) { + + /* + * Get a copy of the volume descriptors to be written in a DVD+RW + * disc + */ + + uint8_t *buf; + struct ecma119_vol_desc_terminator *vol; + IsoImageWriter *writer; + + /* + * In the PVM to be written in the 16th sector of the disc, we + * need to specify the full size. + */ + target->vol_space_size = target->curblock; + + /* write volume descriptor */ + for (i = 0; i < target->nwriters; ++i) { + writer = target->writers[i]; + ret = writer->write_vol_desc(writer); + if (ret < 0) { + iso_msg_debug(target->image->id, + "Error writing overwrite volume descriptors"); + goto target_cleanup; + } + } + + /* skip the first 16 blocks (system area) */ + buf = opts->overwrite + 16 * BLOCK_SIZE; + voldesc_size *= BLOCK_SIZE; + + /* copy the volume descriptors to the overwrite buffer... */ + ret = iso_ring_buffer_read(target->buffer, buf, voldesc_size); + if (ret < 0) { + iso_msg_debug(target->image->id, + "Error reading overwrite volume descriptors"); + goto target_cleanup; + } + + /* ...including the vol desc terminator */ + memset(buf + voldesc_size, 0, BLOCK_SIZE); + vol = (struct ecma119_vol_desc_terminator*) (buf + voldesc_size); + vol->vol_desc_type[0] = 255; + memcpy(vol->std_identifier, "CD001", 5); + vol->vol_desc_version[0] = 1; + } + + /* + * The volume space size is just the size of the last session, in + * case of ms images. + */ + target->vol_space_size = target->curblock - target->ms_block; + target->total_size = (off_t) target->vol_space_size * BLOCK_SIZE; + + /* 4. Create and start writting thread */ + + /* ensure the thread is created joinable */ + pthread_attr_init(&(target->th_attr)); + pthread_attr_setdetachstate(&(target->th_attr), PTHREAD_CREATE_JOINABLE); + + ret = pthread_create(&(target->wthread), &(target->th_attr), + write_function, (void *) target); + if (ret != 0) { + iso_msg_submit(target->image->id, ISO_THREAD_ERROR, 0, + "Cannot create writer thread"); + ret = ISO_THREAD_ERROR; + goto target_cleanup; + } + + /* + * Notice that once we reach this point, target belongs to the writer + * thread and should not be modified until the writer thread finished. + * There're however, specific fields in target that can be accessed, or + * even modified by the read thread (look inside bs_* functions) + */ + + *img = target; + return ISO_SUCCESS; + + target_cleanup: ; + ecma119_image_free(target); + return ret; +} + +static int bs_read(struct burn_source *bs, unsigned char *buf, int size) +{ + int ret; + Ecma119Image *t = (Ecma119Image*)bs->data; + + ret = iso_ring_buffer_read(t->buffer, buf, size); + if (ret == ISO_SUCCESS) { + return size; + } else if (ret < 0) { + /* error */ + iso_msg_submit(t->image->id, ISO_BUF_READ_ERROR, ret, NULL); + return -1; + } else { + /* EOF */ + return 0; + } +} + +static off_t bs_get_size(struct burn_source *bs) +{ + Ecma119Image *target = (Ecma119Image*)bs->data; + return target->total_size; +} + +static void bs_free_data(struct burn_source *bs) +{ + int st; + Ecma119Image *target = (Ecma119Image*)bs->data; + + st = iso_ring_buffer_get_status(bs, NULL, NULL); + + /* was read already finished (i.e, canceled)? */ + if (st < 4) { + /* forces writer to stop if it is still running */ + iso_ring_buffer_reader_close(target->buffer, 0); + + /* wait until writer thread finishes */ + pthread_join(target->wthread, NULL); + iso_msg_debug(target->image->id, "Writer thread joined"); + } + + iso_msg_debug(target->image->id, + "Ring buffer was %d times full and %d times empty", + iso_ring_buffer_get_times_full(target->buffer), + iso_ring_buffer_get_times_empty(target->buffer)); + + /* now we can safety free target */ + ecma119_image_free(target); +} + +static +int bs_cancel(struct burn_source *bs) +{ + int st; + size_t cap, free; + Ecma119Image *target = (Ecma119Image*)bs->data; + + st = iso_ring_buffer_get_status(bs, &cap, &free); + + if (free == cap && (st == 2 || st == 3)) { + /* image was already consumed */ + iso_ring_buffer_reader_close(target->buffer, 0); + } else { + iso_msg_debug(target->image->id, "Reader thread being cancelled"); + + /* forces writer to stop if it is still running */ + iso_ring_buffer_reader_close(target->buffer, ISO_CANCELED); + } + + /* wait until writer thread finishes */ + pthread_join(target->wthread, NULL); + + iso_msg_debug(target->image->id, "Writer thread joined"); + return ISO_SUCCESS; +} + +static +int bs_set_size(struct burn_source *bs, off_t size) +{ + Ecma119Image *target = (Ecma119Image*)bs->data; + + /* + * just set the value to be returned by get_size. This is not used at + * all by libisofs, it is here just for helping libburn to correctly pad + * the image if needed. + */ + target->total_size = size; + return 1; +} + +int iso_image_create_burn_source(IsoImage *image, IsoWriteOpts *opts, + struct burn_source **burn_src) +{ + int ret; + struct burn_source *source; + Ecma119Image *target= NULL; + + if (image == NULL || opts == NULL || burn_src == NULL) { + return ISO_NULL_POINTER; + } + + source = calloc(1, sizeof(struct burn_source)); + if (source == NULL) { + return ISO_OUT_OF_MEM; + } + + ret = ecma119_image_new(image, opts, &target); + if (ret < 0) { + free(source); + return ret; + } + + source->refcount = 1; + source->version = 1; + source->read = NULL; + source->get_size = bs_get_size; + source->set_size = bs_set_size; + source->free_data = bs_free_data; + source->read_xt = bs_read; + source->cancel = bs_cancel; + source->data = target; + + *burn_src = source; + return ISO_SUCCESS; +} + +int iso_write(Ecma119Image *target, void *buf, size_t count) +{ + int ret; + + ret = iso_ring_buffer_write(target->buffer, buf, count); + if (ret == 0) { + /* reader cancelled */ + return ISO_CANCELED; + } + + /* total size is 0 when writing the overwrite buffer */ + if (ret > 0 && (target->total_size != (off_t) 0)){ + unsigned int kbw, kbt; + int percent; + + target->bytes_written += (off_t) count; + kbw = (unsigned int) (target->bytes_written >> 10); + kbt = (unsigned int) (target->total_size >> 10); + percent = (kbw * 100) / kbt; + + /* only report in 5% chunks */ + if (percent >= target->percent_written + 5) { + iso_msg_debug(target->image->id, "Processed %u of %u KB (%d %%)", + kbw, kbt, percent); + target->percent_written = percent; + } + } + + return ret; +} + +int iso_write_opts_new(IsoWriteOpts **opts, int profile) +{ + IsoWriteOpts *wopts; + + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (profile < 0 || profile > 2) { + return ISO_WRONG_ARG_VALUE; + } + + wopts = calloc(1, sizeof(IsoWriteOpts)); + if (wopts == NULL) { + return ISO_OUT_OF_MEM; + } + + switch (profile) { + case 0: + wopts->level = 1; + break; + case 1: + wopts->level = 2; + wopts->rockridge = 1; + break; + case 2: + wopts->level = 2; + wopts->rockridge = 1; + wopts->joliet = 1; + wopts->replace_dir_mode = 1; + wopts->replace_file_mode = 1; + wopts->replace_uid = 1; + wopts->replace_gid = 1; + wopts->replace_timestamps = 1; + wopts->always_gmt = 1; + break; + default: + /* should never happen */ + free(wopts); + return ISO_ASSERT_FAILURE; + break; + } + wopts->fifo_size = 1024; /* 2 MB buffer */ + wopts->sort_files = 1; /* file sorting is always good */ + + *opts = wopts; + return ISO_SUCCESS; +} + +void iso_write_opts_free(IsoWriteOpts *opts) +{ + if (opts == NULL) { + return; + } + + free(opts->output_charset); + free(opts); +} + +int iso_write_opts_set_iso_level(IsoWriteOpts *opts, int level) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (level != 1 && level != 2) { + return ISO_WRONG_ARG_VALUE; + } + opts->level = level; + return ISO_SUCCESS; +} + +int iso_write_opts_set_rockridge(IsoWriteOpts *opts, int enable) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->rockridge = enable ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_joliet(IsoWriteOpts *opts, int enable) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->joliet = enable ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_iso1999(IsoWriteOpts *opts, int enable) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->iso1999 = enable ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_omit_version_numbers(IsoWriteOpts *opts, int omit) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->omit_version_numbers = omit ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_allow_deep_paths(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->allow_deep_paths = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_allow_longer_paths(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->allow_longer_paths = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_max_37_char_filenames(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->max_37_char_filenames = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_no_force_dots(IsoWriteOpts *opts, int no) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->no_force_dots = no ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_allow_lowercase(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->allow_lowercase = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_allow_full_ascii(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->allow_full_ascii = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_relaxed_vol_atts(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->relaxed_vol_atts = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_joliet_longer_paths(IsoWriteOpts *opts, int allow) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->joliet_longer_paths = allow ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_sort_files(IsoWriteOpts *opts, int sort) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->sort_files = sort ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_replace_mode(IsoWriteOpts *opts, int dir_mode, + int file_mode, int uid, int gid) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (dir_mode < 0 || dir_mode > 2) { + return ISO_WRONG_ARG_VALUE; + } + if (file_mode < 0 || file_mode > 2) { + return ISO_WRONG_ARG_VALUE; + } + if (uid < 0 || uid > 2) { + return ISO_WRONG_ARG_VALUE; + } + if (gid < 0 || gid > 2) { + return ISO_WRONG_ARG_VALUE; + } + opts->replace_dir_mode = dir_mode; + opts->replace_file_mode = file_mode; + opts->replace_uid = uid; + opts->replace_gid = gid; + return ISO_SUCCESS; +} + +int iso_write_opts_set_default_dir_mode(IsoWriteOpts *opts, mode_t dir_mode) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->dir_mode = dir_mode; + return ISO_SUCCESS; +} + +int iso_write_opts_set_default_file_mode(IsoWriteOpts *opts, mode_t file_mode) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->file_mode = file_mode; + return ISO_SUCCESS; +} + +int iso_write_opts_set_default_uid(IsoWriteOpts *opts, uid_t uid) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->uid = uid; + return ISO_SUCCESS; +} + +int iso_write_opts_set_default_gid(IsoWriteOpts *opts, gid_t gid) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->gid = gid; + return ISO_SUCCESS; +} + +int iso_write_opts_set_replace_timestamps(IsoWriteOpts *opts, int replace) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (replace < 0 || replace > 2) { + return ISO_WRONG_ARG_VALUE; + } + opts->replace_timestamps = replace; + return ISO_SUCCESS; +} + +int iso_write_opts_set_default_timestamp(IsoWriteOpts *opts, time_t timestamp) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->timestamp = timestamp; + return ISO_SUCCESS; +} + +int iso_write_opts_set_always_gmt(IsoWriteOpts *opts, int gmt) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->always_gmt = gmt ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_output_charset(IsoWriteOpts *opts, const char *charset) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->output_charset = charset ? strdup(charset) : NULL; + return ISO_SUCCESS; +} + +int iso_write_opts_set_appendable(IsoWriteOpts *opts, int appendable) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->appendable = appendable ? 1 : 0; + return ISO_SUCCESS; +} + +int iso_write_opts_set_ms_block(IsoWriteOpts *opts, uint32_t ms_block) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->ms_block = ms_block; + return ISO_SUCCESS; +} + +int iso_write_opts_set_overwrite_buf(IsoWriteOpts *opts, uint8_t *overwrite) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->overwrite = overwrite; + return ISO_SUCCESS; +} + +int iso_write_opts_set_fifo_size(IsoWriteOpts *opts, size_t fifo_size) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (fifo_size < 32) { + return ISO_WRONG_ARG_VALUE; + } + opts->fifo_size = fifo_size; + return ISO_SUCCESS; +} diff --git a/libisofs/ecma119.h b/libisofs/ecma119.h new file mode 100644 index 0000000..ffb1611 --- /dev/null +++ b/libisofs/ecma119.h @@ -0,0 +1,476 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_ECMA119_H_ +#define LIBISO_ECMA119_H_ + +#include "libisofs.h" +#include "util.h" +#include "buffer.h" + +#include +#include + +#define BLOCK_SIZE 2048 + +/** + * Holds the options for the image generation. + */ +struct iso_write_opts { + + int level; /**< ISO level to write at. (ECMA-119, 10) */ + + /** Which extensions to support. */ + unsigned int rockridge :1; + unsigned int joliet :1; + unsigned int iso1999 :1; + + /* allways write timestamps in GMT */ + unsigned int always_gmt :1; + + /* + * Relaxed constraints. Setting any of these to 1 break the specifications, + * but it is supposed to work on most moderns systems. Use with caution. + */ + + /** + * Omit the version number (";1") at the end of the ISO-9660 identifiers. + * Version numbers are usually not used. + */ + unsigned int omit_version_numbers :1; + + /** + * Allow ISO-9660 directory hierarchy to be deeper than 8 levels. + */ + unsigned int allow_deep_paths :1; + + /** + * Allow path in the ISO-9660 tree to have more than 255 characters. + */ + unsigned int allow_longer_paths :1; + + /** + * Allow a single file or directory hierarchy to have up to 37 characters. + * This is larger than the 31 characters allowed by ISO level 2, and the + * extra space is taken from the version number, so this also forces + * omit_version_numbers. + */ + unsigned int max_37_char_filenames :1; + + /** + * ISO-9660 forces filenames to have a ".", that separates file name from + * extension. libisofs adds it if original filename doesn't has one. Set + * this to 1 to prevent this behavior + */ + unsigned int no_force_dots :1; + + /** + * Allow lowercase characters in ISO-9660 filenames. By default, only + * uppercase characters, numbers and a few other characters are allowed. + */ + unsigned int allow_lowercase :1; + + /** + * Allow all ASCII characters to be appear on an ISO-9660 filename. Note + * that "/" and "\0" characters are never allowed, even in RR names. + */ + unsigned int allow_full_ascii :1; + + /** + * Allow all characters to be part of Volume and Volset identifiers on + * the Primary Volume Descriptor. This breaks ISO-9660 contraints, but + * should work on modern systems. + */ + unsigned int relaxed_vol_atts :1; + + /** + * Allow paths in the Joliet tree to have more than 240 characters. + */ + unsigned int joliet_longer_paths :1; + + /** If files should be sorted based on their weight. */ + unsigned int sort_files :1; + + /** + * The following options set the default values for files and directory + * permissions, gid and uid. All these take one of three values: 0, 1 or 2. + * If 0, the corresponding attribute will be kept as setted in the IsoNode. + * Unless you have changed it, it corresponds to the value on disc, so it + * is suitable for backup purposes. If set to 1, the corresponding attrib. + * will be changed by a default suitable value. Finally, if you set it to + * 2, the attrib. will be changed with the value specified in the options + * below. Note that for mode attributes, only the permissions are set, the + * file type remains unchanged. + */ + unsigned int replace_dir_mode :2; + unsigned int replace_file_mode :2; + unsigned int replace_uid :2; + unsigned int replace_gid :2; + + mode_t dir_mode; /** Mode to use on dirs when replace_dir_mode == 2. */ + mode_t file_mode; /** Mode to use on files when replace_file_mode == 2. */ + uid_t uid; /** uid to use when replace_uid == 2. */ + gid_t gid; /** gid to use when replace_gid == 2. */ + + /** + * 0 to use IsoNode timestamps, 1 to use recording time, 2 to use + * values from timestamp field. This has only meaning if RR extensions + * are enabled. + */ + unsigned int replace_timestamps :2; + time_t timestamp; + + /** + * Charset for the RR filenames that will be created. + * NULL to use default charset, the locale one. + */ + char *output_charset; + + /** + * This flags control the type of the image to create. Libisofs support + * two kind of images: stand-alone and appendable. + * + * A stand-alone image is an image that is valid alone, and that can be + * mounted by its own. This is the kind of image you will want to create + * in most cases. A stand-alone image can be burned in an empty CD or DVD, + * or write to an .iso file for future burning or distribution. + * + * On the other side, an appendable image is not self contained, it refers + * to serveral files that are stored outside the image. Its usage is for + * multisession discs, where you add data in a new session, while the + * previous session data can still be accessed. In those cases, the old + * data is not written again. Instead, the new image refers to it, and thus + * it's only valid when appended to the original. Note that in those cases + * the image will be written after the original, and thus you will want + * to use a ms_block greater than 0. + * + * Note that if you haven't import a previous image (by means of + * iso_image_import()), the image will always be a stand-alone image, as + * there is no previous data to refer to. + */ + unsigned int appendable : 1; + + /** + * Start block of the image. It is supposed to be the lba where the first + * block of the image will be written on disc. All references inside the + * ISO image will take this into account, thus providing a mountable image. + * + * For appendable images, that are written to a new session, you should + * pass here the lba of the next writable address on disc. + * + * In stand alone images this is usually 0. However, you may want to + * provide a different ms_block if you don't plan to burn the image in the + * first session on disc, such as in some CD-Extra disc whether the data + * image is written in a new session after some audio tracks. + */ + uint32_t ms_block; + + /** + * When not NULL, it should point to a buffer of at least 64KiB, where + * libisofs will write the contents that should be written at the beginning + * of a overwriteable media, to grow the image. The growing of an image is + * a way, used by first time in growisofs by Andy Polyakov, to allow the + * appending of new data to non-multisession media, such as DVD+RW, in the + * same way you append a new session to a multisession disc, i.e., without + * need to write again the contents of the previous image. + * + * Note that if you want this kind of image growing, you will also need to + * set appendable to "1" and provide a valid ms_block after the previous + * image. + * + * You should initialize the buffer either with 0s, or with the contents of + * the first blocks of the image you're growing. In most cases, 0 is good + * enought. + */ + uint8_t *overwrite; + + /** + * Size, in number of blocks, of the FIFO buffer used between the writer + * thread and the burn_source. You have to provide at least a 32 blocks + * buffer. + */ + size_t fifo_size; +}; + +typedef struct ecma119_image Ecma119Image; +typedef struct ecma119_node Ecma119Node; +typedef struct joliet_node JolietNode; +typedef struct iso1999_node Iso1999Node; +typedef struct Iso_File_Src IsoFileSrc; +typedef struct Iso_Image_Writer IsoImageWriter; + +struct ecma119_image +{ + IsoImage *image; + Ecma119Node *root; + + unsigned int iso_level :2; + + /* extensions */ + unsigned int rockridge :1; + unsigned int joliet :1; + unsigned int eltorito :1; + unsigned int iso1999 :1; + + /* allways write timestamps in GMT */ + unsigned int always_gmt :1; + + /* relaxed constraints */ + unsigned int omit_version_numbers :1; + unsigned int allow_deep_paths :1; + unsigned int allow_longer_paths :1; + unsigned int max_37_char_filenames :1; + unsigned int no_force_dots :1; + unsigned int allow_lowercase :1; + unsigned int allow_full_ascii :1; + + unsigned int relaxed_vol_atts : 1; + + /** Allow paths on Joliet tree to be larger than 240 bytes */ + unsigned int joliet_longer_paths :1; + + /* + * Mode replace. If one of these flags is set, the correspodent values are + * replaced with values below. + */ + unsigned int replace_uid :1; + unsigned int replace_gid :1; + unsigned int replace_file_mode :1; + unsigned int replace_dir_mode :1; + unsigned int replace_timestamps :1; + + uid_t uid; + gid_t gid; + mode_t file_mode; + mode_t dir_mode; + time_t timestamp; + + /** + * if sort files or not. Sorting is based of the weight of each file + */ + int sort_files; + + /** + * In the CD, each file must have an unique inode number. So each + * time we add a new file, this is incremented. + */ + ino_t ino; + + char *input_charset; + char *output_charset; + + unsigned int appendable : 1; + uint32_t ms_block; /**< start block for a ms image */ + time_t now; /**< Time at which writing began. */ + + /** Total size of the output. This only includes the current volume. */ + off_t total_size; + uint32_t vol_space_size; + + /* Bytes already written, just for progress notification */ + off_t bytes_written; + int percent_written; + + /* + * Block being processed, either during image writing or structure + * size calculation. + */ + uint32_t curblock; + + /* + * number of dirs in ECMA-119 tree, computed together with dir position, + * and needed for path table computation in a efficient way + */ + size_t ndirs; + uint32_t path_table_size; + uint32_t l_path_table_pos; + uint32_t m_path_table_pos; + + /* + * Joliet related information + */ + JolietNode *joliet_root; + size_t joliet_ndirs; + uint32_t joliet_path_table_size; + uint32_t joliet_l_path_table_pos; + uint32_t joliet_m_path_table_pos; + + /* + * ISO 9660:1999 related information + */ + Iso1999Node *iso1999_root; + size_t iso1999_ndirs; + uint32_t iso1999_path_table_size; + uint32_t iso1999_l_path_table_pos; + uint32_t iso1999_m_path_table_pos; + + /* + * El-Torito related information + */ + struct el_torito_boot_catalog *catalog; + IsoFileSrc *cat; /**< location of the boot catalog in the new image */ + IsoFileSrc *bootimg; /**< location of the boot image in the new image */ + + /* + * Number of pad blocks that we need to write. Padding blocks are blocks + * filled by 0s that we put between the directory structures and the file + * data. These padding blocks are added by libisofs to improve the handling + * of image growing. The idea is that the first blocks in the image are + * overwritten with the volume descriptors of the new image. These first + * blocks usually correspond to the volume descriptors and directory + * structure of the old image, and can be safety overwritten. However, + * with very small images they might correspond to valid data. To ensure + * this never happens, what we do is to add padding bytes, to ensure no + * file data is written in the first 64 KiB, that are the bytes we usually + * overwrite. + */ + uint32_t pad_blocks; + + size_t nwriters; + IsoImageWriter **writers; + + /* tree of files sources */ + IsoRBTree *files; + + /* Buffer for communication between burn_source and writer thread */ + IsoRingBuffer *buffer; + + /* writer thread descriptor */ + pthread_t wthread; + pthread_attr_t th_attr; +}; + +#define BP(a,b) [(b) - (a) + 1] + +/* ECMA-119, 8.4 */ +struct ecma119_pri_vol_desc +{ + uint8_t vol_desc_type BP(1, 1); + uint8_t std_identifier BP(2, 6); + uint8_t vol_desc_version BP(7, 7); + uint8_t unused1 BP(8, 8); + uint8_t system_id BP(9, 40); + uint8_t volume_id BP(41, 72); + uint8_t unused2 BP(73, 80); + uint8_t vol_space_size BP(81, 88); + uint8_t unused3 BP(89, 120); + uint8_t vol_set_size BP(121, 124); + uint8_t vol_seq_number BP(125, 128); + uint8_t block_size BP(129, 132); + uint8_t path_table_size BP(133, 140); + uint8_t l_path_table_pos BP(141, 144); + uint8_t opt_l_path_table_pos BP(145, 148); + uint8_t m_path_table_pos BP(149, 152); + uint8_t opt_m_path_table_pos BP(153, 156); + uint8_t root_dir_record BP(157, 190); + uint8_t vol_set_id BP(191, 318); + uint8_t publisher_id BP(319, 446); + uint8_t data_prep_id BP(447, 574); + uint8_t application_id BP(575, 702); + uint8_t copyright_file_id BP(703, 739); + uint8_t abstract_file_id BP(740, 776); + uint8_t bibliographic_file_id BP(777, 813); + uint8_t vol_creation_time BP(814, 830); + uint8_t vol_modification_time BP(831, 847); + uint8_t vol_expiration_time BP(848, 864); + uint8_t vol_effective_time BP(865, 881); + uint8_t file_structure_version BP(882, 882); + uint8_t reserved1 BP(883, 883); + uint8_t app_use BP(884, 1395); + uint8_t reserved2 BP(1396, 2048); +}; + +/* ECMA-119, 8.5 */ +struct ecma119_sup_vol_desc +{ + uint8_t vol_desc_type BP(1, 1); + uint8_t std_identifier BP(2, 6); + uint8_t vol_desc_version BP(7, 7); + uint8_t vol_flags BP(8, 8); + uint8_t system_id BP(9, 40); + uint8_t volume_id BP(41, 72); + uint8_t unused2 BP(73, 80); + uint8_t vol_space_size BP(81, 88); + uint8_t esc_sequences BP(89, 120); + uint8_t vol_set_size BP(121, 124); + uint8_t vol_seq_number BP(125, 128); + uint8_t block_size BP(129, 132); + uint8_t path_table_size BP(133, 140); + uint8_t l_path_table_pos BP(141, 144); + uint8_t opt_l_path_table_pos BP(145, 148); + uint8_t m_path_table_pos BP(149, 152); + uint8_t opt_m_path_table_pos BP(153, 156); + uint8_t root_dir_record BP(157, 190); + uint8_t vol_set_id BP(191, 318); + uint8_t publisher_id BP(319, 446); + uint8_t data_prep_id BP(447, 574); + uint8_t application_id BP(575, 702); + uint8_t copyright_file_id BP(703, 739); + uint8_t abstract_file_id BP(740, 776); + uint8_t bibliographic_file_id BP(777, 813); + uint8_t vol_creation_time BP(814, 830); + uint8_t vol_modification_time BP(831, 847); + uint8_t vol_expiration_time BP(848, 864); + uint8_t vol_effective_time BP(865, 881); + uint8_t file_structure_version BP(882, 882); + uint8_t reserved1 BP(883, 883); + uint8_t app_use BP(884, 1395); + uint8_t reserved2 BP(1396, 2048); +}; + +/* ECMA-119, 8.2 */ +struct ecma119_boot_rec_vol_desc +{ + uint8_t vol_desc_type BP(1, 1); + uint8_t std_identifier BP(2, 6); + uint8_t vol_desc_version BP(7, 7); + uint8_t boot_sys_id BP(8, 39); + uint8_t boot_id BP(40, 71); + uint8_t boot_catalog BP(72, 75); + uint8_t unused BP(76, 2048); +}; + +/* ECMA-119, 9.1 */ +struct ecma119_dir_record +{ + uint8_t len_dr BP(1, 1); + uint8_t len_xa BP(2, 2); + uint8_t block BP(3, 10); + uint8_t length BP(11, 18); + uint8_t recording_time BP(19, 25); + uint8_t flags BP(26, 26); + uint8_t file_unit_size BP(27, 27); + uint8_t interleave_gap_size BP(28, 28); + uint8_t vol_seq_number BP(29, 32); + uint8_t len_fi BP(33, 33); + uint8_t file_id BP(34, 34); /* 34 to 33+len_fi */ + /* padding field (if len_fi is even) */ + /* system use (len_dr - len_su + 1 to len_dr) */ +}; + +/* ECMA-119, 9.4 */ +struct ecma119_path_table_record +{ + uint8_t len_di BP(1, 1); + uint8_t len_xa BP(2, 2); + uint8_t block BP(3, 6); + uint8_t parent BP(7, 8); + uint8_t dir_id BP(9, 9); /* 9 to 8+len_di */ + /* padding field (if len_di is odd) */ +}; + +/* ECMA-119, 8.3 */ +struct ecma119_vol_desc_terminator +{ + uint8_t vol_desc_type BP(1, 1); + uint8_t std_identifier BP(2, 6); + uint8_t vol_desc_version BP(7, 7); + uint8_t reserved BP(8, 2048); +}; + +#endif /*LIBISO_ECMA119_H_*/ diff --git a/libisofs/ecma119_tree.c b/libisofs/ecma119_tree.c new file mode 100644 index 0000000..943fa0a --- /dev/null +++ b/libisofs/ecma119_tree.c @@ -0,0 +1,846 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "ecma119_tree.h" +#include "ecma119.h" +#include "node.h" +#include "util.h" +#include "filesrc.h" +#include "messages.h" +#include "image.h" +#include "stream.h" +#include "eltorito.h" + +#include +#include +#include + +static +int get_iso_name(Ecma119Image *img, IsoNode *iso, char **name) +{ + int ret, relaxed; + char *ascii_name; + char *isoname= NULL; + + if (iso->name == NULL) { + /* it is not necessarily an error, it can be the root */ + return ISO_SUCCESS; + } + + ret = str2ascii(img->input_charset, iso->name, &ascii_name); + if (ret < 0) { + iso_msg_submit(img->image->id, ret, 0, "Can't convert %s", iso->name); + return ret; + } + + if (img->allow_full_ascii) { + relaxed = 2; + } else { + relaxed = (int)img->allow_lowercase; + } + if (iso->type == LIBISO_DIR) { + if (img->max_37_char_filenames) { + isoname = iso_r_dirid(ascii_name, 37, relaxed); + } else if (img->iso_level == 1) { + if (relaxed) { + isoname = iso_r_dirid(ascii_name, 8, relaxed); + } else { + isoname = iso_1_dirid(ascii_name); + } + } else { + if (relaxed) { + isoname = iso_r_dirid(ascii_name, 8, relaxed); + } else { + isoname = iso_2_dirid(ascii_name); + } + } + } else { + if (img->max_37_char_filenames) { + isoname = iso_r_fileid(ascii_name, 36, relaxed, + img->no_force_dots ? 0 : 1); + } else if (img->iso_level == 1) { + if (relaxed) { + isoname = iso_r_fileid(ascii_name, 11, relaxed, + img->no_force_dots ? 0 : 1); + } else { + isoname = iso_1_fileid(ascii_name); + } + } else { + if (relaxed) { + isoname = iso_r_fileid(ascii_name, 30, relaxed, + img->no_force_dots ? 0 : 1); + } else { + isoname = iso_2_fileid(ascii_name); + } + } + } + free(ascii_name); + if (isoname != NULL) { + *name = isoname; + return ISO_SUCCESS; + } else { + /* + * only possible if mem error, as check for empty names is done + * in public tree + */ + return ISO_OUT_OF_MEM; + } +} + +static +int create_ecma119_node(Ecma119Image *img, IsoNode *iso, Ecma119Node **node) +{ + Ecma119Node *ecma; + + ecma = calloc(1, sizeof(Ecma119Node)); + if (ecma == NULL) { + return ISO_OUT_OF_MEM; + } + + /* take a ref to the IsoNode */ + ecma->node = iso; + iso_node_ref(iso); + + /* TODO #00009 : add true support for harlinks and inode numbers */ + ecma->nlink = 1; + ecma->ino = ++img->ino; + + *node = ecma; + return ISO_SUCCESS; +} + +/** + * Create a new ECMA-119 node representing a directory from a iso directory + * node. + */ +static +int create_dir(Ecma119Image *img, IsoDir *iso, Ecma119Node **node) +{ + int ret; + Ecma119Node **children; + struct ecma119_dir_info *dir_info; + + children = calloc(1, sizeof(void*) * iso->nchildren); + if (children == NULL) { + return ISO_OUT_OF_MEM; + } + + dir_info = calloc(1, sizeof(struct ecma119_dir_info)); + if (dir_info == NULL) { + free(children); + return ISO_OUT_OF_MEM; + } + + ret = create_ecma119_node(img, (IsoNode*)iso, node); + if (ret < 0) { + free(children); + free(dir_info); + return ret; + } + (*node)->type = ECMA119_DIR; + (*node)->info.dir = dir_info; + (*node)->info.dir->nchildren = 0; + (*node)->info.dir->children = children; + return ISO_SUCCESS; +} + +/** + * Create a new ECMA-119 node representing a regular file from a iso file + * node. + */ +static +int create_file(Ecma119Image *img, IsoFile *iso, Ecma119Node **node) +{ + int ret; + IsoFileSrc *src; + off_t size; + + size = iso_stream_get_size(iso->stream); + if (size > (off_t)0xffffffff) { + return iso_msg_submit(img->image->id, ISO_FILE_TOO_BIG, 0, + "File \"%s\" can't be added to image because " + "is greater than 4GB", iso->node.name); + } + + ret = iso_file_src_create(img, iso, &src); + if (ret < 0) { + return ret; + } + + ret = create_ecma119_node(img, (IsoNode*)iso, node); + if (ret < 0) { + /* + * the src doesn't need to be freed, it is free together with + * the Ecma119Image + */ + return ret; + } + (*node)->type = ECMA119_FILE; + (*node)->info.file = src; + + return ret; +} + +/** + * Create a new ECMA-119 node representing a regular file from an El-Torito + * boot catalog + */ +static +int create_boot_cat(Ecma119Image *img, IsoBoot *iso, Ecma119Node **node) +{ + int ret; + IsoFileSrc *src; + + ret = el_torito_catalog_file_src_create(img, &src); + if (ret < 0) { + return ret; + } + + ret = create_ecma119_node(img, (IsoNode*)iso, node); + if (ret < 0) { + /* + * the src doesn't need to be freed, it is free together with + * the Ecma119Image + */ + return ret; + } + (*node)->type = ECMA119_FILE; + (*node)->info.file = src; + + return ret; +} + +/** + * Create a new ECMA-119 node representing a symbolic link from a iso symlink + * node. + */ +static +int create_symlink(Ecma119Image *img, IsoSymlink *iso, Ecma119Node **node) +{ + int ret; + + ret = create_ecma119_node(img, (IsoNode*)iso, node); + if (ret < 0) { + return ret; + } + (*node)->type = ECMA119_SYMLINK; + return ISO_SUCCESS; +} + +/** + * Create a new ECMA-119 node representing a special file. + */ +static +int create_special(Ecma119Image *img, IsoSpecial *iso, Ecma119Node **node) +{ + int ret; + + ret = create_ecma119_node(img, (IsoNode*)iso, node); + if (ret < 0) { + return ret; + } + (*node)->type = ECMA119_SPECIAL; + return ISO_SUCCESS; +} + +void ecma119_node_free(Ecma119Node *node) +{ + if (node == NULL) { + return; + } + if (node->type == ECMA119_DIR) { + int i; + for (i = 0; i < node->info.dir->nchildren; i++) { + ecma119_node_free(node->info.dir->children[i]); + } + free(node->info.dir->children); + free(node->info.dir); + } + free(node->iso_name); + iso_node_unref(node->node); + free(node); +} + +/** + * + * @return + * 1 success, 0 node ignored, < 0 error + * + */ +static +int create_tree(Ecma119Image *image, IsoNode *iso, Ecma119Node **tree, + int depth, int pathlen) +{ + int ret; + Ecma119Node *node; + int max_path; + char *iso_name= NULL; + + if (image == NULL || iso == NULL || tree == NULL) { + return ISO_NULL_POINTER; + } + + if (iso->hidden & LIBISO_HIDE_ON_RR) { + /* file will be ignored */ + return 0; + } + ret = get_iso_name(image, iso, &iso_name); + if (ret < 0) { + return ret; + } + max_path = pathlen + 1 + (iso_name ? strlen(iso_name) : 0); + if (!image->rockridge) { + if ((iso->type == LIBISO_DIR && depth > 8) && !image->allow_deep_paths) { + free(iso_name); + return iso_msg_submit(image->image->id, ISO_FILE_IMGPATH_WRONG, 0, + "File \"%s\" can't be added, because directory depth " + "is greater than 8.", iso->name); + } else if (max_path > 255 && !image->allow_longer_paths) { + free(iso_name); + return iso_msg_submit(image->image->id, ISO_FILE_IMGPATH_WRONG, 0, + "File \"%s\" can't be added, because path length " + "is greater than 255 characters", iso->name); + } + } + + switch (iso->type) { + case LIBISO_FILE: + ret = create_file(image, (IsoFile*)iso, &node); + break; + case LIBISO_SYMLINK: + if (image->rockridge) { + ret = create_symlink(image, (IsoSymlink*)iso, &node); + } else { + /* symlinks are only supported when RR is enabled */ + ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0, + "File \"%s\" ignored. Symlinks need RockRidge extensions.", + iso->name); + } + break; + case LIBISO_SPECIAL: + if (image->rockridge) { + ret = create_special(image, (IsoSpecial*)iso, &node); + } else { + /* symlinks are only supported when RR is enabled */ + ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0, + "File \"%s\" ignored. Special files need RockRidge extensions.", + iso->name); + } + break; + case LIBISO_BOOT: + if (image->eltorito) { + ret = create_boot_cat(image, (IsoBoot*)iso, &node); + } else { + /* log and ignore */ + ret = iso_msg_submit(image->image->id, ISO_FILE_IGNORED, 0, + "El-Torito catalog found on a image without El-Torito.", + iso->name); + } + break; + case LIBISO_DIR: + { + IsoNode *pos; + IsoDir *dir = (IsoDir*)iso; + ret = create_dir(image, dir, &node); + if (ret < 0) { + return ret; + } + pos = dir->children; + while (pos) { + int cret; + Ecma119Node *child; + cret = create_tree(image, pos, &child, depth + 1, max_path); + if (cret < 0) { + /* error */ + ecma119_node_free(node); + ret = cret; + break; + } else if (cret == ISO_SUCCESS) { + /* add child to this node */ + int nchildren = node->info.dir->nchildren++; + node->info.dir->children[nchildren] = child; + child->parent = node; + } + pos = pos->next; + } + } + break; + default: + /* should never happen */ + return ISO_ASSERT_FAILURE; + } + if (ret <= 0) { + free(iso_name); + return ret; + } + node->iso_name = iso_name; + *tree = node; + return ISO_SUCCESS; +} + +/** + * Compare the iso name of two ECMA-119 nodes + */ +static +int cmp_node_name(const void *f1, const void *f2) +{ + Ecma119Node *f = *((Ecma119Node**)f1); + Ecma119Node *g = *((Ecma119Node**)f2); + return strcmp(f->iso_name, g->iso_name); +} + +/** + * Sorts a the children of each directory in the ECMA-119 tree represented + * by \p root, acording to the order specified in ECMA-119, section 9.3. + */ +static +void sort_tree(Ecma119Node *root) +{ + size_t i; + + qsort(root->info.dir->children, root->info.dir->nchildren, sizeof(void*), + cmp_node_name); + for (i = 0; i < root->info.dir->nchildren; i++) { + if (root->info.dir->children[i]->type == ECMA119_DIR) + sort_tree(root->info.dir->children[i]); + } +} + +/** + * Ensures that the ISO name of each children of the given dir is unique, + * changing some of them if needed. + * It also ensures that resulting filename is always <= than given + * max_name_len, including extension. If needed, the extension will be reduced, + * but never under 3 characters. + */ +static +int mangle_single_dir(Ecma119Image *img, Ecma119Node *dir, int max_file_len, + int max_dir_len) +{ + int ret; + int i, nchildren; + Ecma119Node **children; + IsoHTable *table; + int need_sort = 0; + + nchildren = dir->info.dir->nchildren; + children = dir->info.dir->children; + + /* a hash table will temporary hold the names, for fast searching */ + ret = iso_htable_create((nchildren * 100) / 80, iso_str_hash, + (compare_function_t)strcmp, &table); + if (ret < 0) { + return ret; + } + for (i = 0; i < nchildren; ++i) { + char *name = children[i]->iso_name; + ret = iso_htable_add(table, name, name); + if (ret < 0) { + goto mangle_cleanup; + } + } + + for (i = 0; i < nchildren; ++i) { + char *name, *ext; + char full_name[40]; + int max; /* computed max len for name, without extension */ + int j = i; + int digits = 1; /* characters to change per name */ + + /* first, find all child with same name */ + while (j + 1 < nchildren && !cmp_node_name(children + i, children + j + + 1)) { + ++j; + } + if (j == i) { + /* name is unique */ + continue; + } + + /* + * A max of 7 characters is good enought, it allows handling up to + * 9,999,999 files with same name. We can increment this to + * max_name_len, but the int_pow() function must then be modified + * to return a bigger integer. + */ + while (digits < 8) { + int ok, k; + char *dot; + int change = 0; /* number to be written */ + + /* copy name to buffer */ + strcpy(full_name, children[i]->iso_name); + + /* compute name and extension */ + dot = strrchr(full_name, '.'); + if (dot != NULL && children[i]->type != ECMA119_DIR) { + + /* + * File (not dir) with extension + * Note that we don't need to check for placeholders, as + * tree reparent happens later, so no placeholders can be + * here at this time. + */ + int extlen; + full_name[dot - full_name] = '\0'; + name = full_name; + ext = dot + 1; + + /* + * For iso level 1 we force ext len to be 3, as name + * can't grow on the extension space + */ + extlen = (max_file_len == 12) ? 3 : strlen(ext); + max = max_file_len - extlen - 1 - digits; + if (max <= 0) { + /* this can happen if extension is too long */ + if (extlen + max > 3) { + /* + * reduce extension len, to give name an extra char + * note that max is negative or 0 + */ + extlen = extlen + max - 1; + ext[extlen] = '\0'; + max = max_file_len - extlen - 1 - digits; + } else { + /* + * error, we don't support extensions < 3 + * This can't happen with current limit of digits. + */ + ret = ISO_ERROR; + goto mangle_cleanup; + } + } + /* ok, reduce name by digits */ + if (name + max < dot) { + name[max] = '\0'; + } + } else { + /* Directory, or file without extension */ + if (children[i]->type == ECMA119_DIR) { + max = max_dir_len - digits; + dot = NULL; /* dots have no meaning in dirs */ + } else { + max = max_file_len - digits; + } + name = full_name; + if (max < strlen(name)) { + name[max] = '\0'; + } + /* let ext be an empty string */ + ext = name + strlen(name); + } + + ok = 1; + /* change name of each file */ + for (k = i; k <= j; ++k) { + char tmp[40]; + char fmt[16]; + if (dot != NULL) { + sprintf(fmt, "%%s%%0%dd.%%s", digits); + } else { + sprintf(fmt, "%%s%%0%dd%%s", digits); + } + while (1) { + sprintf(tmp, fmt, name, change, ext); + ++change; + if (change > int_pow(10, digits)) { + ok = 0; + break; + } + if (!iso_htable_get(table, tmp, NULL)) { + /* the name is unique, so it can be used */ + break; + } + } + if (ok) { + char *new = strdup(tmp); + if (new == NULL) { + ret = ISO_OUT_OF_MEM; + goto mangle_cleanup; + } + iso_msg_debug(img->image->id, "\"%s\" renamed to \"%s\"", + children[k]->iso_name, new); + + iso_htable_remove_ptr(table, children[k]->iso_name, NULL); + free(children[k]->iso_name); + children[k]->iso_name = new; + iso_htable_add(table, new, new); + + /* + * if we change a name we need to sort again children + * at the end + */ + need_sort = 1; + } else { + /* we need to increment digits */ + break; + } + } + if (ok) { + break; + } else { + ++digits; + } + } + if (digits == 8) { + ret = ISO_MANGLE_TOO_MUCH_FILES; + goto mangle_cleanup; + } + i = j; + } + + /* + * If needed, sort again the files inside dir + */ + if (need_sort) { + qsort(children, nchildren, sizeof(void*), cmp_node_name); + } + + ret = ISO_SUCCESS; + +mangle_cleanup : ; + iso_htable_destroy(table, NULL); + return ret; +} + +static +int mangle_dir(Ecma119Image *img, Ecma119Node *dir, int max_file_len, + int max_dir_len) +{ + int ret; + size_t i; + + ret = mangle_single_dir(img, dir, max_file_len, max_dir_len); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < dir->info.dir->nchildren; ++i) { + if (dir->info.dir->children[i]->type == ECMA119_DIR) { + ret = mangle_dir(img, dir->info.dir->children[i], max_file_len, + max_dir_len); + if (ret < 0) { + /* error */ + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int mangle_tree(Ecma119Image *img, int recurse) +{ + int max_file, max_dir; + + if (img->max_37_char_filenames) { + max_file = max_dir = 37; + } else if (img->iso_level == 1) { + max_file = 12; /* 8 + 3 + 1 */ + max_dir = 8; + } else { + max_file = max_dir = 31; + } + if (recurse) { + return mangle_dir(img, img->root, max_file, max_dir); + } else { + return mangle_single_dir(img, img->root, max_file, max_dir); + } +} + +/** + * Create a new ECMA-119 node representing a placeholder for a relocated + * dir. + * + * See IEEE P1282, section 4.1.5 for details + */ +static +int create_placeholder(Ecma119Node *parent, Ecma119Node *real, + Ecma119Node **node) +{ + Ecma119Node *ret; + + ret = calloc(1, sizeof(Ecma119Node)); + if (ret == NULL) { + return ISO_OUT_OF_MEM; + } + + /* + * TODO + * If real is a dir, while placeholder is a file, ISO name restricctions + * are different, what to do? + */ + ret->iso_name = strdup(real->iso_name); + if (ret->iso_name == NULL) { + free(ret); + return ISO_OUT_OF_MEM; + } + + /* take a ref to the IsoNode */ + ret->node = real->node; + iso_node_ref(real->node); + ret->parent = parent; + ret->type = ECMA119_PLACEHOLDER; + ret->info.real_me = real; + ret->ino = real->ino; + ret->nlink = real->nlink; + + *node = ret; + return ISO_SUCCESS; +} + +static +size_t max_child_name_len(Ecma119Node *dir) +{ + size_t ret = 0, i; + for (i = 0; i < dir->info.dir->nchildren; i++) { + size_t len = strlen(dir->info.dir->children[i]->iso_name); + ret = MAX(ret, len); + } + return ret; +} + +/** + * Relocates a directory, as specified in Rock Ridge Specification + * (see IEEE P1282, section 4.1.5). This is needed when the number of levels + * on a directory hierarchy exceeds 8, or the length of a path is higher + * than 255 characters, as specified in ECMA-119, section 6.8.2.1 + */ +static +int reparent(Ecma119Node *child, Ecma119Node *parent) +{ + int ret; + size_t i; + Ecma119Node *placeholder; + + /* replace the child in the original parent with a placeholder */ + for (i = 0; i < child->parent->info.dir->nchildren; i++) { + if (child->parent->info.dir->children[i] == child) { + ret = create_placeholder(child->parent, child, &placeholder); + if (ret < 0) { + return ret; + } + child->parent->info.dir->children[i] = placeholder; + break; + } + } + + /* just for debug, this should never happen... */ + if (i == child->parent->info.dir->nchildren) { + return ISO_ASSERT_FAILURE; + } + + /* keep track of the real parent */ + child->info.dir->real_parent = child->parent; + + /* add the child to its new parent */ + child->parent = parent; + parent->info.dir->nchildren++; + parent->info.dir->children = realloc(parent->info.dir->children, + sizeof(void*) * parent->info.dir->nchildren); + parent->info.dir->children[parent->info.dir->nchildren - 1] = child; + return ISO_SUCCESS; +} + +/** + * Reorder the tree, if necessary, to ensure that + * - the depth is at most 8 + * - each path length is at most 255 characters + * This restriction is imposed by ECMA-119 specification (ECMA-119, 6.8.2.1). + * + * @param dir + * Dir we are currently processing + * @param level + * Level of the directory in the hierarchy + * @param pathlen + * Length of the path until dir, including it + * @return + * 1 success, < 0 error + */ +static +int reorder_tree(Ecma119Image *img, Ecma119Node *dir, int level, int pathlen) +{ + int ret; + size_t max_path; + + max_path = pathlen + 1 + max_child_name_len(dir); + + if (level > 8 || max_path > 255) { + ret = reparent(dir, img->root); + if (ret < 0) { + return ret; + } + + /* + * we are appended to the root's children now, so there is no + * need to recurse (the root will hit us again) + */ + } else { + size_t i; + + for (i = 0; i < dir->info.dir->nchildren; i++) { + Ecma119Node *child = dir->info.dir->children[i]; + if (child->type == ECMA119_DIR) { + int newpathlen = pathlen + 1 + strlen(child->iso_name); + ret = reorder_tree(img, child, level + 1, newpathlen); + if (ret < 0) { + return ret; + } + } + } + } + return ISO_SUCCESS; +} + +int ecma119_tree_create(Ecma119Image *img) +{ + int ret; + Ecma119Node *root; + + ret = create_tree(img, (IsoNode*)img->image->root, &root, 1, 0); + if (ret <= 0) { + if (ret == 0) { + /* unexpected error, root ignored!! This can't happen */ + ret = ISO_ASSERT_FAILURE; + } + return ret; + } + img->root = root; + + iso_msg_debug(img->image->id, "Sorting the low level tree..."); + sort_tree(root); + + iso_msg_debug(img->image->id, "Mangling names..."); + ret = mangle_tree(img, 1); + if (ret < 0) { + return ret; + } + + if (img->rockridge && !img->allow_deep_paths) { + + /* reorder the tree, acording to RRIP, 4.1.5 */ + ret = reorder_tree(img, img->root, 1, 0); + if (ret < 0) { + return ret; + } + + /* + * and we need to remangle the root directory, as the function + * above could insert new directories into the root. + * Note that recurse = 0, as we don't need to recurse. + */ + ret = mangle_tree(img, 0); + if (ret < 0) { + return ret; + } + } + + return ISO_SUCCESS; +} diff --git a/libisofs/ecma119_tree.h b/libisofs/ecma119_tree.h new file mode 100644 index 0000000..0cd05ee --- /dev/null +++ b/libisofs/ecma119_tree.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_ECMA119_TREE_H_ +#define LIBISO_ECMA119_TREE_H_ + +#include "libisofs.h" +#include "ecma119.h" + +enum ecma119_node_type { + ECMA119_FILE, + ECMA119_DIR, + ECMA119_SYMLINK, + ECMA119_SPECIAL, + ECMA119_PLACEHOLDER +}; + +/** + * Struct with info about a node representing a directory + */ +struct ecma119_dir_info +{ + /* Block where the directory entries will be written on image */ + size_t block; + + size_t nchildren; + Ecma119Node **children; + + /* + * Size of the dir, i.e., sum of the lengths of all directory records. + * It is computed by calc_dir_size() [ecma119.c]. + * Note that this don't include the length of any SUSP Continuation + * Area needed by the dir, but it includes the size of the SUSP entries + * than fit in the directory records System Use Field. + */ + size_t len; + + /** + * Real parent if the dir has been reallocated. NULL otherwise. + */ + Ecma119Node *real_parent; +}; + +/** + * A node for a tree containing all the information necessary for writing + * an ISO9660 volume. + */ +struct ecma119_node +{ + /** + * Name in ASCII, conforming to selected ISO level. + * Version number is not include, it is added on the fly + */ + char *iso_name; + + Ecma119Node *parent; + + IsoNode *node; /*< reference to the iso node */ + + /* TODO #00009 : add true support for harlinks and inode numbers */ + ino_t ino; + nlink_t nlink; + + /**< file, symlink, special, directory or placeholder */ + enum ecma119_node_type type; + union + { + IsoFileSrc *file; + struct ecma119_dir_info *dir; + /** this field points to the relocated directory. */ + Ecma119Node *real_me; + } info; +}; + +/** + * + */ +int ecma119_tree_create(Ecma119Image *img); + +/** + * Free an Ecma119Node, and its children if node is a dir + */ +void ecma119_node_free(Ecma119Node *node); + +#endif /*LIBISO_ECMA119_TREE_H_*/ diff --git a/libisofs/eltorito.c b/libisofs/eltorito.c new file mode 100644 index 0000000..f8e94c9 --- /dev/null +++ b/libisofs/eltorito.c @@ -0,0 +1,913 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "eltorito.h" +#include "stream.h" +#include "fsource.h" +#include "filesrc.h" +#include "image.h" +#include "messages.h" +#include "writer.h" + +#include +#include + +/** + * This table should be written with accuracy values at offset + * 8 of boot image, when used ISOLINUX boot loader + */ +struct boot_info_table { + uint8_t bi_pvd BP(1, 4); /* LBA of primary volume descriptor */ + uint8_t bi_file BP(5, 8); /* LBA of boot file */ + uint8_t bi_length BP(9, 12); /* Length of boot file */ + uint8_t bi_csum BP(13, 16); /* Checksum of boot file */ + uint8_t bi_reserved BP(17, 56); /* Reserved */ +}; + +/** + * Structure for each one of the four entries in a partition table on a + * hard disk image. + */ +struct partition_desc { + uint8_t boot_ind; + uint8_t begin_chs[3]; + uint8_t type; + uint8_t end_chs[3]; + uint8_t start[4]; + uint8_t size[4]; +}; + +/** + * Structures for a Master Boot Record of a hard disk image. + */ +struct hard_disc_mbr { + uint8_t code_area[440]; + uint8_t opt_disk_sg[4]; + uint8_t pad[2]; + struct partition_desc partition[4]; + uint8_t sign1; + uint8_t sign2; +}; + +/** + * Sets the load segment for the initial boot image. This is only for + * no emulation boot images, and is a NOP for other image types. + */ +void el_torito_set_load_seg(ElToritoBootImage *bootimg, short segment) +{ + if (bootimg->type != ELTORITO_NO_EMUL) + return; + bootimg->load_seg = segment; +} + +/** + * Sets the number of sectors (512b) to be load at load segment during + * the initial boot procedure. This is only for no emulation boot images, + * and is a NOP for other image types. + */ +void el_torito_set_load_size(ElToritoBootImage *bootimg, short sectors) +{ + if (bootimg->type != ELTORITO_NO_EMUL) + return; + bootimg->load_size = sectors; +} + +/** + * Marks the specified boot image as not bootable + */ +void el_torito_set_no_bootable(ElToritoBootImage *bootimg) +{ + bootimg->bootable = 0; +} + +/** + * Specifies that this image needs to be patched. This involves the writting + * of a 56 bytes boot information table at offset 8 of the boot image file. + * The original boot image file won't be modified. + * This is needed for isolinux boot images. + */ +void el_torito_patch_isolinux_image(ElToritoBootImage *bootimg) +{ + bootimg->isolinux = 1; +} + +static +int iso_tree_add_boot_node(IsoDir *parent, const char *name, IsoBoot **boot) +{ + IsoBoot *node; + IsoNode **pos; + time_t now; + + if (parent == NULL || name == NULL || boot == NULL) { + return ISO_NULL_POINTER; + } + if (boot) { + *boot = NULL; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + /* find place where to insert */ + pos = &(parent->children); + while (*pos != NULL && strcmp((*pos)->name, name) < 0) { + pos = &((*pos)->next); + } + if (*pos != NULL && !strcmp((*pos)->name, name)) { + /* a node with same name already exists */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + + node = calloc(1, sizeof(IsoBoot)); + if (node == NULL) { + return ISO_OUT_OF_MEM; + } + + node->node.refcount = 1; + node->node.type = LIBISO_BOOT; + node->node.name = strdup(name); + if (node->node.name == NULL) { + free(node); + return ISO_OUT_OF_MEM; + } + + /* atributes from parent */ + node->node.mode = S_IFREG | (parent->node.mode & 0444); + node->node.uid = parent->node.uid; + node->node.gid = parent->node.gid; + node->node.hidden = parent->node.hidden; + + /* current time */ + now = time(NULL); + node->node.atime = now; + node->node.ctime = now; + node->node.mtime = now; + + /* add to dir */ + node->node.parent = parent; + node->node.next = *pos; + *pos = (IsoNode*)node; + + if (boot) { + *boot = node; + } + return ++parent->nchildren; +} + + +static +int create_image(IsoImage *image, const char *image_path, + enum eltorito_boot_media_type type, + struct el_torito_boot_image **bootimg) +{ + int ret; + struct el_torito_boot_image *boot; + int boot_media_type = 0; + int load_sectors = 0; /* number of sector to load */ + unsigned char partition_type = 0; + IsoNode *imgfile; + IsoStream *stream; + + ret = iso_tree_path_to_node(image, image_path, &imgfile); + if (ret < 0) { + return ret; + } + if (ret == 0) { + return ISO_NODE_DOESNT_EXIST; + } + + if (imgfile->type != LIBISO_FILE) { + return ISO_BOOT_IMAGE_NOT_VALID; + } + + stream = ((IsoFile*)imgfile)->stream; + + /* we need to read the image at least two times */ + if (!iso_stream_is_repeatable(stream)) { + return ISO_BOOT_IMAGE_NOT_VALID; + } + + switch (type) { + case ELTORITO_FLOPPY_EMUL: + switch (iso_stream_get_size(stream)) { + case 1200 * 1024: + boot_media_type = 1; /* 1.2 meg diskette */ + break; + case 1440 * 1024: + boot_media_type = 2; /* 1.44 meg diskette */ + break; + case 2880 * 1024: + boot_media_type = 3; /* 2.88 meg diskette */ + break; + default: + iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, + "Invalid image size %d Kb. Must be one of 1.2, 1.44" + "or 2.88 Mb", iso_stream_get_size(stream) / 1024); + return ISO_BOOT_IMAGE_NOT_VALID; + break; + } + /* it seems that for floppy emulation we need to load + * a single sector (512b) */ + load_sectors = 1; + break; + case ELTORITO_HARD_DISC_EMUL: + { + size_t i; + struct hard_disc_mbr mbr; + int used_partition; + + /* read the MBR on disc and get the type of the partition */ + ret = iso_stream_open(stream); + if (ret < 0) { + iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, ret, + "Can't open image file."); + return ret; + } + ret = iso_stream_read(stream, &mbr, sizeof(mbr)); + iso_stream_close(stream); + if (ret != sizeof(mbr)) { + iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, + "Can't read MBR from image file."); + return ret < 0 ? ret : ISO_FILE_READ_ERROR; + } + + /* check valid MBR signature */ + if ( mbr.sign1 != 0x55 || mbr.sign2 != 0xAA ) { + iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, + "Invalid MBR. Wrong signature."); + return ISO_BOOT_IMAGE_NOT_VALID; + } + + /* ensure single partition */ + used_partition = -1; + for (i = 0; i < 4; ++i) { + if (mbr.partition[i].type != 0) { + /* it's an used partition */ + if (used_partition != -1) { + iso_msg_submit(image->id, ISO_BOOT_IMAGE_NOT_VALID, 0, + "Invalid MBR. At least 2 partitions: %d and " + "%d, are being used\n", used_partition, i); + return ISO_BOOT_IMAGE_NOT_VALID; + } else + used_partition = i; + } + } + partition_type = mbr.partition[used_partition].type; + } + boot_media_type = 4; + + /* only load the MBR */ + load_sectors = 1; + break; + case ELTORITO_NO_EMUL: + boot_media_type = 0; + break; + } + + boot = calloc(1, sizeof(struct el_torito_boot_image)); + if (boot == NULL) { + return ISO_OUT_OF_MEM; + } + boot->image = (IsoFile*)imgfile; + iso_node_ref(imgfile); /* get our ref */ + boot->bootable = 1; + boot->type = boot_media_type; + boot->load_size = load_sectors; + boot->partition_type = partition_type; + + if (bootimg) { + *bootimg = boot; + } + + return ISO_SUCCESS; +} + +int iso_image_set_boot_image(IsoImage *image, const char *image_path, + enum eltorito_boot_media_type type, + const char *catalog_path, + ElToritoBootImage **boot) +{ + int ret; + struct el_torito_boot_catalog *catalog; + ElToritoBootImage *boot_image= NULL; + IsoBoot *cat_node= NULL; + + if (image == NULL || image_path == NULL || catalog_path == NULL) { + return ISO_NULL_POINTER; + } + if (image->bootcat != NULL) { + return ISO_IMAGE_ALREADY_BOOTABLE; + } + + /* create the node for the catalog */ + { + IsoDir *parent; + char *catdir = NULL, *catname = NULL; + catdir = strdup(catalog_path); + if (catdir == NULL) { + return ISO_OUT_OF_MEM; + } + + /* get both the dir and the name */ + catname = strrchr(catdir, '/'); + if (catname == NULL) { + free(catdir); + return ISO_WRONG_ARG_VALUE; + } + if (catname == catdir) { + /* we are apending catalog to root node */ + parent = image->root; + } else { + IsoNode *p; + catname[0] = '\0'; + ret = iso_tree_path_to_node(image, catdir, &p); + if (ret <= 0) { + free(catdir); + return ret < 0 ? ret : ISO_NODE_DOESNT_EXIST; + } + if (p->type != LIBISO_DIR) { + free(catdir); + return ISO_WRONG_ARG_VALUE; + } + parent = (IsoDir*)p; + } + catname++; + ret = iso_tree_add_boot_node(parent, catname, &cat_node); + free(catdir); + if (ret < 0) { + return ret; + } + } + + /* create the boot image */ + ret = create_image(image, image_path, type, &boot_image); + if (ret < 0) { + goto boot_image_cleanup; + } + + /* creates the catalog with the given image */ + catalog = malloc(sizeof(struct el_torito_boot_catalog)); + if (catalog == NULL) { + ret = ISO_OUT_OF_MEM; + goto boot_image_cleanup; + } + catalog->image = boot_image; + catalog->node = cat_node; + iso_node_ref((IsoNode*)cat_node); + image->bootcat = catalog; + + if (boot) { + *boot = boot_image; + } + + return ISO_SUCCESS; + +boot_image_cleanup:; + if (cat_node) { + iso_node_take((IsoNode*)cat_node); + iso_node_unref((IsoNode*)cat_node); + } + if (boot_image) { + iso_node_unref((IsoNode*)boot_image->image); + free(boot_image); + } + return ret; +} + +/** + * Get El-Torito boot image of an ISO image, if any. + * + * This can be useful, for example, to check if a volume read from a previous + * session or an existing image is bootable. It can also be useful to get + * the image and catalog tree nodes. An application would want those, for + * example, to prevent the user removing it. + * + * Both nodes are owned by libisofs and should not be freed. You can get your + * own ref with iso_node_ref(). You can can also check if the node is already + * on the tree by getting its parent (note that when reading El-Torito info + * from a previous image, the nodes might not be on the tree even if you haven't + * removed them). Remember that you'll need to get a new ref + * (with iso_node_ref()) before inserting them again to the tree, and probably + * you will also need to set the name or permissions. + * + * @param image + * The image from which to get the boot image. + * @param boot + * If not NULL, it will be filled with a pointer to the boot image, if + * any. That object is owned by the IsoImage and should not be freed by + * the user, nor dereferenced once the last reference to the IsoImage was + * disposed via iso_image_unref(). + * @param imgnode + * When not NULL, it will be filled with the image tree node. No extra ref + * is added, you can use iso_node_ref() to get one if you need it. + * @param catnode + * When not NULL, it will be filled with the catnode tree node. No extra + * ref is added, you can use iso_node_ref() to get one if you need it. + * @return + * 1 on success, 0 is the image is not bootable (i.e., it has no El-Torito + * image), < 0 error. + */ +int iso_image_get_boot_image(IsoImage *image, ElToritoBootImage **boot, + IsoFile **imgnode, IsoBoot **catnode) +{ + if (image == NULL) { + return ISO_NULL_POINTER; + } + if (image->bootcat == NULL) { + return 0; + } + + /* ok, image is bootable */ + if (boot) { + *boot = image->bootcat->image; + } + if (imgnode) { + *imgnode = image->bootcat->image->image; + } + if (catnode) { + *catnode = image->bootcat->node; + } + return ISO_SUCCESS; +} + +/** + * Removes the El-Torito bootable image. + * + * The IsoBoot node that acts as placeholder for the catalog is also removed + * for the image tree, if there. + * If the image is not bootable (don't have el-torito boot image) this function + * just returns. + */ +void iso_image_remove_boot_image(IsoImage *image) +{ + if (image == NULL || image->bootcat == NULL) + return; + + /* + * remove catalog node from its parent + * (the reference will be disposed next) + */ + iso_node_take((IsoNode*)image->bootcat->node); + + /* free boot catalog and image, including references to nodes */ + el_torito_boot_catalog_free(image->bootcat); + image->bootcat = NULL; +} + +void el_torito_boot_catalog_free(struct el_torito_boot_catalog *cat) +{ + struct el_torito_boot_image *image; + + if (cat == NULL) { + return; + } + + image = cat->image; + iso_node_unref((IsoNode*)image->image); + free(image); + iso_node_unref((IsoNode*)cat->node); + free(cat); +} + +/** + * Stream that generates the contents of a El-Torito catalog. + */ +struct catalog_stream +{ + Ecma119Image *target; + uint8_t buffer[BLOCK_SIZE]; + int offset; /* -1 if stream is not openned */ +}; + +static void +write_validation_entry(uint8_t *buf) +{ + size_t i; + int checksum; + + struct el_torito_validation_entry *ve = + (struct el_torito_validation_entry*)buf; + ve->header_id[0] = 1; + ve->platform_id[0] = 0; /* 0: 80x86, 1: PowerPC, 2: Mac */ + ve->key_byte1[0] = 0x55; + ve->key_byte2[0] = 0xAA; + + /* calculate the checksum, to ensure sum of all words is 0 */ + checksum = 0; + for (i = 0; i < sizeof(struct el_torito_validation_entry); i += 2) { + checksum -= (int16_t) ((buf[i+1] << 8) | buf[i]); + } + iso_lsb(ve->checksum, checksum, 2); +} + +/** + * Write one section entry. + * Currently this is used only for default image (the only supported just now) + */ +static void +write_section_entry(uint8_t *buf, Ecma119Image *t) +{ + struct el_torito_boot_image *img; + struct el_torito_section_entry *se = + (struct el_torito_section_entry*)buf; + + img = t->catalog->image; + + se->boot_indicator[0] = img->bootable ? 0x88 : 0x00; + se->boot_media_type[0] = img->type; + iso_lsb(se->load_seg, img->load_seg, 2); + se->system_type[0] = img->partition_type; + iso_lsb(se->sec_count, img->load_size, 2); + iso_lsb(se->block, t->bootimg->block, 4); +} + +static +int catalog_open(IsoStream *stream) +{ + struct catalog_stream *data; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + data = stream->data; + + if (data->offset != -1) { + return ISO_FILE_ALREADY_OPENNED; + } + + memset(data->buffer, 0, BLOCK_SIZE); + + /* fill the buffer with the catalog contents */ + write_validation_entry(data->buffer); + + /* write default entry */ + write_section_entry(data->buffer + 32, data->target); + + data->offset = 0; + return ISO_SUCCESS; +} + +static +int catalog_close(IsoStream *stream) +{ + struct catalog_stream *data; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + data = stream->data; + + if (data->offset == -1) { + return ISO_FILE_NOT_OPENNED; + } + data->offset = -1; + return ISO_SUCCESS; +} + +static +off_t catalog_get_size(IsoStream *stream) +{ + return BLOCK_SIZE; +} + +static +int catalog_read(IsoStream *stream, void *buf, size_t count) +{ + size_t len; + struct catalog_stream *data; + if (stream == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + if (count == 0) { + return ISO_WRONG_ARG_VALUE; + } + data = stream->data; + + if (data->offset == -1) { + return ISO_FILE_NOT_OPENNED; + } + + len = MIN(count, BLOCK_SIZE - data->offset); + memcpy(buf, data->buffer + data->offset, len); + return len; +} + +static +int catalog_is_repeatable(IsoStream *stream) +{ + return 1; +} + +/** + * fs_id will be the id reserved for El-Torito + * dev_id will be 0 for catalog, 1 for boot image (if needed) + * we leave ino_id for future use when we support multiple boot images + */ +static +void catalog_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id) +{ + *fs_id = ISO_ELTORITO_FS_ID; + *dev_id = 0; + *ino_id = 0; +} + +static +char *catalog_get_name(IsoStream *stream) +{ + return strdup("El-Torito Boot Catalog"); +} + +static +void catalog_free(IsoStream *stream) +{ + free(stream->data); +} + +IsoStreamIface catalog_stream_class = { + catalog_open, + catalog_close, + catalog_get_size, + catalog_read, + catalog_is_repeatable, + catalog_get_id, + catalog_get_name, + catalog_free +}; + +/** + * Create an IsoStream for writing El-Torito catalog for a given target. + */ +static +int catalog_stream_new(Ecma119Image *target, IsoStream **stream) +{ + IsoStream *str; + struct catalog_stream *data; + + if (target == NULL || stream == NULL || target->catalog == NULL) { + return ISO_NULL_POINTER; + } + + str = malloc(sizeof(IsoStream)); + if (str == NULL) { + return ISO_OUT_OF_MEM; + } + data = malloc(sizeof(struct catalog_stream)); + if (str == NULL) { + free(str); + return ISO_OUT_OF_MEM; + } + + /* fill data */ + data->target = target; + data->offset = -1; + + str->refcount = 1; + str->data = data; + str->class = &catalog_stream_class; + + *stream = str; + return ISO_SUCCESS; +} + +int el_torito_catalog_file_src_create(Ecma119Image *target, IsoFileSrc **src) +{ + int ret; + IsoFileSrc *file; + IsoStream *stream; + + if (target == NULL || src == NULL || target->catalog == NULL) { + return ISO_OUT_OF_MEM; + } + + if (target->cat != NULL) { + /* catalog file src already created */ + *src = target->cat; + return ISO_SUCCESS; + } + + file = malloc(sizeof(IsoFileSrc)); + if (file == NULL) { + return ISO_OUT_OF_MEM; + } + + ret = catalog_stream_new(target, &stream); + if (ret < 0) { + free(file); + return ret; + } + + /* fill fields */ + file->prev_img = 0; /* TODO allow copy of old img catalog???? */ + file->block = 0; /* to be filled later */ + file->sort_weight = 1000; /* slightly high */ + file->stream = stream; + + ret = iso_file_src_add(target, file, src); + if (ret <= 0) { + iso_stream_unref(stream); + free(file); + } else { + target->cat = *src; + } + return ret; +} + +/******************* EL-TORITO WRITER *******************************/ + +static +int eltorito_writer_compute_data_blocks(IsoImageWriter *writer) +{ + /* nothing to do, the files are written by the file writer */ + return ISO_SUCCESS; +} + +/** + * Write the Boot Record Volume Descriptor (ECMA-119, 8.2) + */ +static +int eltorito_writer_write_vol_desc(IsoImageWriter *writer) +{ + Ecma119Image *t; + struct el_torito_boot_catalog *cat; + struct ecma119_boot_rec_vol_desc vol; + + if (writer == NULL) { + return ISO_NULL_POINTER; + } + + t = writer->target; + cat = t->catalog; + + iso_msg_debug(t->image->id, "Write El-Torito boot record"); + + memset(&vol, 0, sizeof(struct ecma119_boot_rec_vol_desc)); + vol.vol_desc_type[0] = 0; + memcpy(vol.std_identifier, "CD001", 5); + vol.vol_desc_version[0] = 1; + memcpy(vol.boot_sys_id, "EL TORITO SPECIFICATION", 23); + iso_lsb(vol.boot_catalog, t->cat->block, 4); + + return iso_write(t, &vol, sizeof(struct ecma119_boot_rec_vol_desc)); +} + +/** + * Patch an isolinux boot image. + * + * @return + * 1 on success, 0 error (but continue), < 0 error + */ +static +int patch_boot_image(uint8_t *buf, Ecma119Image *t, size_t imgsize) +{ + struct boot_info_table *info; + uint32_t checksum; + size_t offset; + + if (imgsize < 64) { + return iso_msg_submit(t->image->id, ISO_ISOLINUX_CANT_PATCH, 0, + "Isolinux image too small. We won't patch it."); + } + + /* compute checksum, as the the sum of all 32 bit words in boot image + * from offset 64 */ + checksum = 0; + offset = (size_t) 64; + + while (offset <= imgsize - 4) { + checksum += iso_read_lsb(buf + offset, 4); + offset += 4; + } + if (offset != imgsize) { + /* file length not multiple of 4 */ + return iso_msg_submit(t->image->id, ISO_ISOLINUX_CANT_PATCH, 0, + "Unexpected isolinux image length. Patch might not work."); + } + + /* patch boot info table */ + info = (struct boot_info_table*)(buf + 8); + /*memset(info, 0, sizeof(struct boot_info_table));*/ + iso_lsb(info->bi_pvd, t->ms_block + 16, 4); + iso_lsb(info->bi_file, t->bootimg->block, 4); + iso_lsb(info->bi_length, imgsize, 4); + iso_lsb(info->bi_csum, checksum, 4); + return ISO_SUCCESS; +} + +static +int eltorito_writer_write_data(IsoImageWriter *writer) +{ + /* + * We have nothing to write, but if we need to patch an isolinux image, + * this is a good place to do so. + */ + Ecma119Image *t; + int ret; + + if (writer == NULL) { + return ISO_NULL_POINTER; + } + + t = writer->target; + + if (t->catalog->image->isolinux) { + /* we need to patch the image */ + size_t size; + uint8_t *buf; + IsoStream *new = NULL; + IsoStream *original = t->bootimg->stream; + size = (size_t) iso_stream_get_size(original); + buf = malloc(size); + if (buf == NULL) { + return ISO_OUT_OF_MEM; + } + ret = iso_stream_open(original); + if (ret < 0) { + return ret; + } + ret = iso_stream_read(original, buf, size); + iso_stream_close(original); + if (ret != size) { + return (ret < 0) ? ret : ISO_FILE_READ_ERROR; + } + + /* ok, patch the read buffer */ + ret = patch_boot_image(buf, t, size); + if (ret < 0) { + return ret; + } + + /* replace the original stream with a memory stream that reads from + * the patched buffer */ + ret = iso_memory_stream_new(buf, size, &new); + if (ret < 0) { + return ret; + } + t->bootimg->stream = new; + iso_stream_unref(original); + } + return ISO_SUCCESS; +} + +static +int eltorito_writer_free_data(IsoImageWriter *writer) +{ + /* nothing to do */ + return ISO_SUCCESS; +} + +int eltorito_writer_create(Ecma119Image *target) +{ + int ret; + IsoImageWriter *writer; + IsoFile *bootimg; + IsoFileSrc *src; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + writer->compute_data_blocks = eltorito_writer_compute_data_blocks; + writer->write_vol_desc = eltorito_writer_write_vol_desc; + writer->write_data = eltorito_writer_write_data; + writer->free_data = eltorito_writer_free_data; + writer->data = NULL; + writer->target = target; + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + + /* + * get catalog and image file sources. + * Note that the catalog may be already added, when creating the low + * level ECMA-119 tree. + */ + if (target->cat == NULL) { + ret = el_torito_catalog_file_src_create(target, &src); + if (ret < 0) { + return ret; + } + } + bootimg = target->catalog->image->image; + ret = iso_file_src_create(target, bootimg, &src); + if (ret < 0) { + return ret; + } + target->bootimg = src; + + /* if we have selected to patch the image, it needs to be copied always */ + if (target->catalog->image->isolinux) { + src->prev_img = 0; + } + + /* we need the bootable volume descriptor */ + target->curblock++; + return ISO_SUCCESS; +} + diff --git a/libisofs/eltorito.h b/libisofs/eltorito.h new file mode 100644 index 0000000..3c50799 --- /dev/null +++ b/libisofs/eltorito.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/** + * Declare El-Torito related structures. + * References: + * "El Torito" Bootable CD-ROM Format Specification Version 1.0 (1995) + */ + +#ifndef LIBISO_ELTORITO_H +#define LIBISO_ELTORITO_H + +#include "ecma119.h" +#include "node.h" + +/** + * A node that acts as a placeholder for an El-Torito catalog. + */ +struct Iso_Boot +{ + IsoNode node; +}; + +struct el_torito_boot_catalog { + IsoBoot *node; /* node of the catalog */ + struct el_torito_boot_image *image; /* default boot image */ +}; + +struct el_torito_boot_image { + IsoFile *image; + + unsigned int bootable:1; /**< If the entry is bootable. */ + unsigned int isolinux:1; /**< If the image will be patched */ + unsigned char type; /**< The type of image */ + unsigned char partition_type; /**< type of partition for HD-emul images */ + short load_seg; /**< Load segment for the initial boot image. */ + short load_size; /**< Number of sectors to load. */ +}; + +/** El-Torito, 2.1 */ +struct el_torito_validation_entry { + uint8_t header_id BP(1, 1); + uint8_t platform_id BP(2, 2); + uint8_t reserved BP(3, 4); + uint8_t id_string BP(5, 28); + uint8_t checksum BP(29, 30); + uint8_t key_byte1 BP(31, 31); + uint8_t key_byte2 BP(32, 32); +}; + +/** El-Torito, 2.2 */ +struct el_torito_default_entry { + uint8_t boot_indicator BP(1, 1); + uint8_t boot_media_type BP(2, 2); + uint8_t load_seg BP(3, 4); + uint8_t system_type BP(5, 5); + uint8_t unused1 BP(6, 6); + uint8_t sec_count BP(7, 8); + uint8_t block BP(9, 12); + uint8_t unused2 BP(13, 32); +}; + +/** El-Torito, 2.3 */ +struct el_torito_section_header { + uint8_t header_indicator BP(1, 1); + uint8_t platform_id BP(2, 2); + uint8_t number BP(3, 4); + uint8_t character BP(5, 32); +}; + +/** El-Torito, 2.4 */ +struct el_torito_section_entry { + uint8_t boot_indicator BP(1, 1); + uint8_t boot_media_type BP(2, 2); + uint8_t load_seg BP(3, 4); + uint8_t system_type BP(5, 5); + uint8_t unused1 BP(6, 6); + uint8_t sec_count BP(7, 8); + uint8_t block BP(9, 12); + uint8_t selec_criteria BP(13, 13); + uint8_t vendor_sc BP(14, 32); +}; + +void el_torito_boot_catalog_free(struct el_torito_boot_catalog *cat); + +/** + * Create a IsoFileSrc for writing the el-torito catalog for the given + * target, and add it to target. If the target already has a src for the + * catalog, it just returns. + */ +int el_torito_catalog_file_src_create(Ecma119Image *target, IsoFileSrc **src); + +/** + * Create a writer for el-torito information. + */ +int eltorito_writer_create(Ecma119Image *target); + +#endif /* LIBISO_ELTORITO_H */ diff --git a/libisofs/filesrc.c b/libisofs/filesrc.c new file mode 100644 index 0000000..a4a2bfc --- /dev/null +++ b/libisofs/filesrc.c @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "filesrc.h" +#include "node.h" +#include "util.h" +#include "writer.h" +#include "messages.h" +#include "image.h" + +#include +#include + +int iso_file_src_cmp(const void *n1, const void *n2) +{ + const IsoFileSrc *f1, *f2; + unsigned int fs_id1, fs_id2; + dev_t dev_id1, dev_id2; + ino_t ino_id1, ino_id2; + + f1 = (const IsoFileSrc *)n1; + f2 = (const IsoFileSrc *)n2; + + iso_stream_get_id(f1->stream, &fs_id1, &dev_id1, &ino_id1); + iso_stream_get_id(f2->stream, &fs_id2, &dev_id2, &ino_id2); + + if (fs_id1 < fs_id2) { + return -1; + } else if (fs_id1 > fs_id2) { + return 1; + } else { + /* files belong to the same fs */ + if (dev_id1 > dev_id2) { + return -1; + } else if (dev_id1 < dev_id2) { + return 1; + } else { + /* files belong to same device in same fs */ + return (ino_id1 < ino_id2) ? -1 : (ino_id1 > ino_id2) ? 1 : 0; + } + } +} + +int iso_file_src_create(Ecma119Image *img, IsoFile *file, IsoFileSrc **src) +{ + int ret; + IsoFileSrc *fsrc; + unsigned int fs_id; + dev_t dev_id; + ino_t ino_id; + + if (img == NULL || file == NULL || src == NULL) { + return ISO_NULL_POINTER; + } + + iso_stream_get_id(file->stream, &fs_id, &dev_id, &ino_id); + + fsrc = malloc(sizeof(IsoFileSrc)); + if (fsrc == NULL) { + return ISO_OUT_OF_MEM; + } + + /* fill key and other atts */ + fsrc->prev_img = file->msblock ? 1 : 0; + fsrc->block = file->msblock; + fsrc->sort_weight = file->sort_weight; + fsrc->stream = file->stream; + + /* insert the filesrc in the tree */ + ret = iso_rbtree_insert(img->files, fsrc, (void**)src); + if (ret <= 0) { + free(fsrc); + return ret; + } + iso_stream_ref(fsrc->stream); + return ISO_SUCCESS; +} + +/** + * Add a given IsoFileSrc to the given image target. + * + * The IsoFileSrc will be cached in a tree to prevent the same file for + * being written several times to image. If you call again this function + * with a node that refers to the same source file, the previously + * created one will be returned. + * + * @param img + * The image where this file is to be written + * @param new + * The IsoFileSrc to add + * @param src + * Will be filled with a pointer to the IsoFileSrc really present in + * the tree. It could be different than new if the same file already + * exists in the tree. + * @return + * 1 on success, 0 if file already exists on tree, < 0 error + */ +int iso_file_src_add(Ecma119Image *img, IsoFileSrc *new, IsoFileSrc **src) +{ + int ret; + + if (img == NULL || new == NULL || src == NULL) { + return ISO_NULL_POINTER; + } + + /* insert the filesrc in the tree */ + ret = iso_rbtree_insert(img->files, new, (void**)src); + return ret; +} + +void iso_file_src_free(void *node) +{ + iso_stream_unref(((IsoFileSrc*)node)->stream); + free(node); +} + +off_t iso_file_src_get_size(IsoFileSrc *file) +{ + return iso_stream_get_size(file->stream); +} + +static int cmp_by_weight(const void *f1, const void *f2) +{ + IsoFileSrc *f = *((IsoFileSrc**)f1); + IsoFileSrc *g = *((IsoFileSrc**)f2); + /* higher weighted first */ + return g->sort_weight - f->sort_weight; +} + +static +int is_ms_file(void *arg) +{ + IsoFileSrc *f = (IsoFileSrc *)arg; + return f->prev_img ? 0 : 1; +} + +static +int filesrc_writer_compute_data_blocks(IsoImageWriter *writer) +{ + size_t i, size; + Ecma119Image *t; + IsoFileSrc **filelist; + int (*inc_item)(void *); + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + t = writer->target; + + /* on appendable images, ms files shouldn't be included */ + if (t->appendable) { + inc_item = is_ms_file; + } else { + inc_item = NULL; + } + + /* store the filesrcs in a array */ + filelist = (IsoFileSrc**)iso_rbtree_to_array(t->files, inc_item, &size); + if (filelist == NULL) { + return ISO_OUT_OF_MEM; + } + + /* sort files by weight, if needed */ + if (t->sort_files) { + qsort(filelist, size, sizeof(void*), cmp_by_weight); + } + + /* fill block value */ + for (i = 0; i < size; ++i) { + IsoFileSrc *file = filelist[i]; + file->block = t->curblock; + t->curblock += DIV_UP(iso_file_src_get_size(file), BLOCK_SIZE); + } + + /* the list is only needed by this writer, store locally */ + writer->data = filelist; + return ISO_SUCCESS; +} + +static +int filesrc_writer_write_vol_desc(IsoImageWriter *writer) +{ + /* nothing needed */ + return ISO_SUCCESS; +} + +/* open a file, i.e., its Stream */ +static inline +int filesrc_open(IsoFileSrc *file) +{ + return iso_stream_open(file->stream); +} + +static inline +int filesrc_close(IsoFileSrc *file) +{ + return iso_stream_close(file->stream); +} + +/** + * @return + * 1 ok, 0 EOF, < 0 error + */ +static +int filesrc_read(IsoFileSrc *file, char *buf, size_t count) +{ + size_t bytes = 0; + + /* loop to ensure the full buffer is filled */ + do { + ssize_t result; + result = iso_stream_read(file->stream, buf + bytes, count - bytes); + if (result < 0) { + /* fill buffer with 0s and return */ + memset(buf + bytes, 0, count - bytes); + return result; + } + if (result == 0) + break; + bytes += result; + } while (bytes < count); + + if (bytes < count) { + /* eof */ + memset(buf + bytes, 0, count - bytes); + return 0; + } else { + return 1; + } +} + +static +int filesrc_writer_write_data(IsoImageWriter *writer) +{ + int res; + size_t i, b; + Ecma119Image *t; + IsoFileSrc *file; + IsoFileSrc **filelist; + char *name; + char buffer[BLOCK_SIZE]; + + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + t = writer->target; + filelist = writer->data; + + iso_msg_debug(t->image->id, "Writing Files..."); + + i = 0; + while ((file = filelist[i++]) != NULL) { + + /* + * TODO WARNING + * when we allow files greater than 4GB, current DIV_UP implementation + * can overflow!! + */ + uint32_t nblocks = DIV_UP(iso_file_src_get_size(file), BLOCK_SIZE); + + res = filesrc_open(file); + if (res < 0) { + /* + * UPS, very ugly error, the best we can do is just to write + * 0's to image + */ + name = iso_stream_get_name(file->stream); + iso_report_errfile(name, ISO_FILE_CANT_WRITE, 0, 0); + res = iso_msg_submit(t->image->id, ISO_FILE_CANT_WRITE, res, + "File \"%s\" can't be opened. Filling with 0s.", name); + free(name); + if (res < 0) { + return res; /* aborted due to error severity */ + } + memset(buffer, 0, BLOCK_SIZE); + for (b = 0; b < nblocks; ++b) { + res = iso_write(t, buffer, BLOCK_SIZE); + if (res < 0) { + /* ko, writer error, we need to go out! */ + return res; + } + } + continue; + } else if (res > 1) { + name = iso_stream_get_name(file->stream); + iso_report_errfile(name, ISO_FILE_CANT_WRITE, 0, 0); + res = iso_msg_submit(t->image->id, ISO_FILE_CANT_WRITE, 0, + "Size of file \"%s\" has changed. It will be %s", name, + (res == 2 ? "truncated" : "padded with 0's")); + free(name); + if (res < 0) { + filesrc_close(file); + return res; /* aborted due to error severity */ + } + } +#ifdef LIBISOFS_VERBOSE_DEBUG + else { + name = iso_stream_get_name(file->stream); + iso_msg_debug(t->image->id, "Writing file %s", name); + free(name); + } +#endif + + /* write file contents to image */ + for (b = 0; b < nblocks; ++b) { + int wres; + res = filesrc_read(file, buffer, BLOCK_SIZE); + if (res < 0) { + /* read error */ + break; + } + wres = iso_write(t, buffer, BLOCK_SIZE); + if (wres < 0) { + /* ko, writer error, we need to go out! */ + filesrc_close(file); + return wres; + } + } + + filesrc_close(file); + + if (b < nblocks) { + /* premature end of file, due to error or eof */ + char *name = iso_stream_get_name(file->stream); + iso_report_errfile(name, ISO_FILE_CANT_WRITE, 0, 0); + if (res < 0) { + /* error */ + res = iso_msg_submit(t->image->id, ISO_FILE_CANT_WRITE, res, + "Read error in file %s.", name); + } else { + /* eof */ + res = iso_msg_submit(t->image->id, ISO_FILE_CANT_WRITE, 0, + "Premature end of file %s.", name); + } + free(name); + + if (res < 0) { + return res; /* aborted due error severity */ + } + + /* fill with 0s */ + iso_msg_submit(t->image->id, ISO_FILE_CANT_WRITE, 0, + "Filling with 0"); + memset(buffer, 0, BLOCK_SIZE); + while (b++ < nblocks) { + res = iso_write(t, buffer, BLOCK_SIZE); + if (res < 0) { + /* ko, writer error, we need to go out! */ + return res; + } + } + } + } + + return ISO_SUCCESS; +} + +static +int filesrc_writer_free_data(IsoImageWriter *writer) +{ + /* free the list of files (contents are free together with the tree) */ + free(writer->data); + return ISO_SUCCESS; +} + +int iso_file_src_writer_create(Ecma119Image *target) +{ + IsoImageWriter *writer; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_ASSERT_FAILURE; + } + + writer->compute_data_blocks = filesrc_writer_compute_data_blocks; + writer->write_vol_desc = filesrc_writer_write_vol_desc; + writer->write_data = filesrc_writer_write_data; + writer->free_data = filesrc_writer_free_data; + writer->data = NULL; + writer->target = target; + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + + return ISO_SUCCESS; +} diff --git a/libisofs/filesrc.h b/libisofs/filesrc.h new file mode 100644 index 0000000..5a3c03c --- /dev/null +++ b/libisofs/filesrc.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_FILESRC_H_ +#define LIBISO_FILESRC_H_ + +#include "libisofs.h" +#include "stream.h" +#include "ecma119.h" + +#include + +struct Iso_File_Src +{ + unsigned int prev_img :1; /**< if the file comes from a previous image */ + uint32_t block; /**< Block where this file will be written on image */ + int sort_weight; + IsoStream *stream; +}; + +int iso_file_src_cmp(const void *n1, const void *n2); + +/** + * Create a new IsoFileSrc to get data from a specific IsoFile. + * + * The IsoFileSrc will be cached in a tree to prevent the same file for + * being written several times to image. If you call again this function + * with a node that refers to the same source file, the previously + * created one will be returned. No new IsoFileSrc is created in that case. + * + * @param img + * The image where this file is to be written + * @param file + * The IsoNode we want to write + * @param src + * Will be filled with a pointer to the IsoFileSrc + * @return + * 1 on success, < 0 on error + */ +int iso_file_src_create(Ecma119Image *img, IsoFile *file, IsoFileSrc **src); + +/** + * Add a given IsoFileSrc to the given image target. + * + * The IsoFileSrc will be cached in a tree to prevent the same file for + * being written several times to image. If you call again this function + * with a node that refers to the same source file, the previously + * created one will be returned. + * + * @param img + * The image where this file is to be written + * @param new + * The IsoFileSrc to add + * @param src + * Will be filled with a pointer to the IsoFileSrc really present in + * the tree. It could be different than new if the same file already + * exists in the tree. + * @return + * 1 on success, 0 if file already exists on tree, < 0 error + */ +int iso_file_src_add(Ecma119Image *img, IsoFileSrc *new, IsoFileSrc **src); + +/** + * Free the IsoFileSrc especific data + */ +void iso_file_src_free(void *node); + +/** + * Get the size of the file this IsoFileSrc represents + */ +off_t iso_file_src_get_size(IsoFileSrc *file); + +/** + * Create a Writer for file contents. + * + * It takes care of written the files in the correct order. + */ +int iso_file_src_writer_create(Ecma119Image *target); + +#endif /*LIBISO_FILESRC_H_*/ diff --git a/libisofs/fs_image.c b/libisofs/fs_image.c new file mode 100644 index 0000000..817668d --- /dev/null +++ b/libisofs/fs_image.c @@ -0,0 +1,2642 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * Filesystem/FileSource implementation to access an ISO image, using an + * IsoDataSource to read image data. + */ + +#include "libisofs.h" +#include "ecma119.h" +#include "messages.h" +#include "rockridge.h" +#include "image.h" +#include "tree.h" +#include "eltorito.h" + +#include +#include +#include +#include +#include + + +/** + * Options for image reading. + * There are four kind of options: + * - Related to multisession support. + * In most cases, an image begins at LBA 0 of the data source. However, + * in multisession discs, the later image begins in the last session on + * disc. The block option can be used to specify the start of that last + * session. + * - Related to the tree that will be read. + * As default, when Rock Ridge extensions are present in the image, that + * will be used to get the tree. If RR extensions are not present, libisofs + * will use the Joliet extensions if available. Finally, the plain ISO-9660 + * tree is used if neither RR nor Joliet extensions are available. With + * norock, nojoliet, and preferjoliet options, you can change this + * default behavior. + * - Related to default POSIX attributes. + * When Rock Ridege extensions are not used, libisofs can't figure out what + * are the the permissions, uid or gid for the files. You should supply + * default values for that. + */ +struct iso_read_opts +{ + /** + * Block where the image begins, usually 0, can be different on a + * multisession disc. + */ + uint32_t block; + + unsigned int norock : 1; /*< Do not read Rock Ridge extensions */ + unsigned int nojoliet : 1; /*< Do not read Joliet extensions */ + unsigned int noiso1999 : 1; /*< Do not read ISO 9660:1999 enhanced tree */ + + /** + * When both Joliet and RR extensions are present, the RR tree is used. + * If you prefer using Joliet, set this to 1. + */ + unsigned int preferjoliet : 1; + + uid_t uid; /**< Default uid when no RR */ + gid_t gid; /**< Default uid when no RR */ + mode_t dir_mode; /**< Default mode when no RR (only permissions) */ + mode_t file_mode; + /* TODO #00024 : option to convert names to lower case for iso reading */ + + /** + * Input charset for RR file names. NULL to use default locale charset. + */ + char *input_charset; +}; + +/** + * Return information for image. + * Both size, hasRR and hasJoliet will be filled by libisofs with suitable + * values. + */ +struct iso_read_image_features +{ + /** + * Will be filled with the size (in 2048 byte block) of the image, as + * reported in the PVM. + */ + uint32_t size; + + /** It will be set to 1 if RR extensions are present, to 0 if not. */ + unsigned int hasRR :1; + + /** It will be set to 1 if Joliet extensions are present, to 0 if not. */ + unsigned int hasJoliet :1; + + /** + * It will be set to 1 if the image is an ISO 9660:1999, i.e. it has + * a version 2 Enhanced Volume Descriptor. + */ + unsigned int hasIso1999 :1; + + /** It will be set to 1 if El-Torito boot record is present, to 0 if not.*/ + unsigned int hasElTorito :1; +}; + +static int ifs_fs_open(IsoImageFilesystem *fs); +static int ifs_fs_close(IsoImageFilesystem *fs); +static int iso_file_source_new_ifs(IsoImageFilesystem *fs, + IsoFileSource *parent, struct ecma119_dir_record *record, + IsoFileSource **src); + +/** unique identifier for each image */ +unsigned int fs_dev_id = 0; + +/** + * Should the RR extensions be read? + */ +enum read_rr_ext { + RR_EXT_NO = 0, /*< Do not use RR extensions */ + RR_EXT_110 = 1, /*< RR extensions conforming version 1.10 */ + RR_EXT_112 = 2 /*< RR extensions conforming version 1.12 */ +}; + +/** + * Private data for the image IsoFilesystem + */ +typedef struct +{ + /** DataSource from where data will be read */ + IsoDataSource *src; + + /** unique id for the each image (filesystem instance) */ + unsigned int id; + + /** + * Counter of the times the filesystem has been openned still pending of + * close. It is used to keep track of when we need to actually open or + * close the IsoDataSource. + */ + unsigned int open_count; + + uid_t uid; /**< Default uid when no RR */ + gid_t gid; /**< Default uid when no RR */ + mode_t dir_mode; /**< Default mode when no RR (only permissions) */ + mode_t file_mode; + + int msgid; + + char *input_charset; /**< Input charset for RR names */ + char *local_charset; /**< For RR names, will be set to the locale one */ + + /** + * Will be filled with the block lba of the extend for the root directory + * of the hierarchy that will be read, either from the PVD (ISO, RR) or + * from the SVD (Joliet) + */ + uint32_t iso_root_block; + + /** + * Will be filled with the block lba of the extend for the root directory, + * as read from the PVM + */ + uint32_t pvd_root_block; + + /** + * Will be filled with the block lba of the extend for the root directory, + * as read from the SVD + */ + uint32_t svd_root_block; + + /** + * Will be filled with the block lba of the extend for the root directory, + * as read from the enhanced volume descriptor (ISO 9660:1999) + */ + uint32_t evd_root_block; + + /** + * If we need to read RR extensions. i.e., if the image contains RR + * extensions, and the user wants to read them. + */ + enum read_rr_ext rr; + + /** + * Bytes skipped within the System Use field of a directory record, before + * the beginning of the SUSP system user entries. See IEEE 1281, SUSP. 5.3. + */ + uint8_t len_skp; + + /* Volume attributes */ + char *volset_id; + char *volume_id; /**< Volume identifier. */ + char *publisher_id; /**< Volume publisher. */ + char *data_preparer_id; /**< Volume data preparer. */ + char *system_id; /**< Volume system identifier. */ + char *application_id; /**< Volume application id */ + char *copyright_file_id; + char *abstract_file_id; + char *biblio_file_id; + + /* extension information */ + + /** + * RR version being used in image. + * 0 no RR extension, 1 RRIP 1.10, 2 RRIP 1.12 + */ + enum read_rr_ext rr_version; + + /** If Joliet extensions are available on image */ + unsigned int joliet : 1; + + /** If ISO 9660:1999 is available on image */ + unsigned int iso1999 : 1; + + /** + * Number of blocks of the volume, as reported in the PVM. + */ + uint32_t nblocks; + + /* el-torito information */ + unsigned int eltorito : 1; /* is el-torito available */ + unsigned int bootable:1; /**< If the entry is bootable. */ + unsigned char type; /**< The type of image */ + unsigned char partition_type; /**< type of partition for HD-emul images */ + short load_seg; /**< Load segment for the initial boot image. */ + short load_size; /**< Number of sectors to load. */ + uint32_t imgblock; /**< Block for El-Torito boot image */ + uint32_t catblock; /**< Block for El-Torito catalog */ + +} _ImageFsData; + +typedef struct image_fs_data ImageFileSourceData; + +struct image_fs_data +{ + IsoImageFilesystem *fs; /**< reference to the image it belongs to */ + IsoFileSource *parent; /**< reference to the parent (NULL if root) */ + + struct stat info; /**< filled struct stat */ + char *name; /**< name of this file */ + + uint32_t block; /**< block of the extend */ + unsigned int opened : 2; /**< 0 not opened, 1 opened file, 2 opened dir */ + + /* info for content reading */ + struct + { + /** + * - For regular files, once opened it points to a temporary data + * buffer of 2048 bytes. + * - For dirs, once opened it points to a IsoFileSource* array with + * its children + * - For symlinks, it points to link destination + */ + void *content; + + /** + * - For regular files, number of bytes already read. + */ + off_t offset; + } data; +}; + +struct child_list +{ + IsoFileSource *file; + struct child_list *next; +}; + +void child_list_free(struct child_list *list) +{ + struct child_list *temp; + struct child_list *next = list; + while (next != NULL) { + temp = next->next; + iso_file_source_unref(next->file); + free(next); + next = temp; + } +} + +static +char* ifs_get_path(IsoFileSource *src) +{ + ImageFileSourceData *data; + data = src->data; + + if (data->parent == NULL) { + return strdup(""); + } else { + char *path = ifs_get_path(data->parent); + int pathlen = strlen(path); + path = realloc(path, pathlen + strlen(data->name) + 2); + path[pathlen] = '/'; + path[pathlen + 1] = '\0'; + return strcat(path, data->name); + } +} + +static +char* ifs_get_name(IsoFileSource *src) +{ + ImageFileSourceData *data; + data = src->data; + return data->name == NULL ? NULL : strdup(data->name); +} + +static +int ifs_lstat(IsoFileSource *src, struct stat *info) +{ + ImageFileSourceData *data; + + if (src == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + + data = src->data; + *info = data->info; + return ISO_SUCCESS; +} + +static +int ifs_stat(IsoFileSource *src, struct stat *info) +{ + ImageFileSourceData *data; + + if (src == NULL || info == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (ImageFileSourceData*)src->data; + + if (S_ISLNK(data->info.st_mode)) { + /* TODO #00012 : support follow symlinks on image filesystem */ + return ISO_FILE_BAD_PATH; + } + *info = data->info; + return ISO_SUCCESS; +} + +static +int ifs_access(IsoFileSource *src) +{ + /* we always have access, it is controlled by DataSource */ + return ISO_SUCCESS; +} + +/** + * Read all directory records in a directory, and creates an IsoFileSource for + * each of them, storing them in the data field of the IsoFileSource for the + * given dir. + */ +static +int read_dir(ImageFileSourceData *data) +{ + int ret; + uint32_t size; + uint32_t block; + IsoImageFilesystem *fs; + _ImageFsData *fsdata; + struct ecma119_dir_record *record; + uint8_t buffer[BLOCK_SIZE]; + IsoFileSource *child = NULL; + uint32_t pos = 0; + uint32_t tlen = 0; + + if (data == NULL) { + return ISO_NULL_POINTER; + } + + fs = data->fs; + fsdata = fs->data; + + block = data->block; + ret = fsdata->src->read_block(fsdata->src, block, buffer); + if (ret < 0) { + return ret; + } + + /* "." entry, get size of the dir and skip */ + record = (struct ecma119_dir_record *)(buffer + pos); + size = iso_read_bb(record->length, 4, NULL); + tlen += record->len_dr[0]; + pos += record->len_dr[0]; + + /* skip ".." */ + record = (struct ecma119_dir_record *)(buffer + pos); + tlen += record->len_dr[0]; + pos += record->len_dr[0]; + + while (tlen < size) { + + record = (struct ecma119_dir_record *)(buffer + pos); + if (pos == 2048 || record->len_dr[0] == 0) { + /* + * The directory entries are splitted in several blocks + * read next block + */ + ret = fsdata->src->read_block(fsdata->src, ++block, buffer); + if (ret < 0) { + return ret; + } + tlen += 2048 - pos; + pos = 0; + continue; + } + + /* + * What about ignoring files with existence flag? + * if (record->flags[0] & 0x01) + * continue; + */ + + /* + * For a extrange reason, mkisofs relocates directories under + * a RR_MOVED dir. It seems that it is only used for that purposes, + * and thus it should be removed from the iso tree before + * generating a new image with libisofs, that don't uses it. + */ + if (data->parent == NULL && record->len_fi[0] == 8 + && !strncmp((char*)record->file_id, "RR_MOVED", 8)) { + + iso_msg_debug(fsdata->msgid, "Skipping RR_MOVE entry."); + + tlen += record->len_dr[0]; + pos += record->len_dr[0]; + continue; + } + + /* + * We pass a NULL parent instead of dir, to prevent the circular + * reference from child to parent. + */ + ret = iso_file_source_new_ifs(fs, NULL, record, &child); + if (ret < 0) { + return ret; + } + + /* add to the child list */ + if (ret != 0) { + struct child_list *node; + node = malloc(sizeof(struct child_list)); + if (node == NULL) { + iso_file_source_unref(child); + return ISO_OUT_OF_MEM; + } + /* + * Note that we insert in reverse order. This leads to faster + * addition here, but also when adding to the tree, as insertion + * will be done, sorted, in the first position of the list. + */ + node->next = data->data.content; + node->file = child; + data->data.content = node; + } + + tlen += record->len_dr[0]; + pos += record->len_dr[0]; + } + + return ISO_SUCCESS; +} + +static +int ifs_open(IsoFileSource *src) +{ + int ret; + ImageFileSourceData *data; + + if (src == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + data = (ImageFileSourceData*)src->data; + + if (data->opened) { + return ISO_FILE_ALREADY_OPENNED; + } + + if (S_ISDIR(data->info.st_mode)) { + /* ensure fs is openned */ + ret = data->fs->open(data->fs); + if (ret < 0) { + return ret; + } + + /* + * Cache all directory entries. + * This can waste more memory, but improves as disc is read in much more + * sequencially way, thus reducing jump between tracks on disc + */ + ret = read_dir(data); + data->fs->close(data->fs); + + if (ret < 0) { + /* free probably allocated children */ + child_list_free((struct child_list*)data->data.content); + } else { + data->opened = 2; + } + + return ret; + } else if (S_ISREG(data->info.st_mode)) { + /* ensure fs is openned */ + ret = data->fs->open(data->fs); + if (ret < 0) { + return ret; + } + data->data.content = malloc(BLOCK_SIZE); + if (data->data.content == NULL) { + return ISO_OUT_OF_MEM; + } + data->data.offset = 0; + data->opened = 1; + } else { + /* symlinks and special files inside image can't be openned */ + return ISO_FILE_ERROR; + } + return ISO_SUCCESS; +} + +static +int ifs_close(IsoFileSource *src) +{ + ImageFileSourceData *data; + + if (src == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + data = (ImageFileSourceData*)src->data; + + if (!data->opened) { + return ISO_FILE_NOT_OPENNED; + } + + if (data->opened == 2) { + /* + * close a dir, free all pending pre-allocated children. + * not that we don't need to close the filesystem, it was already + * closed + */ + child_list_free((struct child_list*) data->data.content); + data->data.content = NULL; + data->opened = 0; + } else if (data->opened == 1) { + /* close regular file */ + free(data->data.content); + data->fs->close(data->fs); + data->data.content = NULL; + data->opened = 0; + } else { + /* TODO only dirs and files supported for now */ + return ISO_ERROR; + } + + return ISO_SUCCESS; +} + +/** + * Attempts to read up to count bytes from the given source into + * the buffer starting at buf. + * + * The file src must be open() before calling this, and close() when no + * more needed. Not valid for dirs. On symlinks it reads the destination + * file. + * + * @return + * number of bytes read, 0 if EOF, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * ISO_FILE_IS_DIR + * ISO_OUT_OF_MEM + * ISO_INTERRUPTED + */ +static +int ifs_read(IsoFileSource *src, void *buf, size_t count) +{ + int ret; + ImageFileSourceData *data; + uint32_t read = 0; + + if (src == NULL || src->data == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + if (count == 0) { + return ISO_WRONG_ARG_VALUE; + } + data = (ImageFileSourceData*)src->data; + + if (!data->opened) { + return ISO_FILE_NOT_OPENNED; + } else if (data->opened != 1) { + return ISO_FILE_IS_DIR; + } + + while (read < count && data->data.offset < data->info.st_size) { + size_t bytes; + uint8_t *orig; + + if (data->data.offset % BLOCK_SIZE == 0) { + /* we need to buffer next block */ + uint32_t block; + _ImageFsData *fsdata; + + if (data->data.offset >= data->info.st_size) { + /* EOF */ + break; + } + fsdata = data->fs->data; + block = data->block + (data->data.offset / BLOCK_SIZE); + ret = fsdata->src->read_block(fsdata->src, block, + data->data.content); + if (ret < 0) { + return ret; + } + } + + /* how much can I read */ + bytes = MIN(BLOCK_SIZE - (data->data.offset % BLOCK_SIZE), + count - read); + if (data->data.offset + (off_t)bytes > data->info.st_size) { + bytes = data->info.st_size - data->data.offset; + } + orig = data->data.content; + orig += data->data.offset % BLOCK_SIZE; + memcpy((uint8_t*)buf + read, orig, bytes); + read += bytes; + data->data.offset += (off_t)bytes; + } + return read; +} + +static +int ifs_readdir(IsoFileSource *src, IsoFileSource **child) +{ + ImageFileSourceData *data, *cdata; + struct child_list *children; + + if (src == NULL || src->data == NULL || child == NULL) { + return ISO_NULL_POINTER; + } + data = (ImageFileSourceData*)src->data; + + if (!data->opened) { + return ISO_FILE_NOT_OPENNED; + } else if (data->opened != 2) { + return ISO_FILE_IS_NOT_DIR; + } + + /* return the first child and free it */ + if (data->data.content == NULL) { + return 0; /* EOF */ + } + + children = (struct child_list*)data->data.content; + *child = children->file; + cdata = (ImageFileSourceData*)(*child)->data; + + /* set the ref to the parent */ + cdata->parent = src; + iso_file_source_ref(src); + + /* free the first element of the list */ + data->data.content = children->next; + free(children); + + return ISO_SUCCESS; +} + +/** + * Read the destination of a symlink. You don't need to open the file + * to call this. + * + * @param buf + * allocated buffer of at least bufsiz bytes. + * The dest. will be copied there, and it will be NULL-terminated + * @param bufsiz + * characters to be copied. Destination link will be truncated if + * it is larger than given size. This include the \0 character. + * @return + * 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_WRONG_ARG_VALUE -> if bufsiz <= 0 + * ISO_FILE_IS_NOT_SYMLINK + * ISO_OUT_OF_MEM + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * + */ +static +int ifs_readlink(IsoFileSource *src, char *buf, size_t bufsiz) +{ + char *dest; + size_t len; + ImageFileSourceData *data; + + if (src == NULL || buf == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + + if (bufsiz <= 0) { + return ISO_WRONG_ARG_VALUE; + } + + data = (ImageFileSourceData*)src->data; + + if (!S_ISLNK(data->info.st_mode)) { + return ISO_FILE_IS_NOT_SYMLINK; + } + + dest = (char*)data->data.content; + len = strlen(dest); + if (bufsiz <= len) { + len = bufsiz - 1; + } + + strncpy(buf, dest, len); + buf[len] = '\0'; + + return ISO_SUCCESS; +} + +static +IsoFilesystem* ifs_get_filesystem(IsoFileSource *src) +{ + ImageFileSourceData *data; + + if (src == NULL) { + return NULL; + } + + data = src->data; + return data->fs; +} + +static +void ifs_free(IsoFileSource *src) +{ + ImageFileSourceData *data; + + data = src->data; + + /* close the file if it is already openned */ + if (data->opened) { + src->class->close(src); + } + + /* free destination if it is a link */ + if (S_ISLNK(data->info.st_mode)) { + free(data->data.content); + } + iso_filesystem_unref(data->fs); + if (data->parent != NULL) { + iso_file_source_unref(data->parent); + } + free(data->name); + free(data); +} + +IsoFileSourceIface ifs_class = { + 0, /* version */ + ifs_get_path, + ifs_get_name, + ifs_lstat, + ifs_stat, + ifs_access, + ifs_open, + ifs_close, + ifs_read, + ifs_readdir, + ifs_readlink, + ifs_get_filesystem, + ifs_free +}; + +/** + * Read a file name from a directory record, doing the needed charset + * conversion + */ +static +char *get_name(_ImageFsData *fsdata, const char *str, size_t len) +{ + int ret; + char *name = NULL; + if (strcmp(fsdata->local_charset, fsdata->input_charset)) { + /* charset conversion needed */ + ret = strnconv(str, fsdata->input_charset, fsdata->local_charset, len, + &name); + if (ret == 1) { + return name; + } else { + ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET, ret, + "Charset conversion error. Can't convert %s from %s to %s", + str, fsdata->input_charset, fsdata->local_charset); + if (ret < 0) { + return NULL; /* aborted */ + } + /* fallback */ + } + } + + /* we reach here when the charset conversion is not needed or has failed */ + + name = malloc(len + 1); + if (name == NULL) { + return NULL; + } + memcpy(name, str, len); + name[len] = '\0'; + return name; +} + +/** + * + * @return + * 1 success, 0 record ignored (not an error, can be a relocated dir), + * < 0 error + */ +static +int iso_file_source_new_ifs(IsoImageFilesystem *fs, IsoFileSource *parent, + struct ecma119_dir_record *record, + IsoFileSource **src) +{ + int ret; + struct stat atts; + time_t recorded; + _ImageFsData *fsdata; + IsoFileSource *ifsrc = NULL; + ImageFileSourceData *ifsdata = NULL; + + int namecont = 0; /* 1 if found a NM with CONTINUE flag */ + char *name = NULL; + + /* 1 if found a SL with CONTINUE flag, + * 2 if found a component with continue flag */ + int linkdestcont = 0; + char *linkdest = NULL; + + uint32_t relocated_dir = 0; + + if (fs == NULL || fs->data == NULL || record == NULL || src == NULL) { + return ISO_NULL_POINTER; + } + + fsdata = (_ImageFsData*)fs->data; + + memset(&atts, 0, sizeof(struct stat)); + + /* + * First of all, check for unsupported ECMA-119 features + */ + + /* check for unsupported multiextend */ + if (record->flags[0] & 0x80) { + iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0, + "Unsupported image. This image makes use of Multi-Extend" + " features, that are not supported at this time. If you " + "need support for that, please request us this feature."); + return ISO_UNSUPPORTED_ECMA119; + } + + /* check for unsupported interleaved mode */ + if (record->file_unit_size[0] || record->interleave_gap_size[0]) { + iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0, + "Unsupported image. This image has at least one file recorded " + "in interleaved mode. We don't support this mode, as we think " + "it's not used. If you're reading this, then we're wrong :) " + "Please contact libisofs developers, so we can fix this."); + return ISO_UNSUPPORTED_ECMA119; + } + + /* + * Check for extended attributes, that are not supported. Note that even + * if we don't support them, it is easy to ignore them. + */ + if (record->len_xa[0]) { + iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_ECMA119, 0, + "Unsupported image. This image has at least one file with " + "Extended Attributes, that are not supported"); + return ISO_UNSUPPORTED_ECMA119; + } + + /* TODO #00013 : check for unsupported flags when reading a dir record */ + + /* + * The idea is to read all the RR entries (if we want to do that and RR + * extensions exist on image), storing the info we want from that. + * Then, we need some sanity checks. + * Finally, we select what kind of node it is, and set values properly. + */ + + if (fsdata->rr) { + struct susp_sys_user_entry *sue; + SuspIterator *iter; + + + iter = susp_iter_new(fsdata->src, record, fsdata->len_skp, + fsdata->msgid); + if (iter == NULL) { + return ISO_OUT_OF_MEM; + } + + while ((ret = susp_iter_next(iter, &sue)) > 0) { + + /* ignore entries from different version */ + if (sue->version[0] != 1) + continue; + + if (SUSP_SIG(sue, 'P', 'X')) { + ret = read_rr_PX(sue, &atts); + if (ret < 0) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret, + "Invalid PX entry"); + } + } else if (SUSP_SIG(sue, 'T', 'F')) { + ret = read_rr_TF(sue, &atts); + if (ret < 0) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret, + "Invalid TF entry"); + } + } else if (SUSP_SIG(sue, 'N', 'M')) { + if (name != NULL && namecont == 0) { + /* ups, RR standard violation */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, 0, + "New NM entry found without previous" + "CONTINUE flag. Ignored"); + continue; + } + ret = read_rr_NM(sue, &name, &namecont); + if (ret < 0) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret, + "Invalid NM entry"); + } + } else if (SUSP_SIG(sue, 'S', 'L')) { + if (linkdest != NULL && linkdestcont == 0) { + /* ups, RR standard violation */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, 0, + "New SL entry found without previous" + "CONTINUE flag. Ignored"); + continue; + } + ret = read_rr_SL(sue, &linkdest, &linkdestcont); + if (ret < 0) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret, + "Invalid SL entry"); + } + } else if (SUSP_SIG(sue, 'R', 'E')) { + /* + * this directory entry refers to a relocated directory. + * We simply ignore it, as it will be correctly handled + * when found the CL + */ + susp_iter_free(iter); + free(name); + return 0; /* it's not an error */ + } else if (SUSP_SIG(sue, 'C', 'L')) { + /* + * This entry is a placeholder for a relocated dir. + * We need to ignore other entries, with the exception of NM. + * Then we create a directory node that represents the + * relocated dir, and iterate over its children. + */ + relocated_dir = iso_read_bb(sue->data.CL.child_loc, 4, NULL); + if (relocated_dir == 0) { + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "Invalid SL entry, no child location"); + break; + } + } else if (SUSP_SIG(sue, 'P', 'N')) { + ret = read_rr_PN(sue, &atts); + if (ret < 0) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR_WARN, ret, + "Invalid PN entry"); + } + } else if (SUSP_SIG(sue, 'S', 'F')) { + ret = iso_msg_submit(fsdata->msgid, ISO_UNSUPPORTED_RR, 0, + "Sparse files not supported."); + break; + } else if (SUSP_SIG(sue, 'R', 'R')) { + /* TODO I've seen this RR on mkisofs images. what's this? */ + continue; + } else if (SUSP_SIG(sue, 'S', 'P')) { + /* + * Ignore this, to prevent the hint message, if we are dealing + * with root node (SP is only valid in "." of root node) + */ + if (parent != NULL) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "SP entry found in a directory entry other " + "than '.' entry of root node"); + } + continue; + } else if (SUSP_SIG(sue, 'E', 'R')) { + /* + * Ignore this, to prevent the hint message, if we are dealing + * with root node (ER is only valid in "." of root node) + */ + if (parent != NULL) { + /* notify and continue */ + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "ER entry found in a directory entry other " + "than '.' entry of root node"); + } + continue; + } else { + ret = iso_msg_submit(fsdata->msgid, ISO_SUSP_UNHANDLED, 0, + "Unhandled SUSP entry %c%c.", sue->sig[0], sue->sig[1]); + } + } + + susp_iter_free(iter); + + /* check for RR problems */ + + if (ret < 0) { + /* error was already submitted above */ + iso_msg_debug(fsdata->msgid, "Error parsing RR entries"); + } else if (!relocated_dir && atts.st_mode == (mode_t) 0 ) { + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, "Mandatory " + "Rock Ridge PX entry is not present or it " + "contains invalid values."); + } else { + /* ensure both name and link dest are finished */ + if (namecont != 0) { + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "Incomplete RR name, last NM entry continues"); + } + if (linkdestcont != 0) { + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "Incomplete link destination, last SL entry continues"); + } + } + + if (ret < 0) { + free(name); + return ret; + } + + /* convert name to needed charset */ + if (strcmp(fsdata->input_charset, fsdata->local_charset) && name) { + /* we need to convert name charset */ + char *newname = NULL; + ret = strconv(name, fsdata->input_charset, fsdata->local_charset, + &newname); + if (ret < 0) { + /* its just a hint message */ + ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET, + ret, "Charset conversion error. Can't " + "convert %s from %s to %s", name, + fsdata->input_charset, fsdata->local_charset); + free(newname); + if (ret < 0) { + free(name); + return ret; + } + } else { + free(name); + name = newname; + } + } + + /* convert link destination to needed charset */ + if (strcmp(fsdata->input_charset, fsdata->local_charset) && linkdest) { + /* we need to convert name charset */ + char *newlinkdest = NULL; + ret = strconv(linkdest, fsdata->input_charset, + fsdata->local_charset, &newlinkdest); + if (ret < 0) { + ret = iso_msg_submit(fsdata->msgid, ISO_FILENAME_WRONG_CHARSET, + ret, "Charset conversion error. Can't " + "convert %s from %s to %s", name, + fsdata->input_charset, fsdata->local_charset); + free(newlinkdest); + if (ret < 0) { + free(name); + return ret; + } + } else { + free(linkdest); + linkdest = newlinkdest; + } + } + + } else { + /* RR extensions are not read / used */ + atts.st_gid = fsdata->gid; + atts.st_uid = fsdata->uid; + if (record->flags[0] & 0x02) { + atts.st_mode = S_IFDIR | fsdata->dir_mode; + } else { + atts.st_mode = S_IFREG | fsdata->file_mode; + } + } + + /* + * if we haven't RR extensions, or no NM entry is present, + * we use the name in directory record + */ + if (!name) { + size_t len; + + if (record->len_fi[0] == 1 && record->file_id[0] == 0) { + /* "." entry, we can call this for root node, so... */ + if (!(atts.st_mode & S_IFDIR)) { + return iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, + "Wrong ISO file name. \".\" not dir"); + } + } else { + + name = get_name(fsdata, (char*)record->file_id, record->len_fi[0]); + if (name == NULL) { + return iso_msg_submit(fsdata->msgid, ISO_WRONG_ECMA119, 0, + "Can't retrieve file name"); + } + + /* remove trailing version number */ + len = strlen(name); + if (len > 2 && name[len-2] == ';' && name[len-1] == '1') { + if (len > 3 && name[len-3] == '.') { + /* + * the "." is mandatory, so in most cases is included only + * for standard compliance + */ + name[len-3] = '\0'; + } else { + name[len-2] = '\0'; + } + } + } + } + + if (relocated_dir) { + + /* + * We are dealing with a placeholder for a relocated dir. + * Thus, we need to read attributes for this directory from the "." + * entry of the relocated dir. + */ + uint8_t buffer[BLOCK_SIZE]; + + ret = fsdata->src->read_block(fsdata->src, relocated_dir, buffer); + if (ret < 0) { + return ret; + } + + ret = iso_file_source_new_ifs(fs, parent, (struct ecma119_dir_record*) + buffer, src); + if (ret <= 0) { + return ret; + } + + /* but the real name is the name of the placeholder */ + ifsdata = (ImageFileSourceData*) (*src)->data; + ifsdata->name = name; + return ISO_SUCCESS; + } + + if (fsdata->rr != RR_EXT_112) { + /* + * Only RRIP 1.12 provides valid inode numbers. If not, it is not easy + * to generate those serial numbers, and we use extend block instead. + * It BREAKS POSIX SEMANTICS, but its suitable for our needs + */ + atts.st_ino = (ino_t) iso_read_bb(record->block, 4, NULL); + if (fsdata->rr == 0) { + atts.st_nlink = 1; + } + } + + /* + * if we haven't RR extensions, or a needed TF time stamp is not present, + * we use plain iso recording time + */ + recorded = iso_datetime_read_7(record->recording_time); + if (atts.st_atime == (time_t) 0) { + atts.st_atime = recorded; + } + if (atts.st_ctime == (time_t) 0) { + atts.st_ctime = recorded; + } + if (atts.st_mtime == (time_t) 0) { + atts.st_mtime = recorded; + } + + /* the size is read from iso directory record */ + atts.st_size = iso_read_bb(record->length, 4, NULL); + + /* Fill last entries */ + atts.st_dev = fsdata->id; + atts.st_blksize = BLOCK_SIZE; + atts.st_blocks = DIV_UP(atts.st_size, BLOCK_SIZE); + + /* TODO #00014 : more sanity checks to ensure dir record info is valid */ + if (S_ISLNK(atts.st_mode) && (linkdest == NULL)) { + ret = iso_msg_submit(fsdata->msgid, ISO_WRONG_RR, 0, + "Link without destination."); + free(name); + return ret; + } + + /* ok, we can now create the file source */ + ifsdata = calloc(1, sizeof(ImageFileSourceData)); + if (ifsdata == NULL) { + ret = ISO_OUT_OF_MEM; + goto ifs_cleanup; + } + ifsrc = calloc(1, sizeof(IsoFileSource)); + if (ifsrc == NULL) { + ret = ISO_OUT_OF_MEM; + goto ifs_cleanup; + } + + /* fill data */ + ifsdata->fs = fs; + iso_filesystem_ref(fs); + if (parent != NULL) { + ifsdata->parent = parent; + iso_file_source_ref(parent); + } + ifsdata->info = atts; + ifsdata->name = name; + ifsdata->block = iso_read_bb(record->block, 4, NULL); + + if (S_ISLNK(atts.st_mode)) { + ifsdata->data.content = linkdest; + } + + ifsrc->class = &ifs_class; + ifsrc->data = ifsdata; + ifsrc->refcount = 1; + + *src = ifsrc; + return ISO_SUCCESS; + +ifs_cleanup: ; + free(name); + free(linkdest); + free(ifsdata); + free(ifsrc); + return ret; +} + +static +int ifs_get_root(IsoFilesystem *fs, IsoFileSource **root) +{ + int ret; + _ImageFsData *data; + uint8_t buffer[BLOCK_SIZE]; + + if (fs == NULL || fs->data == NULL || root == NULL) { + return ISO_NULL_POINTER; + } + + data = (_ImageFsData*)fs->data; + + /* open the filesystem */ + ret = ifs_fs_open((IsoImageFilesystem*)fs); + if (ret < 0) { + return ret; + } + + /* read extend for root record */ + ret = data->src->read_block(data->src, data->iso_root_block, buffer); + if (ret < 0) { + ifs_fs_close((IsoImageFilesystem*)fs); + return ret; + } + + /* get root attributes from "." entry */ + ret = iso_file_source_new_ifs((IsoImageFilesystem*)fs, NULL, + (struct ecma119_dir_record*) buffer, root); + + ifs_fs_close((IsoImageFilesystem*)fs); + return ret; +} + +/** + * Find a file inside a node. + * + * @param file + * it is not modified if requested file is not found + * @return + * 1 success, 0 not found, < 0 error + */ +static +int ifs_get_file(IsoFileSource *dir, const char *name, IsoFileSource **file) +{ + int ret; + IsoFileSource *src; + + ret = iso_file_source_open(dir); + if (ret < 0) { + return ret; + } + while ((ret = iso_file_source_readdir(dir, &src)) == 1) { + char *fname = iso_file_source_get_name(src); + if (!strcmp(name, fname)) { + free(fname); + *file = src; + ret = ISO_SUCCESS; + break; + } + free(fname); + iso_file_source_unref(src); + } + iso_file_source_close(dir); + return ret; +} + +static +int ifs_get_by_path(IsoFilesystem *fs, const char *path, IsoFileSource **file) +{ + int ret; + _ImageFsData *data; + IsoFileSource *src; + char *ptr, *brk_info, *component; + + if (fs == NULL || fs->data == NULL || path == NULL || file == NULL) { + return ISO_NULL_POINTER; + } + + if (path[0] != '/') { + /* only absolute paths supported */ + return ISO_FILE_BAD_PATH; + } + + data = (_ImageFsData*)fs->data; + + /* open the filesystem */ + ret = ifs_fs_open((IsoImageFilesystem*)fs); + if (ret < 0) { + return ret; + } + + ret = ifs_get_root(fs, &src); + if (ret < 0) { + return ret; + } + if (!strcmp(path, "/")) { + /* we are looking for root */ + *file = src; + ret = ISO_SUCCESS; + goto get_path_exit; + } + + ptr = strdup(path); + if (ptr == NULL) { + iso_file_source_unref(src); + ret = ISO_OUT_OF_MEM; + goto get_path_exit; + } + + component = strtok_r(ptr, "/", &brk_info); + while (component) { + IsoFileSource *child = NULL; + + ImageFileSourceData *fdata; + fdata = src->data; + if (!S_ISDIR(fdata->info.st_mode)) { + ret = ISO_FILE_BAD_PATH; + break; + } + + ret = ifs_get_file(src, component, &child); + iso_file_source_unref(src); + if (ret <= 0) { + break; + } + + src = child; + component = strtok_r(NULL, "/", &brk_info); + } + + free(ptr); + if (ret < 0) { + iso_file_source_unref(src); + } else if (ret == 0) { + ret = ISO_FILE_DOESNT_EXIST; + } else { + *file = src; + } + + get_path_exit:; + ifs_fs_close((IsoImageFilesystem*)fs); + return ret; +} + +unsigned int ifs_get_id(IsoFilesystem *fs) +{ + return ISO_IMAGE_FS_ID; +} + +static +int ifs_fs_open(IsoImageFilesystem *fs) +{ + _ImageFsData *data; + + if (fs == NULL || fs->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (_ImageFsData*)fs->data; + + if (data->open_count == 0) { + /* we need to actually open the data source */ + int res = data->src->open(data->src); + if (res < 0) { + return res; + } + } + ++data->open_count; + return ISO_SUCCESS; +} + +static +int ifs_fs_close(IsoImageFilesystem *fs) +{ + _ImageFsData *data; + + if (fs == NULL || fs->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (_ImageFsData*)fs->data; + + if (--data->open_count == 0) { + /* we need to actually close the data source */ + return data->src->close(data->src); + } + return ISO_SUCCESS; +} + +static +void ifs_fs_free(IsoFilesystem *fs) +{ + IsoImageFilesystem *ifs; + _ImageFsData *data; + + ifs = (IsoImageFilesystem*)fs; + data = (_ImageFsData*) fs->data; + + /* close data source if already openned */ + if (data->open_count > 0) { + data->src->close(data->src); + } + + /* free our ref to datasource */ + iso_data_source_unref(data->src); + + /* free volume atts */ + free(data->volset_id); + free(data->volume_id); + free(data->publisher_id); + free(data->data_preparer_id); + free(data->system_id); + free(data->application_id); + free(data->copyright_file_id); + free(data->abstract_file_id); + free(data->biblio_file_id); + + free(data->input_charset); + free(data->local_charset); + free(data); +} + +/** + * Read the SUSP system user entries of the "." entry of the root directory, + * indentifying when Rock Ridge extensions are being used. + * + * @return + * 1 success, 0 ignored, < 0 error + */ +static +int read_root_susp_entries(_ImageFsData *data, uint32_t block) +{ + int ret; + unsigned char buffer[2048]; + struct ecma119_dir_record *record; + struct susp_sys_user_entry *sue; + SuspIterator *iter; + + ret = data->src->read_block(data->src, block, buffer); + if (ret < 0) { + return ret; + } + + /* record will be the "." directory entry for the root record */ + record = (struct ecma119_dir_record *)buffer; + + /* + * TODO #00015 : take care of CD-ROM XA discs when reading SP entry + * SUSP specification claims that for CD-ROM XA the SP entry + * is not at position BP 1, but at BP 15. Is that used? + * In that case, we need to set info->len_skp to 15!! + */ + + iter = susp_iter_new(data->src, record, data->len_skp, data->msgid); + if (iter == NULL) { + return ISO_OUT_OF_MEM; + } + + /* first entry must be an SP system use entry */ + ret = susp_iter_next(iter, &sue); + if (ret < 0) { + /* error */ + susp_iter_free(iter); + return ret; + } else if (ret == 0 || !SUSP_SIG(sue, 'S', 'P') ) { + iso_msg_debug(data->msgid, "SUSP/RR is not being used."); + susp_iter_free(iter); + return ISO_SUCCESS; + } + + /* it is a SP system use entry */ + if (sue->version[0] != 1 || sue->data.SP.be[0] != 0xBE + || sue->data.SP.ef[0] != 0xEF) { + + susp_iter_free(iter); + return iso_msg_submit(data->msgid, ISO_UNSUPPORTED_SUSP, 0, + "SUSP SP system use entry seems to be wrong. " + "Ignoring Rock Ridge Extensions."); + } + + iso_msg_debug(data->msgid, "SUSP/RR is being used."); + + /* + * The LEN_SKP field, defined in IEEE 1281, SUSP. 5.3, specifies the + * number of bytes to be skipped within each System Use field. + * I think this will be always 0, but given that support this standard + * feature is easy... + */ + data->len_skp = sue->data.SP.len_skp[0]; + + /* + * Ok, now search for ER entry. + * Just notice that the attributes for root dir are read elsewhere. + * + * TODO #00016 : handle non RR ER entries + * + * if several ER are present, we need to identify the position of + * what refers to RR, and then look for corresponding ES entry in + * each directory record. I have not implemented this (it's not used, + * no?), but if we finally need it, it can be easily implemented in + * the iterator, transparently for the rest of the code. + */ + while ((ret = susp_iter_next(iter, &sue)) > 0) { + + /* ignore entries from different version */ + if (sue->version[0] != 1) + continue; + + if (SUSP_SIG(sue, 'E', 'R')) { + + if (data->rr_version) { + ret = iso_msg_submit(data->msgid, ISO_SUSP_MULTIPLE_ER, 0, + "More than one ER has found. This is not supported. " + "It will be ignored, but can cause problems. " + "Please notify us about this."); + if (ret < 0) { + break; + } + } + + /* + * it seems that Rock Ridge can be identified with any + * of the following + */ + if ( sue->data.ER.len_id[0] == 10 && + !strncmp((char*)sue->data.ER.ext_id, "RRIP_1991A", 10) ) { + + iso_msg_debug(data->msgid, + "Suitable Rock Ridge ER found. Version 1.10."); + data->rr_version = RR_EXT_110; + + } else if ( (sue->data.ER.len_id[0] == 10 && + !strncmp((char*)sue->data.ER.ext_id, "IEEE_P1282", 10)) + || (sue->data.ER.len_id[0] == 9 && + !strncmp((char*)sue->data.ER.ext_id, "IEEE_1282", 9)) ) { + + iso_msg_debug(data->msgid, + "Suitable Rock Ridge ER found. Version 1.12."); + data->rr_version = RR_EXT_112; + } else { + ret = iso_msg_submit(data->msgid, ISO_SUSP_MULTIPLE_ER, 0, + "Not Rock Ridge ER found.\n" + "That will be ignored, but can cause problems in " + "image reading. Please notify us about this"); + if (ret < 0) { + break; + } + } + } + } + + susp_iter_free(iter); + + if (ret < 0) { + return ret; + } + + return ISO_SUCCESS; +} + +static +int read_pvm(_ImageFsData *data, uint32_t block) +{ + int ret; + struct ecma119_pri_vol_desc *pvm; + struct ecma119_dir_record *rootdr; + uint8_t buffer[BLOCK_SIZE]; + + /* read PVM */ + ret = data->src->read_block(data->src, block, buffer); + if (ret < 0) { + return ret; + } + + pvm = (struct ecma119_pri_vol_desc *)buffer; + + /* sanity checks */ + if (pvm->vol_desc_type[0] != 1 || pvm->vol_desc_version[0] != 1 + || strncmp((char*)pvm->std_identifier, "CD001", 5) + || pvm->file_structure_version[0] != 1) { + + return ISO_WRONG_PVD; + } + + /* ok, it is a valid PVD */ + + /* fill volume attributes */ + /* TODO take care of input charset */ + data->volset_id = strcopy((char*)pvm->vol_set_id, 128); + data->volume_id = strcopy((char*)pvm->volume_id, 32); + data->publisher_id = strcopy((char*)pvm->publisher_id, 128); + data->data_preparer_id = strcopy((char*)pvm->data_prep_id, 128); + data->system_id = strcopy((char*)pvm->system_id, 32); + data->application_id = strcopy((char*)pvm->application_id, 128); + data->copyright_file_id = strcopy((char*)pvm->copyright_file_id, 37); + data->abstract_file_id = strcopy((char*)pvm->abstract_file_id, 37); + data->biblio_file_id = strcopy((char*)pvm->bibliographic_file_id, 37); + + data->nblocks = iso_read_bb(pvm->vol_space_size, 4, NULL); + + rootdr = (struct ecma119_dir_record*) pvm->root_dir_record; + data->pvd_root_block = iso_read_bb(rootdr->block, 4, NULL); + + /* + * TODO #00017 : take advantage of other atts of PVD + * PVD has other things that could be interesting, but that don't have a + * member in IsoImage, such as creation date. In a multisession disc, we + * could keep the creation date and update the modification date, for + * example. + */ + + return ISO_SUCCESS; +} + +/** + * @return + * 1 success, 0 ignored, < 0 error + */ +static +int read_el_torito_boot_catalog(_ImageFsData *data, uint32_t block) +{ + int ret; + struct el_torito_validation_entry *ve; + struct el_torito_default_entry *entry; + unsigned char buffer[BLOCK_SIZE]; + + ret = data->src->read_block(data->src, block, buffer); + if (ret < 0) { + return ret; + } + + ve = (struct el_torito_validation_entry*)buffer; + + /* check if it is a valid catalog (TODO: check also the checksum)*/ + if ( (ve->header_id[0] != 1) || (ve->key_byte1[0] != 0x55) + || (ve->key_byte2[0] != 0xAA) ) { + + return iso_msg_submit(data->msgid, ISO_WRONG_EL_TORITO, 0, + "Wrong or damaged El-Torito Catalog. El-Torito info " + "will be ignored."); + } + + /* check for a valid platform */ + if (ve->platform_id[0] != 0) { + return iso_msg_submit(data->msgid, ISO_UNSUPPORTED_EL_TORITO, 0, + "Unsupported El-Torito platform. Only 80x86 is " + "supported. El-Torito info will be ignored."); + } + + /* ok, once we are here we assume it is a valid catalog */ + + /* parse the default entry */ + entry = (struct el_torito_default_entry *)(buffer + 32); + + data->eltorito = 1; + data->bootable = entry->boot_indicator[0] ? 1 : 0; + data->type = entry->boot_media_type[0]; + data->partition_type = entry->system_type[0]; + data->load_seg = iso_read_lsb(entry->load_seg, 2); + data->load_size = iso_read_lsb(entry->sec_count, 2); + data->imgblock = iso_read_lsb(entry->block, 4); + + /* TODO #00018 : check if there are more entries in the boot catalog */ + + return ISO_SUCCESS; +} + +int iso_image_filesystem_new(IsoDataSource *src, struct iso_read_opts *opts, + int msgid, IsoImageFilesystem **fs) +{ + int ret; + uint32_t block; + IsoImageFilesystem *ifs; + _ImageFsData *data; + uint8_t buffer[BLOCK_SIZE]; + + if (src == NULL || opts == NULL || fs == NULL) { + return ISO_NULL_POINTER; + } + + data = calloc(1, sizeof(_ImageFsData)); + if (data == NULL) { + return ISO_OUT_OF_MEM; + } + + ifs = calloc(1, sizeof(IsoImageFilesystem)); + if (ifs == NULL) { + free(data); + return ISO_OUT_OF_MEM; + } + + /* get our ref to IsoDataSource */ + data->src = src; + iso_data_source_ref(src); + data->open_count = 0; + + /* get an id for the filesystem */ + data->id = ++fs_dev_id; + + /* fill data from opts */ + data->gid = opts->gid; + data->uid = opts->uid; + data->file_mode = opts->file_mode & ~S_IFMT; + data->dir_mode = opts->dir_mode & ~S_IFMT; + data->msgid = msgid; + + setlocale(LC_CTYPE, ""); + data->local_charset = strdup(nl_langinfo(CODESET)); + if (data->local_charset == NULL) { + ret = ISO_OUT_OF_MEM; + goto fs_cleanup; + } + + strncpy(ifs->type, "iso ", 4); + ifs->data = data; + ifs->refcount = 1; + ifs->version = 0; + ifs->get_root = ifs_get_root; + ifs->get_by_path = ifs_get_by_path; + ifs->get_id = ifs_get_id; + ifs->open = ifs_fs_open; + ifs->close = ifs_fs_close; + ifs->free = ifs_fs_free; + + /* read Volume Descriptors and ensure it is a valid image */ + + /* 1. first, open the filesystem */ + ifs_fs_open(ifs); + + /* 2. read primary volume description */ + ret = read_pvm(data, opts->block + 16); + if (ret < 0) { + goto fs_cleanup; + } + + /* 3. read next volume descriptors */ + block = opts->block + 17; + do { + ret = src->read_block(src, block, buffer); + if (ret < 0) { + /* cleanup and exit */ + goto fs_cleanup; + } + switch (buffer[0]) { + case 0: + /* boot record */ + { + struct ecma119_boot_rec_vol_desc *vol; + vol = (struct ecma119_boot_rec_vol_desc*)buffer; + + /* some sanity checks */ + if (strncmp((char*)vol->std_identifier, "CD001", 5) + || vol->vol_desc_version[0] != 1 + || strncmp((char*)vol->boot_sys_id, + "EL TORITO SPECIFICATION", 23)) { + + ret = iso_msg_submit(data->msgid, + ISO_UNSUPPORTED_EL_TORITO, 0, + "Unsupported Boot Vol. Desc. Only El-Torito " + "Specification, Version 1.0 Volume " + "Descriptors are supported. Ignoring boot info"); + if (ret < 0) { + goto fs_cleanup; + } + break; + } + data->catblock = iso_read_lsb(vol->boot_catalog, 4); + ret = read_el_torito_boot_catalog(data, data->catblock); + if (ret < 0) { + goto fs_cleanup; + } + } + break; + case 2: + /* suplementary volume descritor */ + { + struct ecma119_sup_vol_desc *sup; + struct ecma119_dir_record *root; + + sup = (struct ecma119_sup_vol_desc*)buffer; + if (sup->esc_sequences[0] == 0x25 && + sup->esc_sequences[1] == 0x2F && + (sup->esc_sequences[2] == 0x40 || + sup->esc_sequences[2] == 0x43 || + sup->esc_sequences[2] == 0x45) ) { + + /* it's a Joliet Sup. Vol. Desc. */ + iso_msg_debug(data->msgid, "Found Joliet extensions"); + data->joliet = 1; + root = (struct ecma119_dir_record*)sup->root_dir_record; + data->svd_root_block = iso_read_bb(root->block, 4, NULL); + /* TODO #00019 : set IsoImage attribs from Joliet SVD? */ + /* TODO #00020 : handle RR info in Joliet tree */ + } else if (sup->vol_desc_version[0] == 2) { + /* + * It is an Enhanced Volume Descriptor, image is an + * ISO 9660:1999 + */ + iso_msg_debug(data->msgid, "Found ISO 9660:1999"); + data->iso1999 = 1; + root = (struct ecma119_dir_record*)sup->root_dir_record; + data->evd_root_block = iso_read_bb(root->block, 4, NULL); + /* TODO #00021 : handle RR info in ISO 9660:1999 tree */ + } else { + ret = iso_msg_submit(data->msgid, ISO_UNSUPPORTED_VD, 0, + "Unsupported Sup. Vol. Desc found."); + if (ret < 0) { + goto fs_cleanup; + } + } + } + break; + case 255: + /* + * volume set terminator + * ignore, as it's checked in loop end condition + */ + break; + default: + ret = iso_msg_submit(data->msgid, ISO_UNSUPPORTED_VD, 0, + "Ignoring Volume descriptor %x.", buffer[0]); + if (ret < 0) { + goto fs_cleanup; + } + break; + } + block++; + } while (buffer[0] != 255); + + /* 4. check if RR extensions are being used */ + ret = read_root_susp_entries(data, data->pvd_root_block); + if (ret < 0) { + goto fs_cleanup; + } + + /* user doesn't want to read RR extensions */ + if (opts->norock) { + data->rr = RR_EXT_NO; + } else { + data->rr = data->rr_version; + } + + /* select what tree to read */ + if (data->rr) { + /* RR extensions are available */ + if (!opts->nojoliet && opts->preferjoliet && data->joliet) { + /* if user prefers joliet, that is used */ + iso_msg_debug(data->msgid, "Reading Joliet extensions."); + data->input_charset = strdup("UCS-2BE"); + data->rr = RR_EXT_NO; + data->iso_root_block = data->svd_root_block; + } else { + /* RR will be used */ + iso_msg_debug(data->msgid, "Reading Rock Ridge extensions."); + data->iso_root_block = data->pvd_root_block; + } + } else { + /* RR extensions are not available */ + if (!opts->nojoliet && data->joliet) { + /* joliet will be used */ + iso_msg_debug(data->msgid, "Reading Joliet extensions."); + data->input_charset = strdup("UCS-2BE"); + data->iso_root_block = data->svd_root_block; + } else if (!opts->noiso1999 && data->iso1999) { + /* we will read ISO 9660:1999 */ + iso_msg_debug(data->msgid, "Reading ISO-9660:1999 tree."); + data->iso_root_block = data->evd_root_block; + } else { + /* default to plain iso */ + iso_msg_debug(data->msgid, "Reading plain ISO-9660 tree."); + data->iso_root_block = data->pvd_root_block; + data->input_charset = strdup("ASCII"); + } + } + + if (data->input_charset == NULL) { + if (opts->input_charset != NULL) { + data->input_charset = strdup(opts->input_charset); + } else { + data->input_charset = strdup(data->local_charset); + } + } + if (data->input_charset == NULL) { + ret = ISO_OUT_OF_MEM; + goto fs_cleanup; + } + + /* and finally return. Note that we keep the DataSource opened */ + + *fs = ifs; + return ISO_SUCCESS; + + fs_cleanup: ; + ifs_fs_free(ifs); + free(ifs); + return ret; +} + +static +int image_builder_create_node(IsoNodeBuilder *builder, IsoImage *image, + IsoFileSource *src, IsoNode **node) +{ + int ret; + struct stat info; + IsoNode *new; + char *name; + ImageFileSourceData *data; + + if (builder == NULL || src == NULL || node == NULL || src->data == NULL) { + return ISO_NULL_POINTER; + } + + data = (ImageFileSourceData*)src->data; + + name = iso_file_source_get_name(src); + + /* get info about source */ + ret = iso_file_source_lstat(src, &info); + if (ret < 0) { + return ret; + } + + new = NULL; + switch (info.st_mode & S_IFMT) { + case S_IFREG: + { + /* source is a regular file */ + _ImageFsData *fsdata = data->fs->data; + + if (fsdata->eltorito && data->block == fsdata->catblock) { + + if (image->bootcat->node != NULL) { + ret = iso_msg_submit(image->id, ISO_EL_TORITO_WARN, 0, + "More than one catalog node has been found. " + "We can continue, but that could lead to " + "problems"); + if (ret < 0) { + return ret; + } + iso_node_unref((IsoNode*)image->bootcat->node); + } + + /* we create a placeholder for the catalog instead of + * a regular file */ + new = calloc(1, sizeof(IsoBoot)); + if (new == NULL) { + ret = ISO_OUT_OF_MEM; + free(name); + return ret; + } + + /* and set the image node */ + image->bootcat->node = (IsoBoot*)new; + new->type = LIBISO_BOOT; + new->refcount = 1; + } else { + IsoStream *stream; + IsoFile *file; + + ret = iso_file_source_stream_new(src, &stream); + if (ret < 0) { + free(name); + return ret; + } + /* take a ref to the src, as stream has taken our ref */ + iso_file_source_ref(src); + + file = calloc(1, sizeof(IsoFile)); + if (file == NULL) { + free(name); + iso_stream_unref(stream); + return ISO_OUT_OF_MEM; + } + + /* the msblock is taken from the image */ + file->msblock = data->block; + + /* + * and we set the sort weight based on the block on image, to + * improve performance on image modifying. + */ + file->sort_weight = INT_MAX - data->block; + + file->stream = stream; + file->node.type = LIBISO_FILE; + new = (IsoNode*) file; + new->refcount = 0; + + if (fsdata->eltorito && data->block == fsdata->imgblock) { + /* it is boot image node */ + if (image->bootcat->image->image != NULL) { + ret = iso_msg_submit(image->id, ISO_EL_TORITO_WARN, 0, + "More than one image node has been found."); + if (ret < 0) { + free(name); + iso_stream_unref(stream); + return ret; + } + } else { + /* and set the image node */ + image->bootcat->image->image = file; + new->refcount++; + } + } + } + } + break; + case S_IFDIR: + { + /* source is a directory */ + new = calloc(1, sizeof(IsoDir)); + if (new == NULL) { + free(name); + return ISO_OUT_OF_MEM; + } + new->type = LIBISO_DIR; + new->refcount = 0; + } + break; + case S_IFLNK: + { + /* source is a symbolic link */ + char dest[PATH_MAX]; + IsoSymlink *link; + + ret = iso_file_source_readlink(src, dest, PATH_MAX); + if (ret < 0) { + free(name); + return ret; + } + link = malloc(sizeof(IsoSymlink)); + if (link == NULL) { + free(name); + return ISO_OUT_OF_MEM; + } + link->dest = strdup(dest); + link->node.type = LIBISO_SYMLINK; + new = (IsoNode*) link; + new->refcount = 0; + } + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + { + /* source is an special file */ + IsoSpecial *special; + special = malloc(sizeof(IsoSpecial)); + if (special == NULL) { + free(name); + return ISO_OUT_OF_MEM; + } + special->dev = info.st_rdev; + special->node.type = LIBISO_SPECIAL; + new = (IsoNode*) special; + new->refcount = 0; + } + break; + } + + /* fill fields */ + new->refcount++; + new->name = name; + new->mode = info.st_mode; + new->uid = info.st_uid; + new->gid = info.st_gid; + new->atime = info.st_atime; + new->mtime = info.st_mtime; + new->ctime = info.st_ctime; + + new->hidden = 0; + + new->parent = NULL; + new->next = NULL; + + *node = new; + return ISO_SUCCESS; +} + +/** + * Create a new builder, that is exactly a copy of an old builder, but where + * create_node() function has been replaced by image_builder_create_node. + */ +static +int iso_image_builder_new(IsoNodeBuilder *old, IsoNodeBuilder **builder) +{ + IsoNodeBuilder *b; + + if (builder == NULL) { + return ISO_NULL_POINTER; + } + + b = malloc(sizeof(IsoNodeBuilder)); + if (b == NULL) { + return ISO_OUT_OF_MEM; + } + + b->refcount = 1; + b->create_file_data = old->create_file_data; + b->create_node_data = old->create_node_data; + b->create_file = old->create_file; + b->create_node = image_builder_create_node; + b->free = old->free; + + *builder = b; + return ISO_SUCCESS; +} + +/** + * Create a file source to access the El-Torito boot image, when it is not + * accessible from the ISO filesystem. + */ +static +int create_boot_img_filesrc(IsoImageFilesystem *fs, IsoFileSource **src) +{ + int ret; + struct stat atts; + _ImageFsData *fsdata; + IsoFileSource *ifsrc = NULL; + ImageFileSourceData *ifsdata = NULL; + + if (fs == NULL || fs->data == NULL || src == NULL) { + return ISO_NULL_POINTER; + } + + fsdata = (_ImageFsData*)fs->data; + + memset(&atts, 0, sizeof(struct stat)); + atts.st_mode = S_IFREG; + atts.st_ino = fsdata->imgblock; /* not the best solution, but... */ + atts.st_nlink = 1; + + /* + * this is the greater problem. We don't know the size. For now, we + * just use a single block of data. In a future, maybe we could figure out + * a better idea. Another alternative is to use several blocks, that way + * is less probable that we throw out valid data. + */ + atts.st_size = (off_t)BLOCK_SIZE; + + /* Fill last entries */ + atts.st_dev = fsdata->id; + atts.st_blksize = BLOCK_SIZE; + atts.st_blocks = DIV_UP(atts.st_size, BLOCK_SIZE); + + /* ok, we can now create the file source */ + ifsdata = calloc(1, sizeof(ImageFileSourceData)); + if (ifsdata == NULL) { + ret = ISO_OUT_OF_MEM; + goto boot_fs_cleanup; + } + ifsrc = calloc(1, sizeof(IsoFileSource)); + if (ifsrc == NULL) { + ret = ISO_OUT_OF_MEM; + goto boot_fs_cleanup; + } + + /* fill data */ + ifsdata->fs = fs; + iso_filesystem_ref(fs); + ifsdata->parent = NULL; + ifsdata->info = atts; + ifsdata->name = NULL; + ifsdata->block = fsdata->imgblock; + + ifsrc->class = &ifs_class; + ifsrc->data = ifsdata; + ifsrc->refcount = 1; + + *src = ifsrc; + return ISO_SUCCESS; + +boot_fs_cleanup: ; + free(ifsdata); + free(ifsrc); + return ret; +} + + +int iso_image_import(IsoImage *image, IsoDataSource *src, + struct iso_read_opts *opts, + IsoReadImageFeatures **features) +{ + int ret; + IsoImageFilesystem *fs; + IsoFilesystem *fsback; + IsoNodeBuilder *blback; + IsoDir *oldroot; + IsoFileSource *newroot; + _ImageFsData *data; + struct el_torito_boot_catalog *oldbootcat; + + if (image == NULL || src == NULL || opts == NULL) { + return ISO_NULL_POINTER; + } + + ret = iso_image_filesystem_new(src, opts, image->id, &fs); + if (ret < 0) { + return ret; + } + data = fs->data; + + /* get root from filesystem */ + ret = fs->get_root(fs, &newroot); + if (ret < 0) { + return ret; + } + + /* backup image filesystem, builder and root */ + fsback = image->fs; + blback = image->builder; + oldroot = image->root; + oldbootcat = image->bootcat; /* could be NULL */ + + image->bootcat = NULL; + + /* create new builder */ + ret = iso_image_builder_new(blback, &image->builder); + if (ret < 0) { + goto import_revert; + } + + image->fs = fs; + + /* create new root, and set root attributes from source */ + ret = iso_node_new_root(&image->root); + if (ret < 0) { + goto import_revert; + } + { + struct stat info; + + /* I know this will not fail */ + iso_file_source_lstat(newroot, &info); + image->root->node.mode = info.st_mode; + image->root->node.uid = info.st_uid; + image->root->node.gid = info.st_gid; + image->root->node.atime = info.st_atime; + image->root->node.mtime = info.st_mtime; + image->root->node.ctime = info.st_ctime; + } + + /* if old image has el-torito, add a new catalog */ + if (data->eltorito) { + struct el_torito_boot_catalog *catalog; + ElToritoBootImage *boot_image= NULL; + + boot_image = calloc(1, sizeof(ElToritoBootImage)); + if (boot_image == NULL) { + ret = ISO_OUT_OF_MEM; + goto import_revert; + } + boot_image->bootable = data->bootable; + boot_image->type = data->type; + boot_image->partition_type = data->partition_type; + boot_image->load_seg = data->load_seg; + boot_image->load_size = data->load_size; + + catalog = calloc(1, sizeof(struct el_torito_boot_catalog)); + if (catalog == NULL) { + ret = ISO_OUT_OF_MEM; + goto import_revert; + } + catalog->image = boot_image; + image->bootcat = catalog; + } + + /* recursively add image */ + ret = iso_add_dir_src_rec(image, image->root, newroot); + + /* error during recursive image addition? */ + if (ret < 0) { + iso_node_builder_unref(image->builder); + goto import_revert; + } + + if (data->eltorito) { + /* if catalog and image nodes were not filled, we create them here */ + if (image->bootcat->image->image == NULL) { + IsoFileSource *src; + IsoNode *node; + ret = create_boot_img_filesrc(fs, &src); + if (ret < 0) { + iso_node_builder_unref(image->builder); + goto import_revert; + } + ret = image_builder_create_node(image->builder, image, src, &node); + if (ret < 0) { + iso_node_builder_unref(image->builder); + goto import_revert; + } + image->bootcat->image->image = (IsoFile*)node; + + /* warn about hidden images */ + iso_msg_submit(image->id, ISO_EL_TORITO_HIDDEN, 0, + "Found hidden El-Torito image. Its size could not " + "be figure out, so image modify or boot image " + "patching may lead to bad results."); + } + if (image->bootcat->node == NULL) { + IsoNode *node = calloc(1, sizeof(IsoBoot)); + if (node == NULL) { + ret = ISO_OUT_OF_MEM; + goto import_revert; + } + node->type = LIBISO_BOOT; + node->mode = S_IFREG; + node->refcount = 1; + image->bootcat->node = (IsoBoot*)node; + } + } + + iso_node_builder_unref(image->builder); + + /* free old root */ + iso_node_unref((IsoNode*)oldroot); + + /* free old boot catalog */ + el_torito_boot_catalog_free(oldbootcat); + + /* set volume attributes */ + iso_image_set_volset_id(image, data->volset_id); + iso_image_set_volume_id(image, data->volume_id); + iso_image_set_publisher_id(image, data->publisher_id); + iso_image_set_data_preparer_id(image, data->data_preparer_id); + iso_image_set_system_id(image, data->system_id); + iso_image_set_application_id(image, data->application_id); + iso_image_set_copyright_file_id(image, data->copyright_file_id); + iso_image_set_abstract_file_id(image, data->abstract_file_id); + iso_image_set_biblio_file_id(image, data->biblio_file_id); + + if (features != NULL) { + *features = malloc(sizeof(IsoReadImageFeatures)); + if (*features == NULL) { + ret = ISO_OUT_OF_MEM; + goto import_cleanup; + } + (*features)->hasJoliet = data->joliet; + (*features)->hasRR = data->rr_version != 0; + (*features)->hasIso1999 = data->iso1999; + (*features)->hasElTorito = data->eltorito; + (*features)->size = data->nblocks; + } + + ret = ISO_SUCCESS; + goto import_cleanup; + + import_revert:; + + iso_node_unref((IsoNode*)image->root); + el_torito_boot_catalog_free(image->bootcat); + image->root = oldroot; + image->fs = fsback; + image->bootcat = oldbootcat; + + import_cleanup:; + + /* recover backed fs and builder */ + image->fs = fsback; + image->builder = blback; + + iso_file_source_unref(newroot); + fs->close(fs); + iso_filesystem_unref(fs); + + return ret; +} + +const char *iso_image_fs_get_volset_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->volset_id; +} + +const char *iso_image_fs_get_volume_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->volume_id; +} + +const char *iso_image_fs_get_publisher_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->publisher_id; +} + +const char *iso_image_fs_get_data_preparer_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->data_preparer_id; +} + +const char *iso_image_fs_get_system_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->system_id; +} + +const char *iso_image_fs_get_application_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->application_id; +} + +const char *iso_image_fs_get_copyright_file_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->copyright_file_id; +} + +const char *iso_image_fs_get_abstract_file_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data; + data = (_ImageFsData*) fs->data; + return data->abstract_file_id; +} + +const char *iso_image_fs_get_biblio_file_id(IsoImageFilesystem *fs) +{ + _ImageFsData *data = (_ImageFsData*) fs->data; + return data->biblio_file_id; +} + +int iso_read_opts_new(IsoReadOpts **opts, int profile) +{ + IsoReadOpts *ropts; + + if (opts == NULL) { + return ISO_NULL_POINTER; + } + if (profile != 0) { + return ISO_WRONG_ARG_VALUE; + } + + ropts = calloc(1, sizeof(IsoReadOpts)); + if (ropts == NULL) { + return ISO_OUT_OF_MEM; + } + + ropts->file_mode = 0444; + ropts->dir_mode = 0555; + *opts = ropts; + return ISO_SUCCESS; +} + +void iso_read_opts_free(IsoReadOpts *opts) +{ + if (opts == NULL) { + return; + } + + free(opts->input_charset); + free(opts); +} + +int iso_read_opts_set_start_block(IsoReadOpts *opts, uint32_t block) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->block = block; + return ISO_SUCCESS; +} + +int iso_read_opts_set_no_rockridge(IsoReadOpts *opts, int norr) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->norock = norr ? 1 :0; + return ISO_SUCCESS; +} + +int iso_read_opts_set_no_joliet(IsoReadOpts *opts, int nojoliet) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->nojoliet = nojoliet ? 1 :0; + return ISO_SUCCESS; +} + +int iso_read_opts_set_no_iso1999(IsoReadOpts *opts, int noiso1999) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->noiso1999 = noiso1999 ? 1 :0; + return ISO_SUCCESS; +} + +int iso_read_opts_set_preferjoliet(IsoReadOpts *opts, int preferjoliet) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->preferjoliet = preferjoliet ? 1 :0; + return ISO_SUCCESS; +} + +int iso_read_opts_set_default_uid(IsoReadOpts *opts, uid_t uid) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->uid = uid; + return ISO_SUCCESS; +} + +int iso_read_opts_set_default_gid(IsoReadOpts *opts, gid_t gid) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->gid = gid; + return ISO_SUCCESS; +} + +int iso_read_opts_set_default_permissions(IsoReadOpts *opts, mode_t file_perm, + mode_t dir_perm) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->file_mode = file_perm; + opts->dir_mode = dir_perm; + return ISO_SUCCESS; +} + +int iso_read_opts_set_input_charset(IsoReadOpts *opts, const char *charset) +{ + if (opts == NULL) { + return ISO_NULL_POINTER; + } + opts->input_charset = charset ? strdup(charset) : NULL; + return ISO_SUCCESS; +} + +/** + * Destroy an IsoReadImageFeatures object obtained with iso_image_import. + */ +void iso_read_image_features_destroy(IsoReadImageFeatures *f) +{ + if (f) { + free(f); + } +} + +/** + * Get the size (in 2048 byte block) of the image, as reported in the PVM. + */ +uint32_t iso_read_image_features_get_size(IsoReadImageFeatures *f) +{ + return f->size; +} + +/** + * Whether RockRidge extensions are present in the image imported. + */ +int iso_read_image_features_has_rockridge(IsoReadImageFeatures *f) +{ + return f->hasRR; +} + +/** + * Whether Joliet extensions are present in the image imported. + */ +int iso_read_image_features_has_joliet(IsoReadImageFeatures *f) +{ + return f->hasJoliet; +} + +/** + * Whether the image is recorded according to ISO 9660:1999, i.e. it has + * a version 2 Enhanced Volume Descriptor. + */ +int iso_read_image_features_has_iso1999(IsoReadImageFeatures *f) +{ + return f->hasIso1999; +} + +/** + * Whether El-Torito boot record is present present in the image imported. + */ +int iso_read_image_features_has_eltorito(IsoReadImageFeatures *f) +{ + return f->hasElTorito; +} diff --git a/libisofs/fs_local.c b/libisofs/fs_local.c new file mode 100644 index 0000000..e9a28b7 --- /dev/null +++ b/libisofs/fs_local.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * Filesystem/FileSource implementation to access the local filesystem. + */ + +#include "fsource.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static +int iso_file_source_new_lfs(IsoFileSource *parent, const char *name, + IsoFileSource **src); + +/* + * We can share a local filesystem object, as it has no private atts. + */ +IsoFilesystem *lfs= NULL; + +typedef struct +{ + /** reference to the parent (if root it points to itself) */ + IsoFileSource *parent; + char *name; + unsigned int openned :2; /* 0: not openned, 1: file, 2:dir */ + union + { + int fd; + DIR *dir; + } info; +} _LocalFsFileSource; + +static +char* lfs_get_path(IsoFileSource *src) +{ + _LocalFsFileSource *data; + data = src->data; + + if (data->parent == src) { + return strdup("/"); + } else { + char *path = lfs_get_path(data->parent); + int pathlen = strlen(path); + path = realloc(path, pathlen + strlen(data->name) + 2); + if (pathlen != 1) { + /* pathlen can only be 1 for root */ + path[pathlen] = '/'; + path[pathlen + 1] = '\0'; + } + return strcat(path, data->name); + } +} + +static +char* lfs_get_name(IsoFileSource *src) +{ + _LocalFsFileSource *data; + data = src->data; + return strdup(data->name); +} + +static +int lfs_lstat(IsoFileSource *src, struct stat *info) +{ + _LocalFsFileSource *data; + char *path; + + if (src == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + data = src->data; + path = lfs_get_path(src); + + if (lstat(path, info) != 0) { + int err; + + /* error, choose an appropriate return code */ + switch (errno) { + case EACCES: + err = ISO_FILE_ACCESS_DENIED; + break; + case ENOTDIR: + case ENAMETOOLONG: + case ELOOP: + err = ISO_FILE_BAD_PATH; + break; + case ENOENT: + err = ISO_FILE_DOESNT_EXIST; + break; + case EFAULT: + case ENOMEM: + err = ISO_OUT_OF_MEM; + break; + default: + err = ISO_FILE_ERROR; + break; + } + return err; + } + free(path); + return ISO_SUCCESS; +} + +static +int lfs_stat(IsoFileSource *src, struct stat *info) +{ + _LocalFsFileSource *data; + char *path; + + if (src == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + data = src->data; + path = lfs_get_path(src); + + if (stat(path, info) != 0) { + int err; + + /* error, choose an appropriate return code */ + switch (errno) { + case EACCES: + err = ISO_FILE_ACCESS_DENIED; + break; + case ENOTDIR: + case ENAMETOOLONG: + case ELOOP: + err = ISO_FILE_BAD_PATH; + break; + case ENOENT: + err = ISO_FILE_DOESNT_EXIST; + break; + case EFAULT: + case ENOMEM: + err = ISO_OUT_OF_MEM; + break; + default: + err = ISO_FILE_ERROR; + break; + } + return err; + } + free(path); + return ISO_SUCCESS; +} + +static +int lfs_access(IsoFileSource *src) +{ + int ret; + _LocalFsFileSource *data; + char *path; + + if (src == NULL) { + return ISO_NULL_POINTER; + } + data = src->data; + path = lfs_get_path(src); + + ret = iso_eaccess(path); + free(path); + return ret; +} + +static +int lfs_open(IsoFileSource *src) +{ + int err; + struct stat info; + _LocalFsFileSource *data; + char *path; + + if (src == NULL) { + return ISO_NULL_POINTER; + } + + data = src->data; + if (data->openned) { + return ISO_FILE_ALREADY_OPENNED; + } + + /* is a file or a dir ? */ + err = lfs_stat(src, &info); + if (err < 0) { + return err; + } + + path = lfs_get_path(src); + if (S_ISDIR(info.st_mode)) { + data->info.dir = opendir(path); + data->openned = data->info.dir ? 2 : 0; + } else { + data->info.fd = open(path, O_RDONLY); + data->openned = data->info.fd != -1 ? 1 : 0; + } + free(path); + + /* + * check for possible errors, note that many of possible ones are + * parsed in the lstat call above + */ + if (data->openned == 0) { + switch (errno) { + case EACCES: + err = ISO_FILE_ACCESS_DENIED; + break; + case EFAULT: + case ENOMEM: + err = ISO_OUT_OF_MEM; + break; + default: + err = ISO_FILE_ERROR; + break; + } + return err; + } + + return ISO_SUCCESS; +} + +static +int lfs_close(IsoFileSource *src) +{ + int ret; + _LocalFsFileSource *data; + + if (src == NULL) { + return ISO_NULL_POINTER; + } + + data = src->data; + switch (data->openned) { + case 1: /* not dir */ + ret = close(data->info.fd) == 0 ? ISO_SUCCESS : ISO_FILE_ERROR; + break; + case 2: /* directory */ + ret = closedir(data->info.dir) == 0 ? ISO_SUCCESS : ISO_FILE_ERROR; + break; + default: + ret = ISO_FILE_NOT_OPENNED; + break; + } + if (ret == ISO_SUCCESS) { + data->openned = 0; + } + return ret; +} + +static +int lfs_read(IsoFileSource *src, void *buf, size_t count) +{ + _LocalFsFileSource *data; + + if (src == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + if (count == 0) { + return ISO_WRONG_ARG_VALUE; + } + + data = src->data; + switch (data->openned) { + case 1: /* not dir */ + { + int ret; + ret = read(data->info.fd, buf, count); + if (ret < 0) { + /* error on read */ + switch (errno) { + case EINTR: + ret = ISO_INTERRUPTED; + break; + case EFAULT: + ret = ISO_OUT_OF_MEM; + break; + case EIO: + ret = ISO_FILE_READ_ERROR; + break; + default: + ret = ISO_FILE_ERROR; + break; + } + } + return ret; + } + case 2: /* directory */ + return ISO_FILE_IS_DIR; + default: + return ISO_FILE_NOT_OPENNED; + } +} + +static +int lfs_readdir(IsoFileSource *src, IsoFileSource **child) +{ + _LocalFsFileSource *data; + + if (src == NULL || child == NULL) { + return ISO_NULL_POINTER; + } + + data = src->data; + switch (data->openned) { + case 1: /* not dir */ + return ISO_FILE_IS_NOT_DIR; + case 2: /* directory */ + { + struct dirent *entry; + int ret; + + /* while to skip "." and ".." dirs */ + while (1) { + entry = readdir(data->info.dir); + if (entry == NULL) { + if (errno == EBADF) + return ISO_FILE_ERROR; + else + return 0; /* EOF */ + } + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { + break; + } + } + + /* create the new FileSrc */ + ret = iso_file_source_new_lfs(src, entry->d_name, child); + return ret; + } + default: + return ISO_FILE_NOT_OPENNED; + } +} + +static +int lfs_readlink(IsoFileSource *src, char *buf, size_t bufsiz) +{ + int size; + _LocalFsFileSource *data; + char *path; + + if (src == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + + if (bufsiz <= 0) { + return ISO_WRONG_ARG_VALUE; + } + + data = src->data; + path = lfs_get_path(src); + + /* + * invoke readlink, with bufsiz -1 to reserve an space for + * the NULL character + */ + size = readlink(path, buf, bufsiz - 1); + free(path); + if (size < 0) { + /* error */ + switch (errno) { + case EACCES: + return ISO_FILE_ACCESS_DENIED; + case ENOTDIR: + case ENAMETOOLONG: + case ELOOP: + return ISO_FILE_BAD_PATH; + case ENOENT: + return ISO_FILE_DOESNT_EXIST; + case EINVAL: + return ISO_FILE_IS_NOT_SYMLINK; + case EFAULT: + case ENOMEM: + return ISO_OUT_OF_MEM; + default: + return ISO_FILE_ERROR; + } + } + + /* NULL-terminate the buf */ + buf[size] = '\0'; + return ISO_SUCCESS; +} + +static +IsoFilesystem* lfs_get_filesystem(IsoFileSource *src) +{ + return src == NULL ? NULL : lfs; +} + +static +void lfs_free(IsoFileSource *src) +{ + _LocalFsFileSource *data; + + data = src->data; + + /* close the file if it is already openned */ + if (data->openned) { + src->class->close(src); + } + if (data->parent != src) { + iso_file_source_unref(data->parent); + } + free(data->name); + free(data); + iso_filesystem_unref(lfs); +} + +IsoFileSourceIface lfs_class = { + 0, /* version */ + lfs_get_path, + lfs_get_name, + lfs_lstat, + lfs_stat, + lfs_access, + lfs_open, + lfs_close, + lfs_read, + lfs_readdir, + lfs_readlink, + lfs_get_filesystem, + lfs_free +}; + +/** + * + * @return + * 1 success, < 0 error + */ +static +int iso_file_source_new_lfs(IsoFileSource *parent, const char *name, + IsoFileSource **src) +{ + IsoFileSource *lfs_src; + _LocalFsFileSource *data; + + if (src == NULL) { + return ISO_NULL_POINTER; + } + + if (lfs == NULL) { + /* this should never happen */ + return ISO_ASSERT_FAILURE; + } + + /* allocate memory */ + data = malloc(sizeof(_LocalFsFileSource)); + if (data == NULL) { + return ISO_OUT_OF_MEM; + } + lfs_src = malloc(sizeof(IsoFileSource)); + if (lfs_src == NULL) { + free(data); + return ISO_OUT_OF_MEM; + } + + /* fill struct */ + data->name = name ? strdup(name) : NULL; + data->openned = 0; + if (parent) { + data->parent = parent; + iso_file_source_ref(parent); + } else { + data->parent = lfs_src; + } + + lfs_src->refcount = 1; + lfs_src->data = data; + lfs_src->class = &lfs_class; + + /* take a ref to local filesystem */ + iso_filesystem_ref(lfs); + + /* return */ + *src = lfs_src; + return ISO_SUCCESS; +} + +static +int lfs_get_root(IsoFilesystem *fs, IsoFileSource **root) +{ + if (fs == NULL || root == NULL) { + return ISO_NULL_POINTER; + } + return iso_file_source_new_lfs(NULL, NULL, root); +} + +static +int lfs_get_by_path(IsoFilesystem *fs, const char *path, IsoFileSource **file) +{ + int ret; + IsoFileSource *src; + struct stat info; + char *ptr, *brk_info, *component; + + if (fs == NULL || path == NULL || file == NULL) { + return ISO_NULL_POINTER; + } + + /* + * first of all check that it is a valid path. + */ + if (lstat(path, &info) != 0) { + int err; + + /* error, choose an appropriate return code */ + switch (errno) { + case EACCES: + err = ISO_FILE_ACCESS_DENIED; + break; + case ENOTDIR: + case ENAMETOOLONG: + case ELOOP: + err = ISO_FILE_BAD_PATH; + break; + case ENOENT: + err = ISO_FILE_DOESNT_EXIST; + break; + case EFAULT: + case ENOMEM: + err = ISO_OUT_OF_MEM; + break; + default: + err = ISO_FILE_ERROR; + break; + } + return err; + } + + /* ok, path is valid. create the file source */ + ret = lfs_get_root(fs, &src); + if (ret < 0) { + return ret; + } + if (!strcmp(path, "/")) { + /* we are looking for root */ + *file = src; + return ISO_SUCCESS; + } + + ptr = strdup(path); + if (ptr == NULL) { + iso_file_source_unref(src); + return ISO_OUT_OF_MEM; + } + + component = strtok_r(ptr, "/", &brk_info); + while (component) { + IsoFileSource *child = NULL; + if (!strcmp(component, ".")) { + child = src; + } else if (!strcmp(component, "..")) { + child = ((_LocalFsFileSource*)src->data)->parent; + iso_file_source_ref(child); + iso_file_source_unref(src); + } else { + ret = iso_file_source_new_lfs(src, component, &child); + iso_file_source_unref(src); + if (ret < 0) { + break; + } + } + + src = child; + component = strtok_r(NULL, "/", &brk_info); + } + + free(ptr); + if (ret > 0) { + *file = src; + } + return ret; +} + +static +unsigned int lfs_get_id(IsoFilesystem *fs) +{ + return ISO_LOCAL_FS_ID; +} + +static +int lfs_fs_open(IsoFilesystem *fs) +{ + /* open() operation is not needed */ + return ISO_SUCCESS; +} + +static +int lfs_fs_close(IsoFilesystem *fs) +{ + /* close() operation is not needed */ + return ISO_SUCCESS; +} + +static +void lfs_fs_free(IsoFilesystem *fs) +{ + lfs = NULL; +} + +int iso_local_filesystem_new(IsoFilesystem **fs) +{ + if (fs == NULL) { + return ISO_NULL_POINTER; + } + + if (lfs != NULL) { + /* just take a new ref */ + iso_filesystem_ref(lfs); + } else { + + lfs = malloc(sizeof(IsoFilesystem)); + if (lfs == NULL) { + return ISO_OUT_OF_MEM; + } + + /* fill struct */ + strncpy(lfs->type, "file", 4); + lfs->refcount = 1; + lfs->version = 0; + lfs->data = NULL; /* we don't need private data */ + lfs->get_root = lfs_get_root; + lfs->get_by_path = lfs_get_by_path; + lfs->get_id = lfs_get_id; + lfs->open = lfs_fs_open; + lfs->close = lfs_fs_close; + lfs->free = lfs_fs_free; + } + *fs = lfs; + return ISO_SUCCESS; +} diff --git a/libisofs/fsource.c b/libisofs/fsource.c new file mode 100644 index 0000000..b6e5c5f --- /dev/null +++ b/libisofs/fsource.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "fsource.h" +#include + +/** + * Values belong 1000 are reserved for libisofs usage + */ +unsigned int iso_fs_global_id = 1000; + +void iso_file_source_ref(IsoFileSource *src) +{ + ++src->refcount; +} + +void iso_file_source_unref(IsoFileSource *src) +{ + if (--src->refcount == 0) { + src->class->free(src); + free(src); + } +} + +void iso_filesystem_ref(IsoFilesystem *fs) +{ + ++fs->refcount; +} + +void iso_filesystem_unref(IsoFilesystem *fs) +{ + if (--fs->refcount == 0) { + fs->free(fs); + free(fs); + } +} + +/* + * this are just helpers to invoque methods in class + */ + +inline +char* iso_file_source_get_path(IsoFileSource *src) +{ + return src->class->get_path(src); +} + +inline +char* iso_file_source_get_name(IsoFileSource *src) +{ + return src->class->get_name(src); +} + +inline +int iso_file_source_lstat(IsoFileSource *src, struct stat *info) +{ + return src->class->lstat(src, info); +} + +inline +int iso_file_source_access(IsoFileSource *src) +{ + return src->class->access(src); +} + +inline +int iso_file_source_stat(IsoFileSource *src, struct stat *info) +{ + return src->class->stat(src, info); +} + +inline +int iso_file_source_open(IsoFileSource *src) +{ + return src->class->open(src); +} + +inline +int iso_file_source_close(IsoFileSource *src) +{ + return src->class->close(src); +} + +inline +int iso_file_source_read(IsoFileSource *src, void *buf, size_t count) +{ + return src->class->read(src, buf, count); +} + +inline +int iso_file_source_readdir(IsoFileSource *src, IsoFileSource **child) +{ + return src->class->readdir(src, child); +} + +inline +int iso_file_source_readlink(IsoFileSource *src, char *buf, size_t bufsiz) +{ + return src->class->readlink(src, buf, bufsiz); +} + +inline +IsoFilesystem* iso_file_source_get_filesystem(IsoFileSource *src) +{ + return src->class->get_filesystem(src); +} diff --git a/libisofs/fsource.h b/libisofs/fsource.h new file mode 100644 index 0000000..44dda9a --- /dev/null +++ b/libisofs/fsource.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_FSOURCE_H_ +#define LIBISO_FSOURCE_H_ + +/* + * Definitions for the file sources. Most functions/structures related with + * this were moved to libisofs.h. + */ + +#include "libisofs.h" + +#define ISO_LOCAL_FS_ID 1 +#define ISO_IMAGE_FS_ID 2 +#define ISO_ELTORITO_FS_ID 3 +#define ISO_MEM_FS_ID 4 + +/** + * Create a new IsoFilesystem to deal with local filesystem. + * + * @return + * 1 sucess, < 0 error + */ +int iso_local_filesystem_new(IsoFilesystem **fs); + +#endif /*LIBISO_FSOURCE_H_*/ diff --git a/libisofs/image.c b/libisofs/image.c new file mode 100644 index 0000000..4539ac9 --- /dev/null +++ b/libisofs/image.c @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "image.h" +#include "node.h" +#include "messages.h" +#include "eltorito.h" + +#include +#include + +/** + * Create a new image, empty. + * + * The image will be owned by you and should be unref() when no more needed. + * + * @param name + * Name of the image. This will be used as volset_id and volume_id. + * @param image + * Location where the image pointer will be stored. + * @return + * 1 sucess, < 0 error + */ +int iso_image_new(const char *name, IsoImage **image) +{ + int res; + IsoImage *img; + + if (image == NULL) { + return ISO_NULL_POINTER; + } + + img = calloc(1, sizeof(IsoImage)); + if (img == NULL) { + return ISO_OUT_OF_MEM; + } + + /* local filesystem will be used by default */ + res = iso_local_filesystem_new(&(img->fs)); + if (res < 0) { + free(img); + return ISO_OUT_OF_MEM; + } + + /* use basic builder as default */ + res = iso_node_basic_builder_new(&(img->builder)); + if (res < 0) { + iso_filesystem_unref(img->fs); + free(img); + return ISO_OUT_OF_MEM; + } + + /* fill image fields */ + res = iso_node_new_root(&img->root); + if (res < 0) { + iso_node_builder_unref(img->builder); + iso_filesystem_unref(img->fs); + free(img); + return res; + } + img->refcount = 1; + img->id = iso_message_id++; + + if (name != NULL) { + img->volset_id = strdup(name); + img->volume_id = strdup(name); + } + *image = img; + return ISO_SUCCESS; +} + +/** + * Increments the reference counting of the given image. + */ +void iso_image_ref(IsoImage *image) +{ + ++image->refcount; +} + +/** + * Decrements the reference couting of the given image. + * If it reaches 0, the image is free, together with its tree nodes (whether + * their refcount reach 0 too, of course). + */ +void iso_image_unref(IsoImage *image) +{ + if (--image->refcount == 0) { + int nexcl; + + /* we need to free the image */ + if (image->user_data_free != NULL) { + /* free attached data */ + image->user_data_free(image->user_data); + } + + for (nexcl = 0; nexcl < image->nexcludes; ++nexcl) { + free(image->excludes[nexcl]); + } + free(image->excludes); + + iso_node_unref((IsoNode*)image->root); + iso_node_builder_unref(image->builder); + iso_filesystem_unref(image->fs); + el_torito_boot_catalog_free(image->bootcat); + free(image->volset_id); + free(image->volume_id); + free(image->publisher_id); + free(image->data_preparer_id); + free(image->system_id); + free(image->application_id); + free(image->copyright_file_id); + free(image->abstract_file_id); + free(image->biblio_file_id); + free(image); + } +} + +/** + * Attach user defined data to the image. Use this if your application needs + * to store addition info together with the IsoImage. If the image already + * has data attached, the old data will be freed. + * + * @param data + * Pointer to application defined data that will be attached to the + * image. You can pass NULL to remove any already attached data. + * @param give_up + * Function that will be called when the image does not need the data + * any more. It receives the data pointer as an argumente, and eventually + * causes data to be free. It can be NULL if you don't need it. + */ +int iso_image_attach_data(IsoImage *image, void *data, void (*give_up)(void*)) +{ + if (image == NULL || (data != NULL && free == NULL)) { + return ISO_NULL_POINTER; + } + + if (image->user_data != NULL) { + /* free previously attached data */ + if (image->user_data_free) { + image->user_data_free(image->user_data); + } + image->user_data = NULL; + image->user_data_free = NULL; + } + + if (data != NULL) { + image->user_data = data; + image->user_data_free = give_up; + } + return ISO_SUCCESS; +} + +/** + * The the data previously attached with iso_image_attach_data() + */ +void *iso_image_get_attached_data(IsoImage *image) +{ + return image->user_data; +} + +IsoDir *iso_image_get_root(const IsoImage *image) +{ + return image->root; +} + +void iso_image_set_volset_id(IsoImage *image, const char *volset_id) +{ + free(image->volset_id); + image->volset_id = strdup(volset_id); +} + +const char *iso_image_get_volset_id(const IsoImage *image) +{ + return image->volset_id; +} + +void iso_image_set_volume_id(IsoImage *image, const char *volume_id) +{ + free(image->volume_id); + image->volume_id = strdup(volume_id); +} + +const char *iso_image_get_volume_id(const IsoImage *image) +{ + return image->volume_id; +} + +void iso_image_set_publisher_id(IsoImage *image, const char *publisher_id) +{ + free(image->publisher_id); + image->publisher_id = strdup(publisher_id); +} + +const char *iso_image_get_publisher_id(const IsoImage *image) +{ + return image->publisher_id; +} + +void iso_image_set_data_preparer_id(IsoImage *image, + const char *data_preparer_id) +{ + free(image->data_preparer_id); + image->data_preparer_id = strdup(data_preparer_id); +} + +const char *iso_image_get_data_preparer_id(const IsoImage *image) +{ + return image->data_preparer_id; +} + +void iso_image_set_system_id(IsoImage *image, const char *system_id) +{ + free(image->system_id); + image->system_id = strdup(system_id); +} + +const char *iso_image_get_system_id(const IsoImage *image) +{ + return image->system_id; +} + +void iso_image_set_application_id(IsoImage *image, const char *application_id) +{ + free(image->application_id); + image->application_id = strdup(application_id); +} + +const char *iso_image_get_application_id(const IsoImage *image) +{ + return image->application_id; +} + +void iso_image_set_copyright_file_id(IsoImage *image, + const char *copyright_file_id) +{ + free(image->copyright_file_id); + image->copyright_file_id = strdup(copyright_file_id); +} + +const char *iso_image_get_copyright_file_id(const IsoImage *image) +{ + return image->copyright_file_id; +} + +void iso_image_set_abstract_file_id(IsoImage *image, + const char *abstract_file_id) +{ + free(image->abstract_file_id); + image->abstract_file_id = strdup(abstract_file_id); +} + +const char *iso_image_get_abstract_file_id(const IsoImage *image) +{ + return image->abstract_file_id; +} + +void iso_image_set_biblio_file_id(IsoImage *image, const char *biblio_file_id) +{ + free(image->biblio_file_id); + image->biblio_file_id = strdup(biblio_file_id); +} + +const char *iso_image_get_biblio_file_id(const IsoImage *image) +{ + return image->biblio_file_id; +} + +int iso_image_get_msg_id(IsoImage *image) +{ + return image->id; +} diff --git a/libisofs/image.h b/libisofs/image.h new file mode 100644 index 0000000..b86aca1 --- /dev/null +++ b/libisofs/image.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_IMAGE_H_ +#define LIBISO_IMAGE_H_ + +#include "libisofs.h" +#include "node.h" +#include "fsource.h" +#include "builder.h" + +/* + * Image is a context for image manipulation. + * Global objects such as the message_queues must belogn to that + * context. Thus we will have, for example, a msg queue per image, + * so images are completelly independent and can be managed together. + * (Usefull, for example, in Multiple-Document-Interface GUI apps. + * [The stuff we have in init belongs really to image!] + */ + +struct Iso_Image +{ + + int refcount; + + IsoDir *root; + + char *volset_id; + + char *volume_id; /**< Volume identifier. */ + char *publisher_id; /**< Volume publisher. */ + char *data_preparer_id; /**< Volume data preparer. */ + char *system_id; /**< Volume system identifier. */ + char *application_id; /**< Volume application id */ + char *copyright_file_id; + char *abstract_file_id; + char *biblio_file_id; + + /* el-torito boot catalog */ + struct el_torito_boot_catalog *bootcat; + + /* image identifier, for message origin identifier */ + int id; + + /** + * Default filesystem to use when adding files to the image tree. + */ + IsoFilesystem *fs; + + /* + * Default builder to use when adding files to the image tree. + */ + IsoNodeBuilder *builder; + + /** + * Whether to follow symlinks or just add them as symlinks + */ + unsigned int follow_symlinks : 1; + + /** + * Whether to skip hidden files + */ + unsigned int ignore_hidden : 1; + + /** + * Flags that determine what special files should be ignore. It is a + * bitmask: + * bit0: ignore FIFOs + * bit1: ignore Sockets + * bit2: ignore char devices + * bit3: ignore block devices + */ + int ignore_special; + + /** + * Files to exclude. Wildcard support is included. + */ + char** excludes; + int nexcludes; + + /** + * if the dir already contains a node with the same name, whether to + * replace or not the old node with the new. + */ + enum iso_replace_mode replace; + + /* TODO + enum iso_replace_mode (*confirm_replace)(IsoFileSource *src, IsoNode *node); + */ + + /** + * When this is not NULL, it is a pointer to a function that will + * be called just before a file will be added. You can control where + * the file will be in fact added or ignored. + * + * @return + * 1 add, 0 ignore, < 0 cancel + */ + int (*report)(IsoImage *image, IsoFileSource *src); + + /** + * User supplied data + */ + void *user_data; + void (*user_data_free)(void *ptr); +}; + +#endif /*LIBISO_IMAGE_H_*/ diff --git a/libisofs/iso1999.c b/libisofs/iso1999.c new file mode 100644 index 0000000..827d5ad --- /dev/null +++ b/libisofs/iso1999.c @@ -0,0 +1,1016 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "iso1999.h" +#include "messages.h" +#include "writer.h" +#include "image.h" +#include "filesrc.h" +#include "eltorito.h" + +#include +#include +#include + +static +int get_iso1999_name(Ecma119Image *t, const char *str, char **fname) +{ + int ret; + char *name; + + if (fname == NULL) { + return ISO_ASSERT_FAILURE; + } + + if (str == NULL) { + /* not an error, can be root node */ + *fname = NULL; + return ISO_SUCCESS; + } + + if (!strcmp(t->input_charset, t->output_charset)) { + /* no conversion needed */ + name = strdup(str); + } else { + ret = strconv(str, t->input_charset, t->output_charset, &name); + if (ret < 0) { + ret = iso_msg_submit(t->image->id, ISO_FILENAME_WRONG_CHARSET, ret, + "Charset conversion error. Can't convert %s from %s to %s", + str, t->input_charset, t->output_charset); + if (ret < 0) { + return ret; /* aborted */ + } + + /* use the original name, it's the best we can do */ + name = strdup(str); + } + } + + /* ISO 9660:1999 7.5.1 */ + if (strlen(name) > 207) { + name[207] = '\0'; + } + + *fname = name; + + return ISO_SUCCESS; +} + +static +void iso1999_node_free(Iso1999Node *node) +{ + if (node == NULL) { + return; + } + if (node->type == ISO1999_DIR) { + int i; + for (i = 0; i < node->info.dir->nchildren; i++) { + iso1999_node_free(node->info.dir->children[i]); + } + free(node->info.dir->children); + free(node->info.dir); + } + iso_node_unref(node->node); + free(node->name); + free(node); +} + +/** + * Create a low level ISO 9660:1999 node + * @return + * 1 success, 0 ignored, < 0 error + */ +static +int create_node(Ecma119Image *t, IsoNode *iso, Iso1999Node **node) +{ + int ret; + Iso1999Node *n; + + n = calloc(1, sizeof(Iso1999Node)); + if (n == NULL) { + return ISO_OUT_OF_MEM; + } + + if (iso->type == LIBISO_DIR) { + IsoDir *dir = (IsoDir*) iso; + n->info.dir = calloc(1, sizeof(struct iso1999_dir_info)); + if (n->info.dir == NULL) { + free(n); + return ISO_OUT_OF_MEM; + } + n->info.dir->children = calloc(sizeof(void*), dir->nchildren); + if (n->info.dir->children == NULL) { + free(n->info.dir); + free(n); + return ISO_OUT_OF_MEM; + } + n->type = ISO1999_DIR; + } else if (iso->type == LIBISO_FILE) { + /* it's a file */ + off_t size; + IsoFileSrc *src; + IsoFile *file = (IsoFile*) iso; + + size = iso_stream_get_size(file->stream); + if (size > (off_t)0xffffffff) { + free(n); + return iso_msg_submit(t->image->id, ISO_FILE_TOO_BIG, 0, + "File \"%s\" can't be added to image because is " + "greater than 4GB", iso->name); + return 0; + } + + ret = iso_file_src_create(t, file, &src); + if (ret < 0) { + free(n); + return ret; + } + n->info.file = src; + n->type = ISO1999_FILE; + } else if (iso->type == LIBISO_BOOT) { + /* it's a el-torito boot catalog, that we write as a file */ + IsoFileSrc *src; + + ret = el_torito_catalog_file_src_create(t, &src); + if (ret < 0) { + free(n); + return ret; + } + n->info.file = src; + n->type = ISO1999_FILE; + } else { + /* should never happen */ + free(n); + return ISO_ASSERT_FAILURE; + } + + /* take a ref to the IsoNode */ + n->node = iso; + iso_node_ref(iso); + + *node = n; + return ISO_SUCCESS; +} + +/** + * Create the low level ISO 9660:1999 tree from the high level ISO tree. + * + * @return + * 1 success, 0 file ignored, < 0 error + */ +static +int create_tree(Ecma119Image *t, IsoNode *iso, Iso1999Node **tree, int pathlen) +{ + int ret, max_path; + Iso1999Node *node = NULL; + char *iso_name = NULL; + + if (t == NULL || iso == NULL || tree == NULL) { + return ISO_NULL_POINTER; + } + + if (iso->hidden & LIBISO_HIDE_ON_1999) { + /* file will be ignored */ + return 0; + } + ret = get_iso1999_name(t, iso->name, &iso_name); + if (ret < 0) { + return ret; + } + + max_path = pathlen + 1 + (iso_name ? strlen(iso_name): 0); + if (!t->allow_longer_paths && max_path > 255) { + free(iso_name); + return iso_msg_submit(t->image->id, ISO_FILE_IMGPATH_WRONG, 0, + "File \"%s\" can't be added to ISO 9660:1999 tree, " + "because its path length is larger than 255", iso->name); + } + + switch (iso->type) { + case LIBISO_FILE: + ret = create_node(t, iso, &node); + break; + case LIBISO_DIR: + { + IsoNode *pos; + IsoDir *dir = (IsoDir*)iso; + ret = create_node(t, iso, &node); + if (ret < 0) { + free(iso_name); + return ret; + } + pos = dir->children; + while (pos) { + int cret; + Iso1999Node *child; + cret = create_tree(t, pos, &child, max_path); + if (cret < 0) { + /* error */ + iso1999_node_free(node); + ret = cret; + break; + } else if (cret == ISO_SUCCESS) { + /* add child to this node */ + int nchildren = node->info.dir->nchildren++; + node->info.dir->children[nchildren] = child; + child->parent = node; + } + pos = pos->next; + } + } + break; + case LIBISO_BOOT: + if (t->eltorito) { + ret = create_node(t, iso, &node); + } else { + /* log and ignore */ + ret = iso_msg_submit(t->image->id, ISO_FILE_IGNORED, 0, + "El-Torito catalog found on a image without El-Torito.", + iso->name); + } + break; + case LIBISO_SYMLINK: + case LIBISO_SPECIAL: + ret = iso_msg_submit(t->image->id, ISO_FILE_IGNORED, 0, + "Can't add %s to ISO 9660:1999 tree. This kind of files " + "can only be added to a Rock Ridget tree. Skipping.", + iso->name); + break; + default: + /* should never happen */ + return ISO_ASSERT_FAILURE; + } + if (ret <= 0) { + free(iso_name); + return ret; + } + node->name = iso_name; + *tree = node; + return ISO_SUCCESS; +} + +static int +cmp_node(const void *f1, const void *f2) +{ + Iso1999Node *f = *((Iso1999Node**)f1); + Iso1999Node *g = *((Iso1999Node**)f2); + + /** + * TODO #00027 Follow ISO 9660:1999 specs when sorting files + * strcmp do not does exactly what ISO 9660:1999, 9.3, as characters + * < 0x20 " " are allowed, so name len must be taken into accout + */ + return strcmp(f->name, g->name); +} + +/** + * Sort the entries inside an ISO 9660:1999 directory, according to + * ISO 9660:1999, 9.3 + */ +static +void sort_tree(Iso1999Node *root) +{ + size_t i; + + qsort(root->info.dir->children, root->info.dir->nchildren, + sizeof(void*), cmp_node); + for (i = 0; i < root->info.dir->nchildren; i++) { + Iso1999Node *child = root->info.dir->children[i]; + if (child->type == ISO1999_DIR) + sort_tree(child); + } +} + +static +int mangle_single_dir(Ecma119Image *img, Iso1999Node *dir) +{ + int ret; + int i, nchildren; + Iso1999Node **children; + IsoHTable *table; + int need_sort = 0; + + nchildren = dir->info.dir->nchildren; + children = dir->info.dir->children; + + /* a hash table will temporary hold the names, for fast searching */ + ret = iso_htable_create((nchildren * 100) / 80, iso_str_hash, + (compare_function_t)strcmp, &table); + if (ret < 0) { + return ret; + } + for (i = 0; i < nchildren; ++i) { + char *name = children[i]->name; + ret = iso_htable_add(table, name, name); + if (ret < 0) { + goto mangle_cleanup; + } + } + + for (i = 0; i < nchildren; ++i) { + char *name, *ext; + char full_name[208]; + int max; /* computed max len for name, without extension */ + int j = i; + int digits = 1; /* characters to change per name */ + + /* first, find all child with same name */ + while (j + 1 < nchildren && + !cmp_node(children + i, children + j + 1)) { + ++j; + } + if (j == i) { + /* name is unique */ + continue; + } + + /* + * A max of 7 characters is good enought, it allows handling up to + * 9,999,999 files with same name. + */ + while (digits < 8) { + int ok, k; + char *dot; + int change = 0; /* number to be written */ + + /* copy name to buffer */ + strcpy(full_name, children[i]->name); + + /* compute name and extension */ + dot = strrchr(full_name, '.'); + if (dot != NULL && children[i]->type != ISO1999_DIR) { + + /* + * File (not dir) with extension. + */ + int extlen; + full_name[dot - full_name] = '\0'; + name = full_name; + ext = dot + 1; + + extlen = strlen(ext); + max = 207 - extlen - 1 - digits; + if (max <= 0) { + /* this can happen if extension is too long */ + if (extlen + max > 3) { + /* + * reduce extension len, to give name an extra char + * note that max is negative or 0 + */ + extlen = extlen + max - 1; + ext[extlen] = '\0'; + max = 207 - extlen - 1 - digits; + } else { + /* + * error, we don't support extensions < 3 + * This can't happen with current limit of digits. + */ + ret = ISO_ERROR; + goto mangle_cleanup; + } + } + /* ok, reduce name by digits */ + if (name + max < dot) { + name[max] = '\0'; + } + } else { + /* Directory, or file without extension */ + if (children[i]->type == ISO1999_DIR) { + dot = NULL; /* dots have no meaning in dirs */ + } + max = 207 - digits; + name = full_name; + if (max < strlen(name)) { + name[max] = '\0'; + } + /* let ext be an empty string */ + ext = name + strlen(name); + } + + ok = 1; + /* change name of each file */ + for (k = i; k <= j; ++k) { + char tmp[208]; + char fmt[16]; + if (dot != NULL) { + sprintf(fmt, "%%s%%0%dd.%%s", digits); + } else { + sprintf(fmt, "%%s%%0%dd%%s", digits); + } + while (1) { + sprintf(tmp, fmt, name, change, ext); + ++change; + if (change > int_pow(10, digits)) { + ok = 0; + break; + } + if (!iso_htable_get(table, tmp, NULL)) { + /* the name is unique, so it can be used */ + break; + } + } + if (ok) { + char *new = strdup(tmp); + if (new == NULL) { + ret = ISO_OUT_OF_MEM; + goto mangle_cleanup; + } + iso_msg_debug(img->image->id, "\"%s\" renamed to \"%s\"", + children[k]->name, new); + + iso_htable_remove_ptr(table, children[k]->name, NULL); + free(children[k]->name); + children[k]->name = new; + iso_htable_add(table, new, new); + + /* + * if we change a name we need to sort again children + * at the end + */ + need_sort = 1; + } else { + /* we need to increment digits */ + break; + } + } + if (ok) { + break; + } else { + ++digits; + } + } + if (digits == 8) { + ret = ISO_MANGLE_TOO_MUCH_FILES; + goto mangle_cleanup; + } + i = j; + } + + /* + * If needed, sort again the files inside dir + */ + if (need_sort) { + qsort(children, nchildren, sizeof(void*), cmp_node); + } + + ret = ISO_SUCCESS; + +mangle_cleanup : ; + iso_htable_destroy(table, NULL); + return ret; +} + +static +int mangle_tree(Ecma119Image *t, Iso1999Node *dir) +{ + int ret; + size_t i; + + ret = mangle_single_dir(t, dir); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < dir->info.dir->nchildren; ++i) { + if (dir->info.dir->children[i]->type == ISO1999_DIR) { + ret = mangle_tree(t, dir->info.dir->children[i]); + if (ret < 0) { + /* error */ + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int iso1999_tree_create(Ecma119Image *t) +{ + int ret; + Iso1999Node *root; + + if (t == NULL) { + return ISO_NULL_POINTER; + } + + ret = create_tree(t, (IsoNode*)t->image->root, &root, 0); + if (ret <= 0) { + if (ret == 0) { + /* unexpected error, root ignored!! This can't happen */ + ret = ISO_ASSERT_FAILURE; + } + return ret; + } + + /* the ISO 9660:1999 tree is stored in Ecma119Image target */ + t->iso1999_root = root; + + iso_msg_debug(t->image->id, "Sorting the ISO 9660:1999 tree..."); + sort_tree(root); + + iso_msg_debug(t->image->id, "Mangling ISO 9660:1999 names..."); + ret = mangle_tree(t, t->iso1999_root); + if (ret < 0) { + return ret; + } + + return ISO_SUCCESS; +} + +/** + * Compute the size of a directory entry for a single node + */ +static +size_t calc_dirent_len(Ecma119Image *t, Iso1999Node *n) +{ + int ret = n->name ? strlen(n->name) + 33 : 34; + if (ret % 2) + ret++; + return ret; +} + +/** + * Computes the total size of all directory entries of a single dir, as + * stated in ISO 9660:1999, 6.8.1.3 + */ +static +size_t calc_dir_size(Ecma119Image *t, Iso1999Node *dir) +{ + size_t i, len; + + /* size of "." and ".." entries */ + len = 34 + 34; + + for (i = 0; i < dir->info.dir->nchildren; ++i) { + size_t remaining; + Iso1999Node *child = dir->info.dir->children[i]; + size_t dirent_len = calc_dirent_len(t, child); + remaining = BLOCK_SIZE - (len % BLOCK_SIZE); + if (dirent_len > remaining) { + /* child directory entry doesn't fit on block */ + len += remaining + dirent_len; + } else { + len += dirent_len; + } + } + + /* + * The size of a dir is always a multiple of block size, as we must add + * the size of the unused space after the last directory record + * (ISO 9660:1999, 6.8.1.3) + */ + len = ROUND_UP(len, BLOCK_SIZE); + + /* cache the len */ + dir->info.dir->len = len; + return len; +} + +static +void calc_dir_pos(Ecma119Image *t, Iso1999Node *dir) +{ + size_t i, len; + + t->iso1999_ndirs++; + dir->info.dir->block = t->curblock; + len = calc_dir_size(t, dir); + t->curblock += DIV_UP(len, BLOCK_SIZE); + for (i = 0; i < dir->info.dir->nchildren; i++) { + Iso1999Node *child = dir->info.dir->children[i]; + if (child->type == ISO1999_DIR) { + calc_dir_pos(t, child); + } + } +} + +/** + * Compute the length of the path table (ISO 9660:1999, 6.9), in bytes. + */ +static +uint32_t calc_path_table_size(Iso1999Node *dir) +{ + uint32_t size; + size_t i; + + /* size of path table for this entry */ + size = 8; + size += dir->name ? strlen(dir->name) : 2; + size += (size % 2); + + /* and recurse */ + for (i = 0; i < dir->info.dir->nchildren; i++) { + Iso1999Node *child = dir->info.dir->children[i]; + if (child->type == ISO1999_DIR) { + size += calc_path_table_size(child); + } + } + return size; +} + +static +int iso1999_writer_compute_data_blocks(IsoImageWriter *writer) +{ + Ecma119Image *t; + uint32_t path_table_size; + + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + t = writer->target; + + /* compute position of directories */ + iso_msg_debug(t->image->id, + "Computing position of ISO 9660:1999 dir structure"); + t->iso1999_ndirs = 0; + calc_dir_pos(t, t->iso1999_root); + + /* compute length of pathlist */ + iso_msg_debug(t->image->id, "Computing length of ISO 9660:1999 pathlist"); + path_table_size = calc_path_table_size(t->iso1999_root); + + /* compute location for path tables */ + t->iso1999_l_path_table_pos = t->curblock; + t->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + t->iso1999_m_path_table_pos = t->curblock; + t->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + t->iso1999_path_table_size = path_table_size; + + return ISO_SUCCESS; +} + +/** + * Write a single directory record (ISO 9660:1999, 9.1). + * + * @param file_id + * if >= 0, we use it instead of the filename (for "." and ".." entries). + * @param len_fi + * Computed length of the file identifier. + */ +static +void write_one_dir_record(Ecma119Image *t, Iso1999Node *node, int file_id, + uint8_t *buf, size_t len_fi) +{ + uint32_t len; + uint32_t block; + uint8_t len_dr; /*< size of dir entry */ + uint8_t *name = (file_id >= 0) ? (uint8_t*)&file_id + : (uint8_t*)node->name; + + struct ecma119_dir_record *rec = (struct ecma119_dir_record*)buf; + + len_dr = 33 + len_fi + (len_fi % 2 ? 0 : 1); + + memcpy(rec->file_id, name, len_fi); + + if (node->type == ISO1999_DIR) { + /* use the cached length */ + len = node->info.dir->len; + block = node->info.dir->block; + } else if (node->type == ISO1999_FILE) { + len = iso_file_src_get_size(node->info.file); + block = node->info.file->block; + } else { + /* + * for nodes other than files and dirs, we set both + * len and block to 0 + */ + len = 0; + block = 0; + } + + /* + * For ".." entry we need to write the parent info! + */ + if (file_id == 1 && node->parent) + node = node->parent; + + rec->len_dr[0] = len_dr; + iso_bb(rec->block, block, 4); + iso_bb(rec->length, len, 4); + iso_datetime_7(rec->recording_time, t->now, t->always_gmt); + rec->flags[0] = (node->type == ISO1999_DIR) ? 2 : 0; + iso_bb(rec->vol_seq_number, 1, 2); + rec->len_fi[0] = len_fi; +} + +/** + * Write the enhanced volume descriptor (ISO/IEC 9660:1999, 8.5) + */ +static +int iso1999_writer_write_vol_desc(IsoImageWriter *writer) +{ + IsoImage *image; + Ecma119Image *t; + + /* The enhanced volume descriptor is like the sup vol desc */ + struct ecma119_sup_vol_desc vol; + + char *vol_id = NULL, *pub_id = NULL, *data_id = NULL; + char *volset_id = NULL, *system_id = NULL, *application_id = NULL; + char *copyright_file_id = NULL, *abstract_file_id = NULL; + char *biblio_file_id = NULL; + + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + t = writer->target; + image = t->image; + + iso_msg_debug(image->id, "Write Enhanced Vol Desc (ISO 9660:1999)"); + + memset(&vol, 0, sizeof(struct ecma119_sup_vol_desc)); + + get_iso1999_name(t, image->volume_id, &vol_id); + str2a_char(t->input_charset, image->publisher_id, &pub_id); + str2a_char(t->input_charset, image->data_preparer_id, &data_id); + get_iso1999_name(t, image->volset_id, &volset_id); + + str2a_char(t->input_charset, image->system_id, &system_id); + str2a_char(t->input_charset, image->application_id, &application_id); + get_iso1999_name(t, image->copyright_file_id, ©right_file_id); + get_iso1999_name(t, image->abstract_file_id, &abstract_file_id); + get_iso1999_name(t, image->biblio_file_id, &biblio_file_id); + + vol.vol_desc_type[0] = 2; + memcpy(vol.std_identifier, "CD001", 5); + + /* descriptor version is 2 (ISO/IEC 9660:1999, 8.5.2) */ + vol.vol_desc_version[0] = 2; + strncpy_pad((char*)vol.volume_id, vol_id, 32); + + iso_bb(vol.vol_space_size, t->vol_space_size, 4); + iso_bb(vol.vol_set_size, 1, 2); + iso_bb(vol.vol_seq_number, 1, 2); + iso_bb(vol.block_size, BLOCK_SIZE, 2); + iso_bb(vol.path_table_size, t->iso1999_path_table_size, 4); + iso_lsb(vol.l_path_table_pos, t->iso1999_l_path_table_pos, 4); + iso_msb(vol.m_path_table_pos, t->iso1999_m_path_table_pos, 4); + + write_one_dir_record(t, t->iso1999_root, 0, vol.root_dir_record, 1); + + strncpy_pad((char*)vol.vol_set_id, volset_id, 128); + strncpy_pad((char*)vol.publisher_id, pub_id, 128); + strncpy_pad((char*)vol.data_prep_id, data_id, 128); + + strncpy_pad((char*)vol.system_id, system_id, 32); + + strncpy_pad((char*)vol.application_id, application_id, 128); + strncpy_pad((char*)vol.copyright_file_id, copyright_file_id, 37); + strncpy_pad((char*)vol.abstract_file_id, abstract_file_id, 37); + strncpy_pad((char*)vol.bibliographic_file_id, biblio_file_id, 37); + + iso_datetime_17(vol.vol_creation_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_modification_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_effective_time, t->now, t->always_gmt); + vol.file_structure_version[0] = 1; + + free(vol_id); + free(volset_id); + free(pub_id); + free(data_id); + free(system_id); + free(application_id); + free(copyright_file_id); + free(abstract_file_id); + free(biblio_file_id); + + /* Finally write the Volume Descriptor */ + return iso_write(t, &vol, sizeof(struct ecma119_sup_vol_desc)); +} + +static +int write_one_dir(Ecma119Image *t, Iso1999Node *dir) +{ + int ret; + uint8_t buffer[BLOCK_SIZE]; + size_t i; + size_t fi_len, len; + + /* buf will point to current write position on buffer */ + uint8_t *buf = buffer; + + /* initialize buffer with 0s */ + memset(buffer, 0, BLOCK_SIZE); + + /* write the "." and ".." entries first */ + write_one_dir_record(t, dir, 0, buf, 1); + buf += 34; + write_one_dir_record(t, dir, 1, buf, 1); + buf += 34; + + for (i = 0; i < dir->info.dir->nchildren; i++) { + Iso1999Node *child = dir->info.dir->children[i]; + + /* compute len of directory entry */ + fi_len = strlen(child->name); + len = fi_len + 33 + (fi_len % 2 ? 0 : 1); + + if ( (buf + len - buffer) > BLOCK_SIZE) { + /* dir doesn't fit in current block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + if (ret < 0) { + return ret; + } + memset(buffer, 0, BLOCK_SIZE); + buf = buffer; + } + /* write the directory entry in any case */ + write_one_dir_record(t, child, -1, buf, fi_len); + buf += len; + } + + /* write the last block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + return ret; +} + +static +int write_dirs(Ecma119Image *t, Iso1999Node *root) +{ + int ret; + size_t i; + + /* write all directory entries for this dir */ + ret = write_one_dir(t, root); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < root->info.dir->nchildren; i++) { + Iso1999Node *child = root->info.dir->children[i]; + if (child->type == ISO1999_DIR) { + ret = write_dirs(t, child); + if (ret < 0) { + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int write_path_table(Ecma119Image *t, Iso1999Node **pathlist, int l_type) +{ + size_t i, len; + uint8_t buf[256]; /* 256 is just a convenient size larger enought */ + struct ecma119_path_table_record *rec; + void (*write_int)(uint8_t*, uint32_t, int); + Iso1999Node *dir; + uint32_t path_table_size; + int parent = 0; + int ret= ISO_SUCCESS; + + path_table_size = 0; + write_int = l_type ? iso_lsb : iso_msb; + + for (i = 0; i < t->iso1999_ndirs; i++) { + dir = pathlist[i]; + + /* find the index of the parent in the table */ + while ((i) && pathlist[parent] != dir->parent) { + parent++; + } + + /* write the Path Table Record (ECMA-119, 9.4) */ + memset(buf, 0, 256); + rec = (struct ecma119_path_table_record*) buf; + rec->len_di[0] = dir->parent ? (uint8_t) strlen(dir->name) : 1; + rec->len_xa[0] = 0; + write_int(rec->block, dir->info.dir->block, 4); + write_int(rec->parent, parent + 1, 2); + if (dir->parent) { + memcpy(rec->dir_id, dir->name, rec->len_di[0]); + } + len = 8 + rec->len_di[0] + (rec->len_di[0] % 2); + ret = iso_write(t, buf, len); + if (ret < 0) { + /* error */ + return ret; + } + path_table_size += len; + } + + /* we need to fill the last block with zeros */ + path_table_size %= BLOCK_SIZE; + if (path_table_size) { + uint8_t zeros[BLOCK_SIZE]; + len = BLOCK_SIZE - path_table_size; + memset(zeros, 0, len); + ret = iso_write(t, zeros, len); + } + return ret; +} + +static +int write_path_tables(Ecma119Image *t) +{ + int ret; + size_t i, j, cur; + Iso1999Node **pathlist; + + iso_msg_debug(t->image->id, "Writing ISO 9660:1999 Path tables"); + + /* allocate temporal pathlist */ + pathlist = malloc(sizeof(void*) * t->iso1999_ndirs); + if (pathlist == NULL) { + return ISO_OUT_OF_MEM; + } + pathlist[0] = t->iso1999_root; + cur = 1; + + for (i = 0; i < t->iso1999_ndirs; i++) { + Iso1999Node *dir = pathlist[i]; + for (j = 0; j < dir->info.dir->nchildren; j++) { + Iso1999Node *child = dir->info.dir->children[j]; + if (child->type == ISO1999_DIR) { + pathlist[cur++] = child; + } + } + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 1); + if (ret < 0) { + goto write_path_tables_exit; + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 0); + + write_path_tables_exit: ; + free(pathlist); + return ret; +} + +static +int iso1999_writer_write_data(IsoImageWriter *writer) +{ + int ret; + Ecma119Image *t; + + if (writer == NULL) { + return ISO_NULL_POINTER; + } + t = writer->target; + + /* first of all, we write the directory structure */ + ret = write_dirs(t, t->iso1999_root); + if (ret < 0) { + return ret; + } + + /* and write the path tables */ + ret = write_path_tables(t); + + return ret; +} + +static +int iso1999_writer_free_data(IsoImageWriter *writer) +{ + /* free the ISO 9660:1999 tree */ + Ecma119Image *t = writer->target; + iso1999_node_free(t->iso1999_root); + return ISO_SUCCESS; +} + +int iso1999_writer_create(Ecma119Image *target) +{ + int ret; + IsoImageWriter *writer; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + writer->compute_data_blocks = iso1999_writer_compute_data_blocks; + writer->write_vol_desc = iso1999_writer_write_vol_desc; + writer->write_data = iso1999_writer_write_data; + writer->free_data = iso1999_writer_free_data; + writer->data = NULL; + writer->target = target; + + iso_msg_debug(target->image->id, + "Creating low level ISO 9660:1999 tree..."); + ret = iso1999_tree_create(target); + if (ret < 0) { + return ret; + } + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + + /* we need the volume descriptor */ + target->curblock++; + return ISO_SUCCESS; +} diff --git a/libisofs/iso1999.h b/libisofs/iso1999.h new file mode 100644 index 0000000..5c986e7 --- /dev/null +++ b/libisofs/iso1999.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/** + * Structures related to ISO/IEC 9660:1999, that is version 2 of ISO-9660 + * "See doc/devel/cookbook/ISO 9660-1999" and + * ISO/IEC DIS 9660:1999(E) "Information processing. Volume and file structure + * of CD­-ROM for Information Interchange" + * for further details. + */ + +#ifndef LIBISO_ISO1999_H +#define LIBISO_ISO1999_H + +#include "libisofs.h" +#include "ecma119.h" + +enum iso1999_node_type { + ISO1999_FILE, + ISO1999_DIR +}; + +struct iso1999_dir_info { + Iso1999Node **children; + size_t nchildren; + size_t len; + size_t block; +}; + +struct iso1999_node +{ + char *name; /**< Name chosen output charset. */ + + Iso1999Node *parent; + + IsoNode *node; /*< reference to the iso node */ + + enum iso1999_node_type type; + union { + IsoFileSrc *file; + struct iso1999_dir_info *dir; + } info; +}; + +/** + * Create a IsoWriter to deal with ISO 9660:1999 estructures, and add it to + * the given target. + * + * @return + * 1 on success, < 0 on error + */ +int iso1999_writer_create(Ecma119Image *target); + +#endif /* LIBISO_ISO1999_H */ diff --git a/libisofs/joliet.c b/libisofs/joliet.c new file mode 100644 index 0000000..0b86f03 --- /dev/null +++ b/libisofs/joliet.c @@ -0,0 +1,1081 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "joliet.h" +#include "messages.h" +#include "writer.h" +#include "image.h" +#include "filesrc.h" +#include "eltorito.h" + +#include +#include +#include + +static +int get_joliet_name(Ecma119Image *t, IsoNode *iso, uint16_t **name) +{ + int ret; + uint16_t *ucs_name; + uint16_t *jname = NULL; + + if (iso->name == NULL) { + /* it is not necessarily an error, it can be the root */ + return ISO_SUCCESS; + } + + ret = str2ucs(t->input_charset, iso->name, &ucs_name); + if (ret < 0) { + iso_msg_debug(t->image->id, "Can't convert %s", iso->name); + return ret; + } + + /* TODO #00022 : support relaxed constraints in joliet filenames */ + if (iso->type == LIBISO_DIR) { + jname = iso_j_dir_id(ucs_name); + } else { + jname = iso_j_file_id(ucs_name); + } + free(ucs_name); + if (jname != NULL) { + *name = jname; + return ISO_SUCCESS; + } else { + /* + * only possible if mem error, as check for empty names is done + * in public tree + */ + return ISO_OUT_OF_MEM; + } +} + +static +void joliet_node_free(JolietNode *node) +{ + if (node == NULL) { + return; + } + if (node->type == JOLIET_DIR) { + int i; + for (i = 0; i < node->info.dir->nchildren; i++) { + joliet_node_free(node->info.dir->children[i]); + } + free(node->info.dir->children); + free(node->info.dir); + } + iso_node_unref(node->node); + free(node->name); + free(node); +} + +/** + * Create a low level Joliet node + * @return + * 1 success, 0 ignored, < 0 error + */ +static +int create_node(Ecma119Image *t, IsoNode *iso, JolietNode **node) +{ + int ret; + JolietNode *joliet; + + joliet = calloc(1, sizeof(JolietNode)); + if (joliet == NULL) { + return ISO_OUT_OF_MEM; + } + + if (iso->type == LIBISO_DIR) { + IsoDir *dir = (IsoDir*) iso; + joliet->info.dir = calloc(1, sizeof(struct joliet_dir_info)); + if (joliet->info.dir == NULL) { + free(joliet); + return ISO_OUT_OF_MEM; + } + joliet->info.dir->children = calloc(sizeof(void*), dir->nchildren); + if (joliet->info.dir->children == NULL) { + free(joliet->info.dir); + free(joliet); + return ISO_OUT_OF_MEM; + } + joliet->type = JOLIET_DIR; + } else if (iso->type == LIBISO_FILE) { + /* it's a file */ + off_t size; + IsoFileSrc *src; + IsoFile *file = (IsoFile*) iso; + + size = iso_stream_get_size(file->stream); + if (size > (off_t)0xffffffff) { + free(joliet); + return iso_msg_submit(t->image->id, ISO_FILE_TOO_BIG, 0, + "File \"%s\" can't be added to image because is " + "greater than 4GB", iso->name); + } + + ret = iso_file_src_create(t, file, &src); + if (ret < 0) { + free(joliet); + return ret; + } + joliet->info.file = src; + joliet->type = JOLIET_FILE; + } else if (iso->type == LIBISO_BOOT) { + /* it's a el-torito boot catalog, that we write as a file */ + IsoFileSrc *src; + + ret = el_torito_catalog_file_src_create(t, &src); + if (ret < 0) { + free(joliet); + return ret; + } + joliet->info.file = src; + joliet->type = JOLIET_FILE; + } else { + /* should never happen */ + free(joliet); + return ISO_ASSERT_FAILURE; + } + + /* take a ref to the IsoNode */ + joliet->node = iso; + iso_node_ref(iso); + + *node = joliet; + return ISO_SUCCESS; +} + +/** + * Create the low level Joliet tree from the high level ISO tree. + * + * @return + * 1 success, 0 file ignored, < 0 error + */ +static +int create_tree(Ecma119Image *t, IsoNode *iso, JolietNode **tree, int pathlen) +{ + int ret, max_path; + JolietNode *node = NULL; + uint16_t *jname = NULL; + + if (t == NULL || iso == NULL || tree == NULL) { + return ISO_NULL_POINTER; + } + + if (iso->hidden & LIBISO_HIDE_ON_JOLIET) { + /* file will be ignored */ + return 0; + } + ret = get_joliet_name(t, iso, &jname); + if (ret < 0) { + return ret; + } + max_path = pathlen + 1 + (jname ? ucslen(jname) * 2 : 0); + if (!t->joliet_longer_paths && max_path > 240) { + free(jname); + /* + * Wow!! Joliet is even more restrictive than plain ISO-9660, + * that allows up to 255 bytes!! + */ + return iso_msg_submit(t->image->id, ISO_FILE_IMGPATH_WRONG, 0, + "File \"%s\" can't be added to Joliet tree, because " + "its path length is larger than 240", iso->name); + } + + switch (iso->type) { + case LIBISO_FILE: + ret = create_node(t, iso, &node); + break; + case LIBISO_DIR: + { + IsoNode *pos; + IsoDir *dir = (IsoDir*)iso; + ret = create_node(t, iso, &node); + if (ret < 0) { + free(jname); + return ret; + } + pos = dir->children; + while (pos) { + int cret; + JolietNode *child; + cret = create_tree(t, pos, &child, max_path); + if (cret < 0) { + /* error */ + joliet_node_free(node); + ret = cret; + break; + } else if (cret == ISO_SUCCESS) { + /* add child to this node */ + int nchildren = node->info.dir->nchildren++; + node->info.dir->children[nchildren] = child; + child->parent = node; + } + pos = pos->next; + } + } + break; + case LIBISO_BOOT: + if (t->eltorito) { + ret = create_node(t, iso, &node); + } else { + /* log and ignore */ + ret = iso_msg_submit(t->image->id, ISO_FILE_IGNORED, 0, + "El-Torito catalog found on a image without El-Torito.", + iso->name); + } + break; + case LIBISO_SYMLINK: + case LIBISO_SPECIAL: + ret = iso_msg_submit(t->image->id, ISO_FILE_IGNORED, 0, + "Can't add %s to Joliet tree. This kind of files can only" + " be added to a Rock Ridget tree. Skipping.", iso->name); + break; + default: + /* should never happen */ + return ISO_ASSERT_FAILURE; + } + if (ret <= 0) { + free(jname); + return ret; + } + node->name = jname; + *tree = node; + return ISO_SUCCESS; +} + +static int +cmp_node(const void *f1, const void *f2) +{ + JolietNode *f = *((JolietNode**)f1); + JolietNode *g = *((JolietNode**)f2); + return ucscmp(f->name, g->name); +} + +static +void sort_tree(JolietNode *root) +{ + size_t i; + + qsort(root->info.dir->children, root->info.dir->nchildren, + sizeof(void*), cmp_node); + for (i = 0; i < root->info.dir->nchildren; i++) { + JolietNode *child = root->info.dir->children[i]; + if (child->type == JOLIET_DIR) + sort_tree(child); + } +} + +static +int cmp_node_name(const void *f1, const void *f2) +{ + JolietNode *f = *((JolietNode**)f1); + JolietNode *g = *((JolietNode**)f2); + return ucscmp(f->name, g->name); +} + +static +int joliet_create_mangled_name(uint16_t *dest, uint16_t *src, int digits, + int number, uint16_t *ext) +{ + int ret, pos; + uint16_t *ucsnumber; + char fmt[16]; + char *nstr = alloca(digits + 1); + + sprintf(fmt, "%%0%dd", digits); + sprintf(nstr, fmt, number); + + ret = str2ucs("ASCII", nstr, &ucsnumber); + if (ret < 0) { + return ret; + } + + /* copy name */ + pos = ucslen(src); + ucsncpy(dest, src, pos); + + /* copy number */ + ucsncpy(dest + pos, ucsnumber, digits); + pos += digits; + + if (ext[0] != (uint16_t)0) { + size_t extlen = ucslen(ext); + dest[pos++] = (uint16_t)0x2E00; /* '.' in big endian UCS */ + ucsncpy(dest + pos, ext, extlen); + pos += extlen; + } + dest[pos] = (uint16_t)0; + free(ucsnumber); + return ISO_SUCCESS; +} + +static +int mangle_single_dir(Ecma119Image *t, JolietNode *dir) +{ + int ret; + int i, nchildren; + JolietNode **children; + IsoHTable *table; + int need_sort = 0; + + nchildren = dir->info.dir->nchildren; + children = dir->info.dir->children; + + /* a hash table will temporary hold the names, for fast searching */ + ret = iso_htable_create((nchildren * 100) / 80, iso_str_hash, + (compare_function_t)ucscmp, &table); + if (ret < 0) { + return ret; + } + for (i = 0; i < nchildren; ++i) { + uint16_t *name = children[i]->name; + ret = iso_htable_add(table, name, name); + if (ret < 0) { + goto mangle_cleanup; + } + } + + for (i = 0; i < nchildren; ++i) { + uint16_t *name, *ext; + uint16_t full_name[66]; + int max; /* computed max len for name, without extension */ + int j = i; + int digits = 1; /* characters to change per name */ + + /* first, find all child with same name */ + while (j + 1 < nchildren && + !cmp_node_name(children + i, children + j + 1)) { + ++j; + } + if (j == i) { + /* name is unique */ + continue; + } + + /* + * A max of 7 characters is good enought, it allows handling up to + * 9,999,999 files with same name. + */ + while (digits < 8) { + int ok, k; + uint16_t *dot; + int change = 0; /* number to be written */ + + /* copy name to buffer */ + ucscpy(full_name, children[i]->name); + + /* compute name and extension */ + dot = ucsrchr(full_name, '.'); + if (dot != NULL && children[i]->type != JOLIET_DIR) { + + /* + * File (not dir) with extension + */ + int extlen; + full_name[dot - full_name] = 0; + name = full_name; + ext = dot + 1; + + extlen = ucslen(ext); + max = 65 - extlen - 1 - digits; + if (max <= 0) { + /* this can happen if extension is too long */ + if (extlen + max > 3) { + /* + * reduce extension len, to give name an extra char + * note that max is negative or 0 + */ + extlen = extlen + max - 1; + ext[extlen] = 0; + max = 66 - extlen - 1 - digits; + } else { + /* + * error, we don't support extensions < 3 + * This can't happen with current limit of digits. + */ + ret = ISO_ERROR; + goto mangle_cleanup; + } + } + /* ok, reduce name by digits */ + if (name + max < dot) { + name[max] = 0; + } + } else { + /* Directory, or file without extension */ + if (children[i]->type == JOLIET_DIR) { + max = 65 - digits; + dot = NULL; /* dots have no meaning in dirs */ + } else { + max = 65 - digits; + } + name = full_name; + if (max < ucslen(name)) { + name[max] = 0; + } + /* let ext be an empty string */ + ext = name + ucslen(name); + } + + ok = 1; + /* change name of each file */ + for (k = i; k <= j; ++k) { + uint16_t tmp[66]; + while (1) { + ret = joliet_create_mangled_name(tmp, name, digits, + change, ext); + if (ret < 0) { + goto mangle_cleanup; + } + ++change; + if (change > int_pow(10, digits)) { + ok = 0; + break; + } + if (!iso_htable_get(table, tmp, NULL)) { + /* the name is unique, so it can be used */ + break; + } + } + if (ok) { + uint16_t *new = ucsdup(tmp); + if (new == NULL) { + ret = ISO_OUT_OF_MEM; + goto mangle_cleanup; + } + + iso_htable_remove_ptr(table, children[k]->name, NULL); + free(children[k]->name); + children[k]->name = new; + iso_htable_add(table, new, new); + + /* + * if we change a name we need to sort again children + * at the end + */ + need_sort = 1; + } else { + /* we need to increment digits */ + break; + } + } + if (ok) { + break; + } else { + ++digits; + } + } + if (digits == 8) { + ret = ISO_MANGLE_TOO_MUCH_FILES; + goto mangle_cleanup; + } + i = j; + } + + /* + * If needed, sort again the files inside dir + */ + if (need_sort) { + qsort(children, nchildren, sizeof(void*), cmp_node_name); + } + + ret = ISO_SUCCESS; + +mangle_cleanup : ; + iso_htable_destroy(table, NULL); + return ret; +} + +static +int mangle_tree(Ecma119Image *t, JolietNode *dir) +{ + int ret; + size_t i; + + ret = mangle_single_dir(t, dir); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < dir->info.dir->nchildren; ++i) { + if (dir->info.dir->children[i]->type == JOLIET_DIR) { + ret = mangle_tree(t, dir->info.dir->children[i]); + if (ret < 0) { + /* error */ + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int joliet_tree_create(Ecma119Image *t) +{ + int ret; + JolietNode *root; + + if (t == NULL) { + return ISO_NULL_POINTER; + } + + ret = create_tree(t, (IsoNode*)t->image->root, &root, 0); + if (ret <= 0) { + if (ret == 0) { + /* unexpected error, root ignored!! This can't happen */ + ret = ISO_ASSERT_FAILURE; + } + return ret; + } + + /* the Joliet tree is stored in Ecma119Image target */ + t->joliet_root = root; + + iso_msg_debug(t->image->id, "Sorting the Joliet tree..."); + sort_tree(root); + + iso_msg_debug(t->image->id, "Mangling Joliet names..."); + ret = mangle_tree(t, t->joliet_root); + if (ret < 0) { + return ret; + } + + return ISO_SUCCESS; +} + +/** + * Compute the size of a directory entry for a single node + */ +static +size_t calc_dirent_len(Ecma119Image *t, JolietNode *n) +{ + /* note than name len is always even, so we always need the pad byte */ + int ret = n->name ? ucslen(n->name) * 2 + 34 : 34; + if (n->type == JOLIET_FILE && !t->omit_version_numbers) { + /* take into account version numbers */ + ret += 4; + } + return ret; +} + +/** + * Computes the total size of all directory entries of a single joliet dir. + * This is like ECMA-119 6.8.1.1, but taking care that names are stored in + * UCS. + */ +static +size_t calc_dir_size(Ecma119Image *t, JolietNode *dir) +{ + size_t i, len; + + /* size of "." and ".." entries */ + len = 34 + 34; + + for (i = 0; i < dir->info.dir->nchildren; ++i) { + size_t remaining; + JolietNode *child = dir->info.dir->children[i]; + size_t dirent_len = calc_dirent_len(t, child); + remaining = BLOCK_SIZE - (len % BLOCK_SIZE); + if (dirent_len > remaining) { + /* child directory entry doesn't fit on block */ + len += remaining + dirent_len; + } else { + len += dirent_len; + } + } + + /* + * The size of a dir is always a multiple of block size, as we must add + * the size of the unused space after the last directory record + * (ECMA-119, 6.8.1.3) + */ + len = ROUND_UP(len, BLOCK_SIZE); + + /* cache the len */ + dir->info.dir->len = len; + return len; +} + +static +void calc_dir_pos(Ecma119Image *t, JolietNode *dir) +{ + size_t i, len; + + t->joliet_ndirs++; + dir->info.dir->block = t->curblock; + len = calc_dir_size(t, dir); + t->curblock += DIV_UP(len, BLOCK_SIZE); + for (i = 0; i < dir->info.dir->nchildren; i++) { + JolietNode *child = dir->info.dir->children[i]; + if (child->type == JOLIET_DIR) { + calc_dir_pos(t, child); + } + } +} + +/** + * Compute the length of the joliet path table, in bytes. + */ +static +uint32_t calc_path_table_size(JolietNode *dir) +{ + uint32_t size; + size_t i; + + /* size of path table for this entry */ + size = 8; + size += dir->name ? ucslen(dir->name) * 2 : 2; + + /* and recurse */ + for (i = 0; i < dir->info.dir->nchildren; i++) { + JolietNode *child = dir->info.dir->children[i]; + if (child->type == JOLIET_DIR) { + size += calc_path_table_size(child); + } + } + return size; +} + +static +int joliet_writer_compute_data_blocks(IsoImageWriter *writer) +{ + Ecma119Image *t; + uint32_t path_table_size; + + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + t = writer->target; + + /* compute position of directories */ + iso_msg_debug(t->image->id, "Computing position of Joliet dir structure"); + t->joliet_ndirs = 0; + calc_dir_pos(t, t->joliet_root); + + /* compute length of pathlist */ + iso_msg_debug(t->image->id, "Computing length of Joliet pathlist"); + path_table_size = calc_path_table_size(t->joliet_root); + + /* compute location for path tables */ + t->joliet_l_path_table_pos = t->curblock; + t->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + t->joliet_m_path_table_pos = t->curblock; + t->curblock += DIV_UP(path_table_size, BLOCK_SIZE); + t->joliet_path_table_size = path_table_size; + + return ISO_SUCCESS; +} + +/** + * Write a single directory record for Joliet. It is like (ECMA-119, 9.1), + * but file identifier is stored in UCS. + * + * @param file_id + * if >= 0, we use it instead of the filename (for "." and ".." entries). + * @param len_fi + * Computed length of the file identifier. Total size of the directory + * entry will be len + 34 (ECMA-119, 9.1.12), as padding is always needed + */ +static +void write_one_dir_record(Ecma119Image *t, JolietNode *node, int file_id, + uint8_t *buf, size_t len_fi) +{ + uint32_t len; + uint32_t block; + uint8_t len_dr; /*< size of dir entry */ + uint8_t *name = (file_id >= 0) ? (uint8_t*)&file_id + : (uint8_t*)node->name; + + struct ecma119_dir_record *rec = (struct ecma119_dir_record*)buf; + + len_dr = 33 + len_fi + (len_fi % 2 ? 0 : 1); + + memcpy(rec->file_id, name, len_fi); + + if (node->type == JOLIET_FILE && !t->omit_version_numbers) { + len_dr += 4; + rec->file_id[len_fi++] = 0; + rec->file_id[len_fi++] = ';'; + rec->file_id[len_fi++] = 0; + rec->file_id[len_fi++] = '1'; + } + + if (node->type == JOLIET_DIR) { + /* use the cached length */ + len = node->info.dir->len; + block = node->info.dir->block; + } else if (node->type == JOLIET_FILE) { + len = iso_file_src_get_size(node->info.file); + block = node->info.file->block; + } else { + /* + * for nodes other than files and dirs, we set both + * len and block to 0 + */ + len = 0; + block = 0; + } + + /* + * For ".." entry we need to write the parent info! + */ + if (file_id == 1 && node->parent) + node = node->parent; + + rec->len_dr[0] = len_dr; + iso_bb(rec->block, block, 4); + iso_bb(rec->length, len, 4); + iso_datetime_7(rec->recording_time, t->now, t->always_gmt); + rec->flags[0] = (node->type == JOLIET_DIR) ? 2 : 0; + iso_bb(rec->vol_seq_number, 1, 2); + rec->len_fi[0] = len_fi; +} + +/** + * Copy up to \p max characters from \p src to \p dest. If \p src has less than + * \p max characters, we pad dest with " " characters. + */ +static +void ucsncpy_pad(uint16_t *dest, const uint16_t *src, size_t max) +{ + char *cdest, *csrc; + size_t len, i; + + cdest = (char*)dest; + csrc = (char*)src; + + if (src != NULL) { + len = MIN(ucslen(src) * 2, max); + } else { + len = 0; + } + + for (i = 0; i < len; ++i) + cdest[i] = csrc[i]; + + for (i = len; i < max; i += 2) { + cdest[i] = '\0'; + cdest[i + 1] = ' '; + } +} + +static +int joliet_writer_write_vol_desc(IsoImageWriter *writer) +{ + IsoImage *image; + Ecma119Image *t; + struct ecma119_sup_vol_desc vol; + + uint16_t *vol_id = NULL, *pub_id = NULL, *data_id = NULL; + uint16_t *volset_id = NULL, *system_id = NULL, *application_id = NULL; + uint16_t *copyright_file_id = NULL, *abstract_file_id = NULL; + uint16_t *biblio_file_id = NULL; + + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + t = writer->target; + image = t->image; + + iso_msg_debug(image->id, "Write SVD for Joliet"); + + memset(&vol, 0, sizeof(struct ecma119_sup_vol_desc)); + + str2ucs(t->input_charset, image->volume_id, &vol_id); + str2ucs(t->input_charset, image->publisher_id, &pub_id); + str2ucs(t->input_charset, image->data_preparer_id, &data_id); + str2ucs(t->input_charset, image->volset_id, &volset_id); + + str2ucs(t->input_charset, image->system_id, &system_id); + str2ucs(t->input_charset, image->application_id, &application_id); + str2ucs(t->input_charset, image->copyright_file_id, ©right_file_id); + str2ucs(t->input_charset, image->abstract_file_id, &abstract_file_id); + str2ucs(t->input_charset, image->biblio_file_id, &biblio_file_id); + + vol.vol_desc_type[0] = 2; + memcpy(vol.std_identifier, "CD001", 5); + vol.vol_desc_version[0] = 1; + ucsncpy_pad((uint16_t*)vol.volume_id, vol_id, 32); + + /* make use of UCS-2 Level 3 */ + memcpy(vol.esc_sequences, "%/E", 3); + + iso_bb(vol.vol_space_size, t->vol_space_size, 4); + iso_bb(vol.vol_set_size, 1, 2); + iso_bb(vol.vol_seq_number, 1, 2); + iso_bb(vol.block_size, BLOCK_SIZE, 2); + iso_bb(vol.path_table_size, t->joliet_path_table_size, 4); + iso_lsb(vol.l_path_table_pos, t->joliet_l_path_table_pos, 4); + iso_msb(vol.m_path_table_pos, t->joliet_m_path_table_pos, 4); + + write_one_dir_record(t, t->joliet_root, 0, vol.root_dir_record, 1); + + ucsncpy_pad((uint16_t*)vol.vol_set_id, volset_id, 128); + ucsncpy_pad((uint16_t*)vol.publisher_id, pub_id, 128); + ucsncpy_pad((uint16_t*)vol.data_prep_id, data_id, 128); + + ucsncpy_pad((uint16_t*)vol.system_id, system_id, 32); + + ucsncpy_pad((uint16_t*)vol.application_id, application_id, 128); + ucsncpy_pad((uint16_t*)vol.copyright_file_id, copyright_file_id, 37); + ucsncpy_pad((uint16_t*)vol.abstract_file_id, abstract_file_id, 37); + ucsncpy_pad((uint16_t*)vol.bibliographic_file_id, biblio_file_id, 37); + + iso_datetime_17(vol.vol_creation_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_modification_time, t->now, t->always_gmt); + iso_datetime_17(vol.vol_effective_time, t->now, t->always_gmt); + vol.file_structure_version[0] = 1; + + free(vol_id); + free(volset_id); + free(pub_id); + free(data_id); + free(system_id); + free(application_id); + free(copyright_file_id); + free(abstract_file_id); + free(biblio_file_id); + + /* Finally write the Volume Descriptor */ + return iso_write(t, &vol, sizeof(struct ecma119_sup_vol_desc)); +} + +static +int write_one_dir(Ecma119Image *t, JolietNode *dir) +{ + int ret; + uint8_t buffer[BLOCK_SIZE]; + size_t i; + size_t fi_len, len; + + /* buf will point to current write position on buffer */ + uint8_t *buf = buffer; + + /* initialize buffer with 0s */ + memset(buffer, 0, BLOCK_SIZE); + + /* write the "." and ".." entries first */ + write_one_dir_record(t, dir, 0, buf, 1); + buf += 34; + write_one_dir_record(t, dir, 1, buf, 1); + buf += 34; + + for (i = 0; i < dir->info.dir->nchildren; i++) { + JolietNode *child = dir->info.dir->children[i]; + + /* compute len of directory entry */ + fi_len = ucslen(child->name) * 2; + len = fi_len + 34; + if (child->type == JOLIET_FILE && !t->omit_version_numbers) { + len += 4; + } + + if ( (buf + len - buffer) > BLOCK_SIZE) { + /* dir doesn't fit in current block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + if (ret < 0) { + return ret; + } + memset(buffer, 0, BLOCK_SIZE); + buf = buffer; + } + /* write the directory entry in any case */ + write_one_dir_record(t, child, -1, buf, fi_len); + buf += len; + } + + /* write the last block */ + ret = iso_write(t, buffer, BLOCK_SIZE); + return ret; +} + +static +int write_dirs(Ecma119Image *t, JolietNode *root) +{ + int ret; + size_t i; + + /* write all directory entries for this dir */ + ret = write_one_dir(t, root); + if (ret < 0) { + return ret; + } + + /* recurse */ + for (i = 0; i < root->info.dir->nchildren; i++) { + JolietNode *child = root->info.dir->children[i]; + if (child->type == JOLIET_DIR) { + ret = write_dirs(t, child); + if (ret < 0) { + return ret; + } + } + } + return ISO_SUCCESS; +} + +static +int write_path_table(Ecma119Image *t, JolietNode **pathlist, int l_type) +{ + size_t i, len; + uint8_t buf[256]; /* 256 is just a convenient size larger enought */ + struct ecma119_path_table_record *rec; + void (*write_int)(uint8_t*, uint32_t, int); + JolietNode *dir; + uint32_t path_table_size; + int parent = 0; + int ret= ISO_SUCCESS; + + path_table_size = 0; + write_int = l_type ? iso_lsb : iso_msb; + + for (i = 0; i < t->joliet_ndirs; i++) { + dir = pathlist[i]; + + /* find the index of the parent in the table */ + while ((i) && pathlist[parent] != dir->parent) { + parent++; + } + + /* write the Path Table Record (ECMA-119, 9.4) */ + memset(buf, 0, 256); + rec = (struct ecma119_path_table_record*) buf; + rec->len_di[0] = dir->parent ? (uint8_t) ucslen(dir->name) * 2 : 1; + rec->len_xa[0] = 0; + write_int(rec->block, dir->info.dir->block, 4); + write_int(rec->parent, parent + 1, 2); + if (dir->parent) { + memcpy(rec->dir_id, dir->name, rec->len_di[0]); + } + len = 8 + rec->len_di[0] + (rec->len_di[0] % 2); + ret = iso_write(t, buf, len); + if (ret < 0) { + /* error */ + return ret; + } + path_table_size += len; + } + + /* we need to fill the last block with zeros */ + path_table_size %= BLOCK_SIZE; + if (path_table_size) { + uint8_t zeros[BLOCK_SIZE]; + len = BLOCK_SIZE - path_table_size; + memset(zeros, 0, len); + ret = iso_write(t, zeros, len); + } + return ret; +} + +static +int write_path_tables(Ecma119Image *t) +{ + int ret; + size_t i, j, cur; + JolietNode **pathlist; + + iso_msg_debug(t->image->id, "Writing Joliet Path tables"); + + /* allocate temporal pathlist */ + pathlist = malloc(sizeof(void*) * t->joliet_ndirs); + if (pathlist == NULL) { + return ISO_OUT_OF_MEM; + } + pathlist[0] = t->joliet_root; + cur = 1; + + for (i = 0; i < t->joliet_ndirs; i++) { + JolietNode *dir = pathlist[i]; + for (j = 0; j < dir->info.dir->nchildren; j++) { + JolietNode *child = dir->info.dir->children[j]; + if (child->type == JOLIET_DIR) { + pathlist[cur++] = child; + } + } + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 1); + if (ret < 0) { + goto write_path_tables_exit; + } + + /* Write L Path Table */ + ret = write_path_table(t, pathlist, 0); + + write_path_tables_exit: ; + free(pathlist); + return ret; +} + +static +int joliet_writer_write_data(IsoImageWriter *writer) +{ + int ret; + Ecma119Image *t; + + if (writer == NULL) { + return ISO_NULL_POINTER; + } + t = writer->target; + + /* first of all, we write the directory structure */ + ret = write_dirs(t, t->joliet_root); + if (ret < 0) { + return ret; + } + + /* and write the path tables */ + ret = write_path_tables(t); + + return ret; +} + +static +int joliet_writer_free_data(IsoImageWriter *writer) +{ + /* free the Joliet tree */ + Ecma119Image *t = writer->target; + joliet_node_free(t->joliet_root); + return ISO_SUCCESS; +} + +int joliet_writer_create(Ecma119Image *target) +{ + int ret; + IsoImageWriter *writer; + + writer = malloc(sizeof(IsoImageWriter)); + if (writer == NULL) { + return ISO_OUT_OF_MEM; + } + + writer->compute_data_blocks = joliet_writer_compute_data_blocks; + writer->write_vol_desc = joliet_writer_write_vol_desc; + writer->write_data = joliet_writer_write_data; + writer->free_data = joliet_writer_free_data; + writer->data = NULL; + writer->target = target; + + iso_msg_debug(target->image->id, "Creating low level Joliet tree..."); + ret = joliet_tree_create(target); + if (ret < 0) { + return ret; + } + + /* add this writer to image */ + target->writers[target->nwriters++] = writer; + + /* we need the volume descriptor */ + target->curblock++; + return ISO_SUCCESS; +} diff --git a/libisofs/joliet.h b/libisofs/joliet.h new file mode 100644 index 0000000..a2db189 --- /dev/null +++ b/libisofs/joliet.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/** + * Declare Joliet related structures. + */ + +#ifndef LIBISO_JOLIET_H +#define LIBISO_JOLIET_H + +#include "libisofs.h" +#include "ecma119.h" + +enum joliet_node_type { + JOLIET_FILE, + JOLIET_DIR +}; + +struct joliet_dir_info { + JolietNode **children; + size_t nchildren; + size_t len; + size_t block; +}; + +struct joliet_node +{ + uint16_t *name; /**< Name in UCS-2BE. */ + + JolietNode *parent; + + IsoNode *node; /*< reference to the iso node */ + + enum joliet_node_type type; + union { + IsoFileSrc *file; + struct joliet_dir_info *dir; + } info; +}; + +/** + * Create a IsoWriter to deal with Joliet estructures, and add it to the given + * target. + * + * @return + * 1 on success, < 0 on error + */ +int joliet_writer_create(Ecma119Image *target); + +#endif /* LIBISO_JOLIET_H */ diff --git a/libisofs/libiso_msgs.c b/libisofs/libiso_msgs.c new file mode 100644 index 0000000..6a60429 --- /dev/null +++ b/libisofs/libiso_msgs.c @@ -0,0 +1,439 @@ + +/* libiso_msgs (generated from libdax_msgs : Fri Feb 22 19:42:52 CET 2008) + Message handling facility of libisofs. + Copyright (C) 2006 - 2008 Thomas Schmitt , + provided under GPL version 2 +*/ + +#include +#include +#include +#include +#include +#include +#include + +/* Only this single source module is entitled to do this */ +#define LIBISO_MSGS_H_INTERNAL 1 + +/* All participants in the messaging system must do this */ +#include "libiso_msgs.h" + + +/* ----------------------------- libiso_msgs_item ------------------------- */ + + +static int libiso_msgs_item_new(struct libiso_msgs_item **item, + struct libiso_msgs_item *link, int flag) +{ + int ret; + struct libiso_msgs_item *o; + struct timeval tv; + struct timezone tz; + + (*item)= o= + (struct libiso_msgs_item *) malloc(sizeof(struct libiso_msgs_item)); + if(o==NULL) + return(-1); + o->timestamp= 0.0; + ret= gettimeofday(&tv,&tz); + if(ret==0) + o->timestamp= tv.tv_sec+0.000001*tv.tv_usec; + o->process_id= getpid(); + o->origin= -1; + o->severity= LIBISO_MSGS_SEV_ALL; + o->priority= LIBISO_MSGS_PRIO_ZERO; + o->error_code= 0; + o->msg_text= NULL; + o->os_errno= 0; + o->prev= link; + o->next= NULL; + if(link!=NULL) { + if(link->next!=NULL) { + link->next->prev= o; + o->next= link->next; + } + link->next= o; + } + return(1); +} + + +/** Detaches item from its queue and eventually readjusts start, end pointers + of the queue */ +int libiso_msgs_item_unlink(struct libiso_msgs_item *o, + struct libiso_msgs_item **chain_start, + struct libiso_msgs_item **chain_end, int flag) +{ + if(o->prev!=NULL) + o->prev->next= o->next; + if(o->next!=NULL) + o->next->prev= o->prev; + if(chain_start!=NULL) + if(*chain_start == o) + *chain_start= o->next; + if(chain_end!=NULL) + if(*chain_end == o) + *chain_end= o->prev; + o->next= o->prev= NULL; + return(1); +} + + +int libiso_msgs_item_destroy(struct libiso_msgs_item **item, + int flag) +{ + struct libiso_msgs_item *o; + + o= *item; + if(o==NULL) + return(0); + libiso_msgs_item_unlink(o,NULL,NULL,0); + if(o->msg_text!=NULL) + free((char *) o->msg_text); + free((char *) o); + *item= NULL; + return(1); +} + + +int libiso_msgs_item_get_msg(struct libiso_msgs_item *item, + int *error_code, char **msg_text, int *os_errno, + int flag) +{ + *error_code= item->error_code; + *msg_text= item->msg_text; + *os_errno= item->os_errno; + return(1); +} + + +int libiso_msgs_item_get_origin(struct libiso_msgs_item *item, + double *timestamp, pid_t *process_id, int *origin, + int flag) +{ + *timestamp= item->timestamp; + *process_id= item->process_id; + *origin= item->origin; + return(1); +} + + +int libiso_msgs_item_get_rank(struct libiso_msgs_item *item, + int *severity, int *priority, int flag) +{ + *severity= item->severity; + *priority= item->priority; + return(1); +} + + +/* ------------------------------- libiso_msgs ---------------------------- */ + + +int libiso_msgs_new(struct libiso_msgs **m, int flag) +{ + struct libiso_msgs *o; + + (*m)= o= (struct libiso_msgs *) malloc(sizeof(struct libiso_msgs)); + if(o==NULL) + return(-1); + o->refcount= 1; + o->oldest= NULL; + o->youngest= NULL; + o->count= 0; + o->queue_severity= LIBISO_MSGS_SEV_ALL; + o->print_severity= LIBISO_MSGS_SEV_NEVER; + strcpy(o->print_id,"libiso: "); + +#ifndef LIBISO_MSGS_SINGLE_THREADED + pthread_mutex_init(&(o->lock_mutex),NULL); +#endif + + return(1); +} + + +static int libiso_msgs_lock(struct libiso_msgs *m, int flag) +{ + +#ifndef LIBISO_MSGS_SINGLE_THREADED + int ret; + + ret= pthread_mutex_lock(&(m->lock_mutex)); + if(ret!=0) + return(0); +#endif + + return(1); +} + + +static int libiso_msgs_unlock(struct libiso_msgs *m, int flag) +{ + +#ifndef LIBISO_MSGS_SINGLE_THREADED + int ret; + + ret= pthread_mutex_unlock(&(m->lock_mutex)); + if(ret!=0) + return(0); +#endif + + return(1); +} + + +int libiso_msgs_destroy(struct libiso_msgs **m, int flag) +{ + struct libiso_msgs *o; + struct libiso_msgs_item *item, *next_item; + + o= *m; + if(o==NULL) + return(0); + if(o->refcount > 1) { + if(libiso_msgs_lock(*m,0)<=0) + return(-1); + o->refcount--; + libiso_msgs_unlock(*m,0); + *m= NULL; + return(1); + } + +#ifndef LIBISO_MSGS_SINGLE_THREADED + if(pthread_mutex_destroy(&(o->lock_mutex))!=0) { + pthread_mutex_unlock(&(o->lock_mutex)); + pthread_mutex_destroy(&(o->lock_mutex)); + } +#endif + + for(item= o->oldest; item!=NULL; item= next_item) { + next_item= item->next; + libiso_msgs_item_destroy(&item,0); + } + free((char *) o); + *m= NULL; + return(1); +} + + +int libiso_msgs_refer(struct libiso_msgs **pt, struct libiso_msgs *m, int flag) +{ + if(libiso_msgs_lock(m,0)<=0) + return(0); + m->refcount++; + *pt= m; + libiso_msgs_unlock(m,0); + return(1); +} + + +int libiso_msgs_set_severities(struct libiso_msgs *m, int queue_severity, + int print_severity, char *print_id, int flag) +{ + if(libiso_msgs_lock(m,0)<=0) + return(0); + m->queue_severity= queue_severity; + m->print_severity= print_severity; + strncpy(m->print_id,print_id,80); + m->print_id[80]= 0; + libiso_msgs_unlock(m,0); + return(1); +} + + +int libiso_msgs__text_to_sev(char *severity_name, int *severity, + int flag) +{ + if(strncmp(severity_name,"NEVER",5)==0) + *severity= LIBISO_MSGS_SEV_NEVER; + else if(strncmp(severity_name,"ABORT",5)==0) + *severity= LIBISO_MSGS_SEV_ABORT; + else if(strncmp(severity_name,"FATAL",5)==0) + *severity= LIBISO_MSGS_SEV_FATAL; + else if(strncmp(severity_name,"FAILURE",7)==0) + *severity= LIBISO_MSGS_SEV_FAILURE; + else if(strncmp(severity_name,"MISHAP",6)==0) + *severity= LIBISO_MSGS_SEV_MISHAP; + else if(strncmp(severity_name,"SORRY",5)==0) + *severity= LIBISO_MSGS_SEV_SORRY; + else if(strncmp(severity_name,"WARNING",7)==0) + *severity= LIBISO_MSGS_SEV_WARNING; + else if(strncmp(severity_name,"HINT",4)==0) + *severity= LIBISO_MSGS_SEV_HINT; + else if(strncmp(severity_name,"NOTE",4)==0) + *severity= LIBISO_MSGS_SEV_NOTE; + else if(strncmp(severity_name,"UPDATE",6)==0) + *severity= LIBISO_MSGS_SEV_UPDATE; + else if(strncmp(severity_name,"DEBUG",5)==0) + *severity= LIBISO_MSGS_SEV_DEBUG; + else if(strncmp(severity_name,"ERRFILE",7)==0) + *severity= LIBISO_MSGS_SEV_ERRFILE; + else if(strncmp(severity_name,"ALL",3)==0) + *severity= LIBISO_MSGS_SEV_ALL; + else { + *severity= LIBISO_MSGS_SEV_ALL; + return(0); + } + return(1); +} + + +int libiso_msgs__sev_to_text(int severity, char **severity_name, + int flag) +{ + if(flag&1) { + *severity_name= "NEVER\nABORT\nFATAL\nFAILURE\nMISHAP\nSORRY\nWARNING\nHINT\nNOTE\nUPDATE\nDEBUG\nERRFILE\nALL"; + return(1); + } + *severity_name= ""; + if(severity>=LIBISO_MSGS_SEV_NEVER) + *severity_name= "NEVER"; + else if(severity>=LIBISO_MSGS_SEV_ABORT) + *severity_name= "ABORT"; + else if(severity>=LIBISO_MSGS_SEV_FATAL) + *severity_name= "FATAL"; + else if(severity>=LIBISO_MSGS_SEV_FAILURE) + *severity_name= "FAILURE"; + else if(severity>=LIBISO_MSGS_SEV_MISHAP) + *severity_name= "MISHAP"; + else if(severity>=LIBISO_MSGS_SEV_SORRY) + *severity_name= "SORRY"; + else if(severity>=LIBISO_MSGS_SEV_WARNING) + *severity_name= "WARNING"; + else if(severity>=LIBISO_MSGS_SEV_HINT) + *severity_name= "HINT"; + else if(severity>=LIBISO_MSGS_SEV_NOTE) + *severity_name= "NOTE"; + else if(severity>=LIBISO_MSGS_SEV_UPDATE) + *severity_name= "UPDATE"; + else if(severity>=LIBISO_MSGS_SEV_DEBUG) + *severity_name= "DEBUG"; + else if(severity>=LIBISO_MSGS_SEV_ERRFILE) + *severity_name= "ERRFILE"; + else if(severity>=LIBISO_MSGS_SEV_ALL) + *severity_name= "ALL"; + else { + *severity_name= ""; + return(0); + } + return(1); +} + + +int libiso_msgs_submit(struct libiso_msgs *m, int origin, int error_code, + int severity, int priority, char *msg_text, + int os_errno, int flag) +{ + int ret; + char *textpt,*sev_name,sev_text[81]; + struct libiso_msgs_item *item= NULL; + + if(severity >= m->print_severity) { + if(msg_text==NULL) + textpt= ""; + else + textpt= msg_text; + sev_text[0]= 0; + ret= libiso_msgs__sev_to_text(severity,&sev_name,0); + if(ret>0) + sprintf(sev_text,"%s : ",sev_name); + + fprintf(stderr,"%s%s%s\n",m->print_id,sev_text,textpt); + if(os_errno!=0) { + ret= libiso_msgs_lock(m,0); + if(ret<=0) + return(-1); + fprintf(stderr,"%s( Most recent system error: %d '%s' )\n", + m->print_id,os_errno,strerror(os_errno)); + libiso_msgs_unlock(m,0); + } + + } + if(severity < m->queue_severity) + return(0); + + ret= libiso_msgs_lock(m,0); + if(ret<=0) + return(-1); + ret= libiso_msgs_item_new(&item,m->youngest,0); + if(ret<=0) + goto failed; + item->origin= origin; + item->error_code= error_code; + item->severity= severity; + item->priority= priority; + if(msg_text!=NULL) { + item->msg_text= malloc(strlen(msg_text)+1); + if(item->msg_text==NULL) + goto failed; + strcpy(item->msg_text,msg_text); + } + item->os_errno= os_errno; + if(m->oldest==NULL) + m->oldest= item; + m->youngest= item; + m->count++; + libiso_msgs_unlock(m,0); + +/* +fprintf(stderr,"libiso_experimental: message submitted to queue (now %d)\n", + m->count); +*/ + + return(1); +failed:; + libiso_msgs_item_destroy(&item,0); + libiso_msgs_unlock(m,0); + return(-1); +} + + +int libiso_msgs_obtain(struct libiso_msgs *m, struct libiso_msgs_item **item, + int severity, int priority, int flag) +{ + int ret; + struct libiso_msgs_item *im, *next_im= NULL; + + *item= NULL; + ret= libiso_msgs_lock(m,0); + if(ret<=0) + return(-1); + for(im= m->oldest; im!=NULL; im= next_im) { + for(; im!=NULL; im= next_im) { + next_im= im->next; + if(im->severity>=severity) + break; + libiso_msgs_item_unlink(im,&(m->oldest),&(m->youngest),0); + libiso_msgs_item_destroy(&im,0); /* severity too low: delete */ + } + if(im==NULL) + break; + if(im->priority>=priority) + break; + } + if(im==NULL) + {ret= 0; goto ex;} + libiso_msgs_item_unlink(im,&(m->oldest),&(m->youngest),0); + *item= im; + ret= 1; +ex:; + libiso_msgs_unlock(m,0); + return(ret); +} + + +int libiso_msgs_destroy_item(struct libiso_msgs *m, + struct libiso_msgs_item **item, int flag) +{ + int ret; + + ret= libiso_msgs_lock(m,0); + if(ret<=0) + return(-1); + ret= libiso_msgs_item_destroy(item,0); + libiso_msgs_unlock(m,0); + return(ret); +} + diff --git a/libisofs/libiso_msgs.h b/libisofs/libiso_msgs.h new file mode 100644 index 0000000..17e8f9e --- /dev/null +++ b/libisofs/libiso_msgs.h @@ -0,0 +1,682 @@ + +/* libiso_msgs (generated from libdax_msgs : Fri Feb 22 19:42:52 CET 2008) + Message handling facility of libisofs. + Copyright (C) 2006-2008 Thomas Schmitt , + provided under GPL version 2 +*/ + + +/* + *Never* set this macro outside libiso_msgs.c ! + The entrails of the message handling facility are not to be seen by + the other library components or the applications. +*/ +#ifdef LIBISO_MSGS_H_INTERNAL + + +#ifndef LIBISO_MSGS_SINGLE_THREADED +#include +#endif + + +struct libiso_msgs_item { + + double timestamp; + pid_t process_id; + int origin; + + int severity; + int priority; + + /* Apply for your developer's error code range at + libburn-hackers@pykix.org + Report introduced codes in the list below. */ + int error_code; + + char *msg_text; + int os_errno; + + struct libiso_msgs_item *prev,*next; + +}; + + +struct libiso_msgs { + + int refcount; + + struct libiso_msgs_item *oldest; + struct libiso_msgs_item *youngest; + int count; + + int queue_severity; + int print_severity; + char print_id[81]; + +#ifndef LIBISO_MSGS_SINGLE_THREADED + pthread_mutex_t lock_mutex; +#endif + + +}; + +#endif /* LIBISO_MSGS_H_INTERNAL */ + + +#ifndef LIBISO_MSGS_H_INCLUDED +#define LIBISO_MSGS_H_INCLUDED 1 + + +#ifndef LIBISO_MSGS_H_INTERNAL + + + /* Architectural aspects */ +/* + libdax_msgs is designed to serve in libraries which want to offer their + applications a way to control the output of library messages. It shall be + incorporated by an owner, i.e. a software entity which encloses the code + of the .c file. + + Owner of libdax_msgs is libburn. A fully compatible variant named libiso_msgs + is owned by libisofs and can get generated by a script of the libburn + project: libburn/libiso_msgs_to_xyz_msgs.sh . + + Reason: One cannot link two owners of the same variant together because + both would offer the same functions to the linker. For that situation one + has to create a compatible variant as it is done for libisofs. + + Compatible variants may get plugged together by call combinations like + burn_set_messenger(iso_get_messenger()); + A new variant would demand a _set_messenger() function if it has to work + with libisofs. If only libburn is planned as link partner then a simple + _get_messenger() does suffice. + Take care to shutdown libburn before its provider of the *_msgs object + gets shut down. + +*/ + + /* Public Opaque Handles */ + +/** A pointer to this is a opaque handle to a message handling facility */ +struct libiso_msgs; + +/** A pointer to this is a opaque handle to a single message item */ +struct libiso_msgs_item; + +#endif /* ! LIBISO_MSGS_H_INTERNAL */ + + + /* Public Macros */ + + +/* Registered Severities */ + +/* It is well advisable to let applications select severities via strings and + forwarded functions libiso_msgs__text_to_sev(), libiso_msgs__sev_to_text(). + These macros are for use by the owner of libiso_msgs. +*/ + +/** Use this to get messages of any severity. Do not use for submitting. +*/ +#define LIBISO_MSGS_SEV_ALL 0x00000000 + + +/** Messages of this severity shall transport plain disk file paths + whenever an event of severity SORRY or above is related with an + individual disk file. + No message text shall be added to the file path. The ERRFILE message + shall be issued before the human readable message which carries the + true event severity. That message should contain the file path so it + can be found by strstr(message, path)!=NULL. + The error code shall be the same as with the human readable message. +*/ +#define LIBISO_MSGS_SEV_ERRFILE 0x08000000 + + +/** Debugging messages not to be visible to normal users by default +*/ +#define LIBISO_MSGS_SEV_DEBUG 0x10000000 + +/** Update of a progress report about long running actions +*/ +#define LIBISO_MSGS_SEV_UPDATE 0x20000000 + +/** Not so usual events which were gracefully handled +*/ +#define LIBISO_MSGS_SEV_NOTE 0x30000000 + +/** Possibilities to achieve a better result +*/ +#define LIBISO_MSGS_SEV_HINT 0x40000000 + +/** Warnings about problems which could not be handled optimally +*/ +#define LIBISO_MSGS_SEV_WARNING 0x50000000 + + +/** Non-fatal error messages indicating that parts of an action failed but + processing may go on if one accepts deviations from the desired result. + + SORRY may also be the severity for incidents which are severe enough + for FAILURE but happen within already started irrevocable actions, + like ISO image generation. A precondition for such a severity ease is + that the action can be continued after the incident. + See below MISHAP for what xorriso would need instead of this kind of SORRY + and generates for itself in case of libisofs image generation. + + E.g.: A pattern yields no result. + A speed setting cannot be made. + A libisofs input file is inaccessible during image generation. + + After SORRY a function should try to go on if that makes any sense + and if no threshold prescribes abort on SORRY. The function should + nevertheless indicate some failure in its return value. + It should - but it does not have to. +*/ +#define LIBISO_MSGS_SEV_SORRY 0x60000000 + + +/** A FAILURE (see below) which can be tolerated during long lasting + operations just because they cannot simply be stopped or revoked. + + xorriso converts libisofs SORRY messages issued during image generation + into MISHAP messages in order to allow its evaluators to distinguish + image generation problems from minor image composition problems. + E.g.: + A libisofs input file is inaccessible during image generation. + + After a MISHAP a function should behave like after SORRY. +*/ +#define LIBISO_MSGS_SEV_MISHAP 0x64000000 + + +/** Non-fatal error indicating that an important part of an action failed and + that only a new setup of preconditions will give hope for sufficient + success. + + E.g.: No media is inserted in the output drive. + No write mode can be found for inserted media. + A libisofs input file is inaccessible during grafting. + + After FAILURE a function should end with a return value indicating failure. + It is at the discretion of the function whether it ends immediately in any + case or whether it tries to go on if the eventual threshold allows. +*/ +#define LIBISO_MSGS_SEV_FAILURE 0x68000000 + + +/** An error message which puts the whole operation of the program in question + + E.g.: Not enough memory for essential temporary objects. + Irregular errors from resources. + Programming errors (soft assert). + + After FATAL a function should end very soon with a return value + indicating severe failure. +*/ +#define LIBISO_MSGS_SEV_FATAL 0x70000000 + + +/** A message from an abort handler which will finally finish libburn +*/ +#define LIBISO_MSGS_SEV_ABORT 0x71000000 + +/** A severity to exclude resp. discard any possible message. + Do not use this severity for submitting. +*/ +#define LIBISO_MSGS_SEV_NEVER 0x7fffffff + + +/* Registered Priorities */ + +/* Priorities are to be selected by the programmers and not by the user. */ + +#define LIBISO_MSGS_PRIO_ZERO 0x00000000 +#define LIBISO_MSGS_PRIO_LOW 0x10000000 +#define LIBISO_MSGS_PRIO_MEDIUM 0x20000000 +#define LIBISO_MSGS_PRIO_HIGH 0x30000000 +#define LIBISO_MSGS_PRIO_TOP 0x7ffffffe + +/* Do not use this priority for submitting */ +#define LIBISO_MSGS_PRIO_NEVER 0x7fffffff + + +/* Origin numbers of libburn drives may range from 0 to 1048575 */ +#define LIBISO_MSGS_ORIGIN_DRIVE_BASE 0 +#define LIBISO_MSGS_ORIGIN_DRIVE_TOP 0xfffff + +/* Origin numbers of libisofs images may range from 1048575 to 2097152 */ +#define LIBISO_MSGS_ORIGIN_IMAGE_BASE 0x100000 +#define LIBISO_MSGS_ORIGIN_IMAGE_TOP 0x1fffff + + + + /* Public Functions */ + + /* Calls initiated from inside the direct owner (e.g. from libburn) */ + + +/** Create new empty message handling facility with queue and issue a first + official reference to it. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return >0 success, <=0 failure +*/ +int libiso_msgs_new(struct libiso_msgs **m, int flag); + + +/** Destroy a message handling facility and all its eventual messages. + The submitted pointer gets set to NULL. + Actually only the last destroy call of all offical references to the object + will really dispose it. All others just decrement the reference counter. + Call this function only with official reference pointers obtained by + libiso_msgs_new() or libiso_msgs_refer(), and only once per such pointer. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 for success, 0 for pointer to NULL, -1 for fatal error +*/ +int libiso_msgs_destroy(struct libiso_msgs **m, int flag); + + +/** Create an official reference to an existing libiso_msgs object. The + references keep the object alive at least until it is released by + a matching number of destroy calls. So each reference MUST be revoked + by exactly one call to libiso_msgs_destroy(). + @param pt The pointer to be set and registered + @param m A pointer to the existing object + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 for success, 0 for failure +*/ +int libiso_msgs_refer(struct libiso_msgs **pt, struct libiso_msgs *o, int flag); + + +/** Submit a message to a message handling facility. + @param origin program specific identification number of the originator of + a message. E.g. drive number. Programs should have an own + range of origin numbers. See above LIBISO_MSGS_ORIGIN_*_BASE + Use -1 if no number is known. + @param error_code Unique error code. Use only registered codes. See below. + The same unique error_code may be issued at different + occasions but those should be equivalent out of the view + of a libiso_msgs application. (E.g. "cannot open ATA drive" + versus "cannot open SCSI drive" would be equivalent.) + @param severity The LIBISO_MSGS_SEV_* of the event. + @param priority The LIBISO_MSGS_PRIO_* number of the event. + @param msg_text Printable and human readable message text. + @param os_errno Eventual error code from operating system (0 if none) + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 on success, 0 on rejection, <0 for severe errors +*/ +int libiso_msgs_submit(struct libiso_msgs *m, int origin, int error_code, + int severity, int priority, char *msg_text, + int os_errno, int flag); + + + + /* Calls from applications (to be forwarded by direct owner) */ + + +/** Convert a registered severity number into a severity name + @param flag Bitfield for control purposes: + bit0= list all severity names in a newline separated string + @return >0 success, <=0 failure +*/ +int libiso_msgs__sev_to_text(int severity, char **severity_name, + int flag); + + +/** Convert a severity name into a severity number, + @param flag Bitfield for control purposes (unused yet, submit 0) + @return >0 success, <=0 failure +*/ +int libiso_msgs__text_to_sev(char *severity_name, int *severity, + int flag); + + +/** Set minimum severity for messages to be queued (default + LIBISO_MSGS_SEV_ALL) and for messages to be printed directly to stderr + (default LIBISO_MSGS_SEV_NEVER). + @param print_id A text of at most 80 characters to be printed before + any eventually printed message (default is "libiso: "). + @param flag Bitfield for control purposes (unused yet, submit 0) + @return always 1 for now +*/ +int libiso_msgs_set_severities(struct libiso_msgs *m, int queue_severity, + int print_severity, char *print_id, int flag); + + +/** Obtain a message item that has at least the given severity and priority. + Usually all older messages of lower severity are discarded then. If no + item of sufficient severity was found, all others are discarded from the + queue. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 if a matching item was found, 0 if not, <0 for severe errors +*/ +int libiso_msgs_obtain(struct libiso_msgs *m, struct libiso_msgs_item **item, + int severity, int priority, int flag); + + +/** Destroy a message item obtained by libiso_msgs_obtain(). The submitted + pointer gets set to NULL. + Caution: Copy eventually obtained msg_text before destroying the item, + if you want to use it further. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 for success, 0 for pointer to NULL, <0 for severe errors +*/ +int libiso_msgs_destroy_item(struct libiso_msgs *m, + struct libiso_msgs_item **item, int flag); + + +/** Obtain from a message item the three application oriented components as + submitted with the originating call of libiso_msgs_submit(). + Caution: msg_text becomes a pointer into item, not a copy. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 on success, 0 on invalid item, <0 for servere errors +*/ +int libiso_msgs_item_get_msg(struct libiso_msgs_item *item, + int *error_code, char **msg_text, int *os_errno, + int flag); + + +/** Obtain from a message item the submitter identification submitted + with the originating call of libiso_msgs_submit(). + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 on success, 0 on invalid item, <0 for servere errors +*/ +int libiso_msgs_item_get_origin(struct libiso_msgs_item *item, + double *timestamp, pid_t *process_id, int *origin, + int flag); + + +/** Obtain from a message item severity and priority as submitted + with the originating call of libiso_msgs_submit(). + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 on success, 0 on invalid item, <0 for servere errors +*/ +int libiso_msgs_item_get_rank(struct libiso_msgs_item *item, + int *severity, int *priority, int flag); + + +#ifdef LIDBAX_MSGS_________________ + + + /* Registered Error Codes */ + + +Format: error_code (LIBISO_MSGS_SEV_*,LIBISO_MSGS_PRIO_*) = explanation +If no severity or priority are fixely associated, use "(,)". + +------------------------------------------------------------------------------ +Range "libiso_msgs" : 0x00000000 to 0x0000ffff + + 0x00000000 (ALL,ZERO) = Initial setting in new libiso_msgs_item + 0x00000001 (DEBUG,ZERO) = Test error message + 0x00000002 (DEBUG,ZERO) = Debugging message + 0x00000003 (FATAL,HIGH) = Out of virtual memory + + +------------------------------------------------------------------------------ +Range "elmom" : 0x00010000 to 0x0001ffff + + + +------------------------------------------------------------------------------ +Range "scdbackup" : 0x00020000 to 0x0002ffff + + Acessing and defending drives: + + 0x00020001 (SORRY,LOW) = Cannot open busy device + 0x00020002 (SORRY,HIGH) = Encountered error when closing drive + 0x00020003 (SORRY,HIGH) = Could not grab drive + 0x00020004 (NOTE,HIGH) = Opened O_EXCL scsi sibling + 0x00020005 (SORRY,HIGH) = Failed to open device + 0x00020006 (FATAL,HIGH) = Too many scsi siblings + 0x00020007 (NOTE,HIGH) = Closed O_EXCL scsi siblings + 0x00020008 (SORRY,HIGH) = Device busy. Failed to fcntl-lock + 0x00020009 (SORRY,HIGH) = Neither stdio-path nor its directory exist + + General library operations: + + 0x00020101 (WARNING,HIGH) = Cannot find given worker item + 0x00020102 (SORRY,HIGH) = A drive operation is still going on + 0x00020103 (WARNING,HIGH) = After scan a drive operation is still going on + 0x00020104 (SORRY,HIGH) = NULL pointer caught + 0x00020105 (SORRY,HIGH) = Drive is already released + 0x00020106 (SORRY,HIGH) = Drive is busy on attempt to close + 0x00020107 (WARNING,HIGH) = A drive is still busy on shutdown of library + 0x00020108 (SORRY,HIGH) = Drive is not grabbed on disc status inquiry + 0x00020108 (FATAL,HIGH) = Could not allocate new drive object + 0x00020109 (FATAL,HIGH) = Library not running + 0x0002010a (FATAL,HIGH) = Unsuitable track mode + 0x0002010b (FATAL,HIGH) = Burn run failed + 0x0002010c (FATAL,HIGH) = Failed to transfer command to drive + 0x0002010d (DEBUG,HIGH) = Could not inquire TOC + 0x0002010e (FATAL,HIGH) = Attempt to read ATIP from ungrabbed drive + 0x0002010f (DEBUG,HIGH) = SCSI error condition on command + 0x00020110 (FATAL,HIGH) = Persistent drive address too long + 0x00020111 (FATAL,HIGH) = Could not allocate new auxiliary object + 0x00020112 (SORRY,HIGH) = Bad combination of write_type and block_type + 0x00020113 (FATAL,HIGH) = Drive capabilities not inquired yet + 0x00020114 (SORRY,HIGH) = Attempt to set ISRC with bad data + 0x00020115 (SORRY,HIGH) = Attempt to set track mode to unusable value + 0x00020116 (FATAL,HIGH) = Track mode has unusable value + 0x00020117 (FATAL,HIGH) = toc_entry of drive is already in use + 0x00020118 (DEBUG,HIGH) = Closing track + 0x00020119 (DEBUG,HIGH) = Closing session + 0x0002011a (NOTE,HIGH) = Padding up track to minimum size + 0x0002011b (FATAL,HIGH) = Attempt to read track info from ungrabbed drive + 0x0002011c (FATAL,HIGH) = Attempt to read track info from busy drive + 0x0002011d (FATAL,HIGH) = SCSI error on write + 0x0002011e (SORRY,HIGH) = Unsuitable media detected + 0x0002011f (SORRY,HIGH) = Burning is restricted to a single track + 0x00020120 (NOTE,HIGH) = FORMAT UNIT ignored + 0x00020121 (FATAL,HIGH) = Write preparation setup failed + 0x00020122 (FATAL,HIGH) = SCSI error on format_unit + 0x00020123 (SORRY,HIGH) = DVD Media are unsuitable for desired track type + 0x00020124 (SORRY,HIGH) = SCSI error on set_streaming + 0x00020125 (SORRY,HIGH) = Write start address not supported + 0x00020126 (SORRY,HIGH) = Write start address not properly aligned + 0x00020127 (NOTE,HIGH) = Write start address is ... + 0x00020128 (FATAL,HIGH) = Unsupported inquiry_type with mmc_get_performance + 0x00020129 (SORRY,HIGH) = Will not format media type + 0x0002012a (FATAL,HIGH) = Cannot inquire write mode capabilities + 0x0002012b (FATAL,HIGH) = Drive offers no suitable write mode with this job + 0x0002012c (SORRY,HIGH) = Too many logical tracks recorded + 0x0002012d (FATAL,HIGH) = Exceeding range of permissible write addresses + 0x0002012e (NOTE,HIGH) = Activated track default size + 0x0002012f (SORRY,HIGH) = SAO is restricted to single fixed size session + 0x00020130 (SORRY,HIGH) = Drive and media state unsuitable for blanking + 0x00020131 (SORRY,HIGH) = No suitable formatting type offered by drive + 0x00020132 (SORRY,HIGH) = Selected format is not suitable for libburn + 0x00020133 (SORRY,HIGH) = Cannot mix data and audio in SAO mode + 0x00020134 (NOTE,HIGH) = Defaulted TAO to DAO + 0x00020135 (SORRY,HIGH) = Cannot perform TAO, job unsuitable for DAO + 0x00020136 (SORRY,HIGH) = DAO burning restricted to single fixed size track + 0x00020137 (HINT,HIGH) = TAO would be possible + 0x00020138 (FATAL,HIGH) = Cannot reserve track + 0x00020139 (SORRY,HIGH) = Write job parameters are unsuitable + 0x0002013a (FATAL,HIGH) = No suitable media detected + 0x0002013b (DEBUG,HIGH) = SCSI command indicates host or driver error + 0x0002013c (SORRY,HIGH) = Malformed capabilities page 2Ah received + 0x0002013d (DEBUG,LOW) = Waiting for free buffer space takes long time + 0x0002013e (SORRY,HIGH) = Timeout with waiting for free buffer. Now disabled + 0x0002013f (DEBUG,LOW) = Reporting total time spent with waiting for buffer + 0x00020140 (FATAL,HIGH) = Drive is busy on attempt to write random access + 0x00020141 (SORRY,HIGH) = Write data count not properly aligned + 0x00020142 (FATAL,HIGH) = Drive is not grabbed on random access write + 0x00020143 (SORRY,HIGH) = Read start address not properly aligned + 0x00020144 (SORRY,HIGH) = SCSI error on read + 0x00020145 (FATAL,HIGH) = Drive is busy on attempt to read data + 0x00020146 (FATAL,HIGH) = Drive is a virtual placeholder + 0x00020147 (SORRY,HIGH) = Cannot address start byte + 0x00020148 (SORRY,HIGH) = Cannot write desired amount of data + 0x00020149 (SORRY,HIGH) = Unsuitable filetype for pseudo-drive + 0x0002014a (SORRY,HIGH) = Cannot read desired amount of data + 0x0002014b (SORRY,HIGH) = Drive is already registered resp. scanned + 0x0002014c (FATAL,HIGH) = Emulated drive caught in SCSI function + 0x0002014d (SORRY,HIGH) = Asynchromous SCSI error + 0x0002014f (SORRY,HIGH) = Timeout with asynchromous SCSI command + 0x00020150 (DEBUG,LOW) = Reporting asynchronous waiting time + 0x00020151 (FATAL,HIGH) = Read attempt on write-only drive + 0x00020152 (FATAL,HIGH) = Cannot start fifo thread + 0x00020153 (SORRY,HIGH) = Read error on fifo input + 0x00020154 (NOTE,HIGH) = Forwarded input error ends output + 0x00020155 (SORRY,HIGH) = Desired fifo buffer too large + 0x00020156 (SORRY,HIGH) = Desired fifo buffer too small + 0x00020157 (FATAL,HIGH) = burn_source is not a fifo object + 0x00020158 (DEBUG,LOW) = Reporting thread disposal precautions + 0x00020159 (DEBUG,HIGH) = TOC Format 0 returns inconsistent data + + libiso_audioxtr: + 0x00020200 (SORRY,HIGH) = Cannot open audio source file + 0x00020201 (SORRY,HIGH) = Audio source file has unsuitable format + 0x00020202 (SORRY,HIGH) = Failed to prepare reading of audio data + + + +------------------------------------------------------------------------------ +Range "vreixo" : 0x00030000 to 0x0003ffff + + 0x0003ffff (FAILURE,HIGH) = Operation canceled + 0x0003fffe (FATAL,HIGH) = Unknown or unexpected fatal error + 0x0003fffd (FAILURE,HIGH) = Unknown or unexpected error + 0x0003fffc (FATAL,HIGH) = Internal programming error + 0x0003fffb (FAILURE,HIGH) = NULL pointer where NULL not allowed + 0x0003fffa (FATAL,HIGH) = Memory allocation error + 0x0003fff9 (FATAL,HIGH) = Interrupted by a signal + 0x0003fff8 (FAILURE,HIGH) = Invalid parameter value + 0x0003fff7 (FATAL,HIGH) = Cannot create a needed thread + 0x0003fff6 (FAILURE,HIGH) = Write error + 0x0003fff5 (FAILURE,HIGH) = Buffer read error + 0x0003ffc0 (FAILURE,HIGH) = Trying to add a node already added to another dir + 0x0003ffbf (FAILURE,HIGH) = Node with same name already exist + 0x0003ffbe (FAILURE,HIGH) = Trying to remove a node that was not added to dir + 0x0003ffbd (FAILURE,HIGH) = A requested node does not exist + 0x0003ffbc (FAILURE,HIGH) = Image already bootable + 0x0003ffbb (FAILURE,HIGH) = Trying to use an invalid file as boot image + 0x0003ff80 (FAILURE,HIGH) = Error on file operation + 0x0003ff7f (FAILURE,HIGH) = Trying to open an already openned file + 0x0003ff7e (FAILURE,HIGH) = Access to file is not allowed + 0x0003ff7d (FAILURE,HIGH) = Incorrect path to file + 0x0003ff7c (FAILURE,HIGH) = The file does not exist in the filesystem + 0x0003ff7b (FAILURE,HIGH) = Trying to read or close a file not openned + 0x0003ff7a (FAILURE,HIGH) = Directory used where no dir is expected + 0x0003ff79 (FAILURE,HIGH) = File read error + 0x0003ff78 (FAILURE,HIGH) = Not dir used where a dir is expected + 0x0003ff77 (FAILURE,HIGH) = Not symlink used where a symlink is expected + 0x0003ff76 (FAILURE,HIGH) = Cannot seek to specified location + 0x0003ff75 (HINT,MEDIUM) = File not supported in ECMA-119 tree and ignored + 0x0003ff74 (HINT,MEDIUM) = File bigger than supported by used standard + 0x0003ff73 (MISHAP,HIGH) = File read error during image creation + 0x0003ff72 (HINT,MEDIUM) = Cannot convert filename to requested charset + 0x0003ff71 (SORRY,HIGH) = File cannot be added to the tree + 0x0003ff70 (HINT,MEDIUM) = File path breaks specification constraints + 0x0003ff00 (FAILURE,HIGH) = Charset conversion error + 0x0003feff (FAILURE,HIGH) = Too much files to mangle + 0x0003fec0 (FAILURE,HIGH) = Wrong or damaged Primary Volume Descriptor + 0x0003febf (SORRY,HIGH) = Wrong or damaged RR entry + 0x0003febe (SORRY,HIGH) = Unsupported RR feature + 0x0003febd (FAILURE,HIGH) = Wrong or damaged ECMA-119 + 0x0003febc (FAILURE,HIGH) = Unsupported ECMA-119 feature + 0x0003febb (SORRY,HIGH) = Wrong or damaged El-Torito catalog + 0x0003feba (SORRY,HIGH) = Unsupported El-Torito feature + 0x0003feb9 (SORRY,HIGH) = Cannot patch isolinux boot image + 0x0003feb8 (SORRY,HIGH) = Unsupported SUSP feature + 0x0003feb7 (WARNING,HIGH) = Error on a RR entry that can be ignored + 0x0003feb6 (HINT,MEDIUM) = Error on a RR entry that can be ignored + 0x0003feb5 (WARNING,HIGH) = Multiple ER SUSP entries found + 0x0003feb4 (HINT,MEDIUM) = Unsupported volume descriptor found + 0x0003feb3 (WARNING,HIGH) = El-Torito related warning + 0x0003feb2 (MISHAP,HIGH) = Image write cancelled + 0x0003feb1 (WARNING,HIGH) = El-Torito image is hidden + +Outdated codes which may not be re-used for other purposes than +re-instating them, if ever: + +X 0x00031001 (SORRY,HIGH) = Cannot read file (ignored) +X 0x00031002 (FATAL,HIGH) = Cannot read file (operation canceled) +X 0x00031000 (FATAL,HIGH) = Unsupported ISO-9660 image +X 0x00031001 (HINT,MEDIUM) = Unsupported Vol Desc that will be ignored +X 0x00031002 (FATAL,HIGH) = Damaged ISO-9660 image +X 0x00031003 (SORRY,HIGH) = Cannot read previous image file +X 0x00030101 (HINT,MEDIUM) = Unsupported SUSP entry that will be ignored +X 0x00030102 (SORRY,HIGH) = Wrong/damaged SUSP entry +X 0x00030103 (WARNING,MEDIUM)= Multiple SUSP ER entries where found +X 0x00030111 (SORRY,HIGH) = Unsupported RR feature +X 0x00030112 (SORRY,HIGH) = Error in a Rock Ridge entry +X 0x00030201 (HINT,MEDIUM) = Unsupported Boot Vol Desc that will be ignored +X 0x00030202 (SORRY,HIGH) = Wrong El-Torito catalog +X 0x00030203 (HINT,MEDIUM) = Unsupported El-Torito feature +X 0x00030204 (SORRY,HIGH) = Invalid file to be an El-Torito image +X 0x00030205 (WARNING,MEDIUM)= Cannot properly patch isolinux image +X 0x00030206 (WARNING,MEDIUM)= Copying El-Torito from a previous image without +X enought info about it +X 0x00030301 (NOTE,MEDIUM) = Unsupported file type for Joliet tree + + +------------------------------------------------------------------------------ +Range "application" : 0x00040000 to 0x0004ffff + + 0x00040000 (ABORT,HIGH) : Application supplied message + 0x00040001 (FATAL,HIGH) : Application supplied message + 0x00040002 (SORRY,HIGH) : Application supplied message + 0x00040003 (WARNING,HIGH) : Application supplied message + 0x00040004 (HINT,HIGH) : Application supplied message + 0x00040005 (NOTE,HIGH) : Application supplied message + 0x00040006 (UPDATE,HIGH) : Application supplied message + 0x00040007 (DEBUG,HIGH) : Application supplied message + 0x00040008 (*,HIGH) : Application supplied message + + +------------------------------------------------------------------------------ +Range "libisofs-xorriso" : 0x00050000 to 0x0005ffff + +This is an alternative representation of libisofs.so.6 error codes in xorriso. +If values returned by iso_error_get_code() do not fit into 0x30000 to 0x3ffff +then they get truncated to 16 bit and mapped into this range. +(This should never need to happen, of course.) + +------------------------------------------------------------------------------ +Range "libisoburn" : 0x00060000 to 0x00006ffff + + 0x00060000 (*,*) : Message which shall be attributed to libisoburn + + >>> the messages of libisoburn need to be registered individually + + +------------------------------------------------------------------------------ + +#endif /* LIDBAX_MSGS_________________ */ + + + +#ifdef LIBISO_MSGS_H_INTERNAL + + /* Internal Functions */ + + +/** Lock before doing side effect operations on m */ +static int libiso_msgs_lock(struct libiso_msgs *m, int flag); + +/** Unlock after effect operations on m are done */ +static int libiso_msgs_unlock(struct libiso_msgs *m, int flag); + + +/** Create new empty message item. + @param link Previous item in queue + @param flag Bitfield for control purposes (unused yet, submit 0) + @return >0 success, <=0 failure +*/ +static int libiso_msgs_item_new(struct libiso_msgs_item **item, + struct libiso_msgs_item *link, int flag); + +/** Destroy a message item obtained by libiso_msgs_obtain(). The submitted + pointer gets set to NULL. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return 1 for success, 0 for pointer to NULL +*/ +static int libiso_msgs_item_destroy(struct libiso_msgs_item **item, int flag); + + +#endif /* LIBISO_MSGS_H_INTERNAL */ + + +#endif /* ! LIBISO_MSGS_H_INCLUDED */ diff --git a/libisofs/libisofs.h b/libisofs/libisofs.h new file mode 100644 index 0000000..2cbc37f --- /dev/null +++ b/libisofs/libisofs.h @@ -0,0 +1,3165 @@ +/* + * Copyright (c) 2007-2008 Vreixo Formoso, Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_LIBISOFS_H_ +#define LIBISO_LIBISOFS_H_ + +#include +#include +#include + +struct burn_source; + +/** + * Context for image creation. It holds the files that will be added to image, + * and several options to control libisofs behavior. + * + * @since 0.6.2 + */ +typedef struct Iso_Image IsoImage; + +/* + * A node in the iso tree, i.e. a file that will be written to image. + * + * It can represent any kind of files. When needed, you can get the type with + * iso_node_get_type() and cast it to the appropiate subtype. Useful macros + * are provided, see below. + * + * @since 0.6.2 + */ +typedef struct Iso_Node IsoNode; + +/** + * A directory in the iso tree. It is an special type of IsoNode and can be + * casted to it in any case. + * + * @since 0.6.2 + */ +typedef struct Iso_Dir IsoDir; + +/** + * A symbolic link in the iso tree. It is an special type of IsoNode and can be + * casted to it in any case. + * + * @since 0.6.2 + */ +typedef struct Iso_Symlink IsoSymlink; + +/** + * A regular file in the iso tree. It is an special type of IsoNode and can be + * casted to it in any case. + * + * @since 0.6.2 + */ +typedef struct Iso_File IsoFile; + +/** + * An special file in the iso tree. This is used to represent any POSIX file + * other that regular files, directories or symlinks, i.e.: socket, block and + * character devices, and fifos. + * It is an special type of IsoNode and can be casted to it in any case. + * + * @since 0.6.2 + */ +typedef struct Iso_Special IsoSpecial; + +/** + * The type of an IsoNode. + * + * When an user gets an IsoNode from an image, (s)he can use + * iso_node_get_type() to get the current type of the node, and then + * cast to the appropriate subtype. For example: + * + * ... + * IsoNode *node; + * res = iso_dir_iter_next(iter, &node); + * if (res == 1 && iso_node_get_type(node) == LIBISO_DIR) { + * IsoDir *dir = (IsoDir *)node; + * ... + * } + * + * @since 0.6.2 + */ +enum IsoNodeType { + LIBISO_DIR, + LIBISO_FILE, + LIBISO_SYMLINK, + LIBISO_SPECIAL, + LIBISO_BOOT +}; + +/* macros to check node type */ +#define ISO_NODE_IS_DIR(n) (iso_node_get_type(n) == LIBISO_DIR) +#define ISO_NODE_IS_FILE(n) (iso_node_get_type(n) == LIBISO_FILE) +#define ISO_NODE_IS_SYMLINK(n) (iso_node_get_type(n) == LIBISO_SYMLINK) +#define ISO_NODE_IS_SPECIAL(n) (iso_node_get_type(n) == LIBISO_SPECIAL) +#define ISO_NODE_IS_BOOTCAT(n) (iso_node_get_type(n) == LIBISO_BOOT) + +/* macros for safe downcasting */ +#define ISO_DIR(n) ((IsoDir*)(ISO_NODE_IS_DIR(n) ? n : NULL)) +#define ISO_FILE(n) ((IsoFile*)(ISO_NODE_IS_FILE(n) ? n : NULL)) +#define ISO_SYMLINK(n) ((IsoSymlink*)(ISO_NODE_IS_SYMLINK(n) ? n : NULL)) +#define ISO_SPECIAL(n) ((IsoSpecial*)(ISO_NODE_IS_SPECIAL(n) ? n : NULL)) + +#define ISO_NODE(n) ((IsoNode*)n) + +/** + * Context for iterate on directory children. + * @see iso_dir_get_children() + * + * @since 0.6.2 + */ +typedef struct Iso_Dir_Iter IsoDirIter; + +/** + * It represents an El-Torito boot image. + * + * @since 0.6.2 + */ +typedef struct el_torito_boot_image ElToritoBootImage; + +/** + * An special type of IsoNode that acts as a placeholder for an El-Torito + * boot catalog. Once written, it will appear as a regular file. + * + * @since 0.6.2 + */ +typedef struct Iso_Boot IsoBoot; + +/** + * Flag used to hide a file in the RR/ISO or Joliet tree. + * + * @see iso_node_set_hidden + * @since 0.6.2 + */ +enum IsoHideNodeFlag { + /** Hide the node in the ECMA-119 / RR tree */ + LIBISO_HIDE_ON_RR = 1 << 0, + /** Hide the node in the Joliet tree, if Joliet extension are enabled */ + LIBISO_HIDE_ON_JOLIET = 1 << 1, + /** Hide the node in the ISO-9660:1999 tree, if that format is enabled */ + LIBISO_HIDE_ON_1999 = 1 << 2 +}; + +/** + * El-Torito bootable image type. + * + * @since 0.6.2 + */ +enum eltorito_boot_media_type { + ELTORITO_FLOPPY_EMUL, + ELTORITO_HARD_DISC_EMUL, + ELTORITO_NO_EMUL +}; + +/** + * Replace mode used when addding a node to a file. + * This controls how libisofs will act when you tried to add to a dir a file + * with the same name that an existing file. + * + * @since 0.6.2 + */ +enum iso_replace_mode { + /** + * Never replace an existing node, and instead fail with + * ISO_NODE_NAME_NOT_UNIQUE. + */ + ISO_REPLACE_NEVER, + /** + * Always replace the old node with the new. + */ + ISO_REPLACE_ALWAYS, + /** + * Replace with the new node if it is the same file type + */ + ISO_REPLACE_IF_SAME_TYPE, + /** + * Replace with the new node if it is the same file type and its ctime + * is newer than the old one. + */ + ISO_REPLACE_IF_SAME_TYPE_AND_NEWER, + /** + * Replace with the new node if its ctime is newer than the old one. + */ + ISO_REPLACE_IF_NEWER + /* + * TODO #00006 define more values + * -if both are dirs, add contents (and what to do with conflicts?) + */ +}; + +/** + * Options for image written. + * @see iso_write_opts_new() + * @since 0.6.2 + */ +typedef struct iso_write_opts IsoWriteOpts; + +/** + * Options for image reading or import. + * @see iso_read_opts_new() + * @since 0.6.2 + */ +typedef struct iso_read_opts IsoReadOpts; + +/** + * Source for image reading. + * + * @see struct iso_data_source + * @since 0.6.2 + */ +typedef struct iso_data_source IsoDataSource; + +/** + * Data source used by libisofs for reading an existing image. + * + * It offers homogeneous read access to arbitrary blocks to different sources + * for images, such as .iso files, CD/DVD drives, etc... + * + * To create a multisession image, libisofs needs a IsoDataSource, that the + * user must provide. The function iso_data_source_new_from_file() constructs + * an IsoDataSource that uses POSIX I/O functions to access data. You can use + * it with regular .iso images, and also with block devices that represent a + * drive. + * + * @since 0.6.2 + */ +struct iso_data_source +{ + + /* reserved for future usage, set to 0 */ + int version; + + /** + * Reference count for the data source. Should be 1 when a new source + * is created. Don't access it directly, but with iso_data_source_ref() + * and iso_data_source_unref() functions. + */ + unsigned int refcount; + + /** + * Opens the given source. You must open() the source before any attempt + * to read data from it. The open is the right place for grabbing the + * underlying resources. + * + * @return + * 1 if success, < 0 on error + */ + int (*open)(IsoDataSource *src); + + /** + * Close a given source, freeing all system resources previously grabbed in + * open(). + * + * @return + * 1 if success, < 0 on error + */ + int (*close)(IsoDataSource *src); + + /** + * Read an arbitrary block (2048 bytes) of data from the source. + * + * @param lba + * Block to be read. + * @param buffer + * Buffer where the data will be written. It should have at least + * 2048 bytes. + * @return + * 1 if success, < 0 on error + */ + int (*read_block)(IsoDataSource *src, uint32_t lba, uint8_t *buffer); + + /** + * Clean up the source specific data. Never call this directly, it is + * automatically called by iso_data_source_unref() when refcount reach + * 0. + */ + void (*free_data)(IsoDataSource *); + + /** Source specific data */ + void *data; +}; + +/** + * Return information for image. This is optionally allocated by libisofs, + * as a way to inform user about the features of an existing image, such as + * extensions present, size, ... + * + * @see iso_image_import() + * @since 0.6.2 + */ +typedef struct iso_read_image_features IsoReadImageFeatures; + +/** + * POSIX abstraction for source files. + * + * @see struct iso_file_source + * @since 0.6.2 + */ +typedef struct iso_file_source IsoFileSource; + +/** + * Abstract for source filesystems. + * + * @see struct iso_filesystem + * @since 0.6.2 + */ +typedef struct iso_filesystem IsoFilesystem; + +/** + * Interface that defines the operations (methods) available for an + * IsoFileSource. + * + * @see struct IsoFileSource_Iface + * @since 0.6.2 + */ +typedef struct IsoFileSource_Iface IsoFileSourceIface; + +/** + * IsoFilesystem implementation to deal with ISO images, and to offer a way to + * access specific information of the image, such as several volume attributes, + * extensions being used, El-Torito artifacts... + * + * @since 0.6.2 + */ +typedef IsoFilesystem IsoImageFilesystem; + +/** + * See IsoFilesystem->get_id() for info about this. + * @since 0.6.2 + */ +extern unsigned int iso_fs_global_id; + +/** + * An IsoFilesystem is a handler for a source of files, or a "filesystem". + * That is defined as a set of files that are organized in a hierarchical + * structure. + * + * A filesystem allows libisofs to access files from several sources in + * an homogeneous way, thus abstracting the underlying operations needed to + * access and read file contents. Note that this doesn't need to be tied + * to the disc filesystem used in the partition being accessed. For example, + * we have an IsoFilesystem implementation to access any mounted filesystem, + * using standard Linux functions. It is also legal, of course, to implement + * an IsoFilesystem to deal with a specific filesystem over raw partitions. + * That is what we do, for example, to access an ISO Image. + * + * Each file inside an IsoFilesystem is represented as an IsoFileSource object, + * that defines POSIX-like interface for accessing files. + * + * @since 0.6.2 + */ +struct iso_filesystem +{ + /** + * Type of filesystem. + * "file" -> local filesystem + * "iso " -> iso image filesystem + */ + char type[4]; + + /* reserved for future usage, set to 0 */ + int version; + + /** + * Get the root of a filesystem. + * + * @return + * 1 on success, < 0 on error + */ + int (*get_root)(IsoFilesystem *fs, IsoFileSource **root); + + /** + * Retrieve a file from its absolute path inside the filesystem. + * + * @return + * 1 success, < 0 error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + */ + int (*get_by_path)(IsoFilesystem *fs, const char *path, + IsoFileSource **file); + + /** + * Get filesystem identifier. + * + * If the filesystem is able to generate correct values of the st_dev + * and st_ino fields for the struct stat of each file, this should + * return an unique number, greater than 0. + * + * To get a identifier for your filesystem implementation you should + * use iso_fs_global_id, incrementing it by one each time. + * + * Otherwise, if you can't ensure values in the struct stat are valid, + * this should return 0. + */ + unsigned int (*get_id)(IsoFilesystem *fs); + + /** + * Opens the filesystem for several read operations. Calling this funcion + * is not needed at all, each time that the underlying system resource + * needs to be accessed, it is openned propertly. + * However, if you plan to execute several operations on the filesystem, + * it is a good idea to open it previously, to prevent several open/close + * operations to occur. + * + * @return 1 on success, < 0 on error + */ + int (*open)(IsoFilesystem *fs); + + /** + * Close the filesystem, thus freeing all system resources. You should + * call this function if you have previously open() it. + * Note that you can open()/close() a filesystem several times. + * + * @return 1 on success, < 0 on error + */ + int (*close)(IsoFilesystem *fs); + + /** + * Free implementation specific data. Should never be called by user. + * Use iso_filesystem_unref() instead. + */ + void (*free)(IsoFilesystem *fs); + + /* internal usage, do never access them directly */ + unsigned int refcount; + void *data; +}; + +/** + * Interface definition for an IsoFileSource. Defines the POSIX-like function + * to access files and abstract underlying source. + * + * @since 0.6.2 + */ +struct IsoFileSource_Iface +{ + /* reserved for future usage, set to 0 */ + int version; + + /** + * Get the path, relative to the filesystem this file source belongs to. + * + * @return + * the path of the FileSource inside the filesystem, it should be + * freed when no more needed. + */ + char* (*get_path)(IsoFileSource *src); + + /** + * Get the name of the file, with the dir component of the path. + * + * @return + * the name of the file, it should be freed when no more needed. + */ + char* (*get_name)(IsoFileSource *src); + + /** + * Get information about the file. It is equivalent to lstat(2). + * + * @return + * 1 success, < 0 error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + */ + int (*lstat)(IsoFileSource *src, struct stat *info); + + /** + * Get information about the file. If the file is a symlink, the info + * returned refers to the destination. It is equivalent to stat(2). + * + * @return + * 1 success, < 0 error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + */ + int (*stat)(IsoFileSource *src, struct stat *info); + + /** + * Check if the process has access to read file contents. Note that this + * is not necessarily related with (l)stat functions. For example, in a + * filesystem implementation to deal with an ISO image, if the user has + * read access to the image it will be able to read all files inside it, + * despite of the particular permission of each file in the RR tree, that + * are what the above functions return. + * + * @return + * 1 if process has read access, < 0 on error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + */ + int (*access)(IsoFileSource *src); + + /** + * Opens the source. + * @return 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ALREADY_OPENNED + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + */ + int (*open)(IsoFileSource *src); + + /** + * Close a previuously openned file + * @return 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + */ + int (*close)(IsoFileSource *src); + + /** + * Attempts to read up to count bytes from the given source into + * the buffer starting at buf. + * + * The file src must be open() before calling this, and close() when no + * more needed. Not valid for dirs. On symlinks it reads the destination + * file. + * + * @return + * number of bytes read, 0 if EOF, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * ISO_WRONG_ARG_VALUE -> if count == 0 + * ISO_FILE_IS_DIR + * ISO_OUT_OF_MEM + * ISO_INTERRUPTED + */ + int (*read)(IsoFileSource *src, void *buf, size_t count); + + /** + * Read a directory. + * + * Each call to this function will return a new children, until we reach + * the end of file (i.e, no more children), in that case it returns 0. + * + * The dir must be open() before calling this, and close() when no more + * needed. Only valid for dirs. + * + * Note that "." and ".." children MUST NOT BE returned. + * + * @param child + * pointer to be filled with the given child. Undefined on error or OEF + * @return + * 1 on success, 0 if EOF (no more children), < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * ISO_FILE_IS_NOT_DIR + * ISO_OUT_OF_MEM + */ + int (*readdir)(IsoFileSource *src, IsoFileSource **child); + + /** + * Read the destination of a symlink. You don't need to open the file + * to call this. + * + * @param buf + * allocated buffer of at least bufsiz bytes. + * The dest. will be copied there, and it will be NULL-terminated + * @param bufsiz + * characters to be copied. Destination link will be truncated if + * it is larger than given size. This include the \0 character. + * @return + * 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_WRONG_ARG_VALUE -> if bufsiz <= 0 + * ISO_FILE_IS_NOT_SYMLINK + * ISO_OUT_OF_MEM + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * + */ + int (*readlink)(IsoFileSource *src, char *buf, size_t bufsiz); + + /** + * Get the filesystem for this source. No extra ref is added, so you + * musn't unref the IsoFilesystem. + * + * @return + * The filesystem, NULL on error + */ + IsoFilesystem* (*get_filesystem)(IsoFileSource *src); + + /** + * Free implementation specific data. Should never be called by user. + * Use iso_file_source_unref() instead. + */ + void (*free)(IsoFileSource *src); + + /* + * TODO #00004 Add a get_mime_type() function. + * This can be useful for GUI apps, to choose the icon of the file + */ +}; + +/** + * An IsoFile Source is a POSIX abstraction of a file. + * + * @since 0.6.2 + */ +struct iso_file_source +{ + const IsoFileSourceIface *class; + int refcount; + void *data; +}; + +/** + * Initialize libisofs. You must call this before any usage of the library. + * @return 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_init(); + +/** + * Finalize libisofs. + * + * @since 0.6.2 + */ +void iso_finish(); + +/** + * Create a new image, empty. + * + * The image will be owned by you and should be unref() when no more needed. + * + * @param name + * Name of the image. This will be used as volset_id and volume_id. + * @param image + * Location where the image pointer will be stored. + * @return + * 1 sucess, < 0 error + * + * @since 0.6.2 + */ +int iso_image_new(const char *name, IsoImage **image); + + +/** + * The following two functions three macros are utilities to help ensuring + * version match of application, compile time header, and runtime library. + */ +/** + * Get version of the libisofs library at runtime. + * + * @since 0.6.2 + */ +void iso_lib_version(int *major, int *minor, int *micro); + +/** + * Check at runtime if the library is ABI compatible with the given version. + * + * @return + * 1 lib is compatible, 0 is not. + * + * @since 0.6.2 + */ +int iso_lib_is_compatible(int major, int minor, int micro); + + +/** + * These three release version numbers tell the revision of this header file + * and of the API it describes. They are memorized by applications at + * compile time. + * They must show the same values as these symbols in ./configure.ac + * LIBISOFS_MAJOR_VERSION=... + * LIBISOFS_MINOR_VERSION=... + * LIBISOFS_MICRO_VERSION=... + * Note to anybody who does own work inside libisofs: + * Any change of configure.ac or libisofs.h has to keep up this equality ! + * + * Before usage of these macros on your code, please read the usage discussion + * below. + * + * @since 0.6.2 + */ +#define iso_lib_header_version_major 0 +#define iso_lib_header_version_minor 6 +#define iso_lib_header_version_micro 3 + +/** + * Usage discussion: + * + * Some developers of the libburnia project have differing opinions how to + * ensure the compatibility of libaries and applications. + * + * It is about whether to use at compile time and at runtime the version + * numbers provided here. Thomas Schmitt advises to use them. Vreixo Formoso + * advises to use other means. + * + * At compile time: + * + * Vreixo Formoso advises to leave proper version matching to properly + * programmed checks in the the application's build system, which will + * eventually refuse compilation. + * + * Thomas Schmitt advises to use the macros defined here for comparison with + * the application's requirements of library revisions and to eventually + * break compilation. + * + * Both advises are combinable. I.e. be master of your build system and have + * #if checks in the source code of your application, nevertheless. + * + * At runtime (via iso_lib_is_compatible()): + * + * Vreixo Formoso advises to compare the application's requirements of + * library revisions with the runtime library. This is to allow runtime + * libraries which are young enough for the application but too old for + * the lib*.h files seen at compile time. + * + * Thomas Schmitt advises to compare the header revisions defined here with + * the runtime library. This is to enforce a strictly monotonous chain of + * revisions from app to header to library, at the cost of excluding some older + * libraries. + * + * These two advises are mutually exclusive. + */ + + +/** + * Creates an IsoWriteOpts for writing an image. You should set the options + * desired with the correspondent setters. + * + * Options by default are determined by the selected profile. Fifo size is set + * by default to 2 MB. + * + * @param opts + * Pointer to the location where the newly created IsoWriteOpts will be + * stored. You should free it with iso_write_opts_free() when no more + * needed. + * @param profile + * Default profile for image creation. For now the following values are + * defined: + * ---> 0 [BASIC] + * No extensions are enabled, and ISO level is set to 1. Only suitable + * for usage for very old and limited systems (like MS-DOS), or by a + * start point from which to set your custom options. + * ---> 1 [BACKUP] + * POSIX compatibility for backup. Simple settings, ISO level is set to + * 2 and RR extensions are enabled. Useful for backup purposes. + * ---> 2 [DISTRIBUTION] + * Setting for information distribution. Both RR and Joliet are enabled + * to maximize compatibility with most systems. Permissions are set to + * default values, and timestamps to the time of recording. + * @return + * 1 success, < 0 error + * + * @since 0.6.2 + */ +int iso_write_opts_new(IsoWriteOpts **opts, int profile); + +/** + * Free an IsoWriteOpts previously allocated with iso_write_opts_new(). + * + * @since 0.6.2 + */ +void iso_write_opts_free(IsoWriteOpts *opts); + +/** + * Set the ISO-9960 level to write at. + * + * @param level + * -> 1 for higher compatibility with old systems. With this level + * filenames are restricted to 8.3 characters. + * -> 2 to allow up to 31 filename characters. + * @return + * 1 success, < 0 error + * + * @since 0.6.2 + */ +int iso_write_opts_set_iso_level(IsoWriteOpts *opts, int level); + +/** + * Whether to use or not Rock Ridge extensions. + * + * This are standard extensions to ECMA-119, intended to add POSIX filesystem + * features to ECMA-119 images. Thus, usage of this flag is highly recommended + * for images used on GNU/Linux systems. With the usage of RR extension, the + * resulting image will have long filenames (up to 255 characters), deeper + * directory structure, POSIX permissions and owner info on files and + * directories, support for symbolic links or special files... All that + * attributes can be modified/setted with the appropiate function. + * + * @param enable + * 1 to enable RR extension, 0 to not add them + * @return + * 1 success, < 0 error + * + * @since 0.6.2 + */ +int iso_write_opts_set_rockridge(IsoWriteOpts *opts, int enable); + +/** + * Whether to add the non-standard Joliet extension to the image. + * + * This extensions are heavily used in Microsoft Windows systems, so if you + * plan to use your disc on such a system you should add this extension. + * Usage of Joliet supplies longer filesystem length (up to 64 unicode + * characters), and deeper directory structure. + * + * @param enable + * 1 to enable Joliet extension, 0 to not add them + * @return + * 1 success, < 0 error + * + * @since 0.6.2 + */ +int iso_write_opts_set_joliet(IsoWriteOpts *opts, int enable); + +/** + * Whether to use newer ISO-9660:1999 version. + * + * This is the second version of ISO-9660. It allows longer filenames and has + * less restrictions than old ISO-9660. However, nobody is using it so there + * are no much reasons to enable this. + * + * @since 0.6.2 + */ +int iso_write_opts_set_iso1999(IsoWriteOpts *opts, int enable); + +/** + * Omit the version number (";1") at the end of the ISO-9660 identifiers. + * This breaks ECMA-119 specification, but version numbers are usually not + * used, so it should work on most systems. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_omit_version_numbers(IsoWriteOpts *opts, int omit); + +/** + * Allow ISO-9660 directory hierarchy to be deeper than 8 levels. + * This breaks ECMA-119 specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_allow_deep_paths(IsoWriteOpts *opts, int allow); + +/** + * Allow path in the ISO-9660 tree to have more than 255 characters. + * This breaks ECMA-119 specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_allow_longer_paths(IsoWriteOpts *opts, int allow); + +/** + * Allow a single file or directory hierarchy to have up to 37 characters. + * This is larger than the 31 characters allowed by ISO level 2, and the + * extra space is taken from the version number, so this also forces + * omit_version_numbers. + * This breaks ECMA-119 specification and could lead to buffer overflow + * problems on old systems. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_max_37_char_filenames(IsoWriteOpts *opts, int allow); + +/** + * ISO-9660 forces filenames to have a ".", that separates file name from + * extension. libisofs adds it if original filename doesn't has one. Set + * this to 1 to prevent this behavior. + * This breaks ECMA-119 specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_no_force_dots(IsoWriteOpts *opts, int no); + +/** + * Allow lowercase characters in ISO-9660 filenames. By default, only + * uppercase characters, numbers and a few other characters are allowed. + * This breaks ECMA-119 specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_allow_lowercase(IsoWriteOpts *opts, int allow); + +/** + * Allow all ASCII characters to be appear on an ISO-9660 filename. Note + * that "/" and "\0" characters are never allowed, even in RR names. + * This breaks ECMA-119 specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_allow_full_ascii(IsoWriteOpts *opts, int allow); + +/** + * Allow all characters to be part of Volume and Volset identifiers on + * the Primary Volume Descriptor. This breaks ISO-9660 contraints, but + * should work on modern systems. + * + * @since 0.6.2 + */ +int iso_write_opts_set_relaxed_vol_atts(IsoWriteOpts *opts, int allow); + +/** + * Allow paths in the Joliet tree to have more than 240 characters. + * This breaks Joliet specification. Use with caution. + * + * @since 0.6.2 + */ +int iso_write_opts_set_joliet_longer_paths(IsoWriteOpts *opts, int allow); + +/** + * Whether to sort files based on their weight. + * + * @see iso_node_set_sort_weight + * @since 0.6.2 + */ +int iso_write_opts_set_sort_files(IsoWriteOpts *opts, int sort); + +/** + * Whether to set default values for files and directory permissions, gid and + * uid. All these take one of three values: 0, 1 or 2. + * + * If 0, the corresponding attribute will be kept as setted in the IsoNode. + * Unless you have changed it, it corresponds to the value on disc, so it + * is suitable for backup purposes. If set to 1, the corresponding attrib. + * will be changed by a default suitable value. Finally, if you set it to + * 2, the attrib. will be changed with the value specified by the functioins + * below. Note that for mode attributes, only the permissions are set, the + * file type remains unchanged. + * + * @see iso_write_opts_set_default_dir_mode + * @see iso_write_opts_set_default_file_mode + * @see iso_write_opts_set_default_uid + * @see iso_write_opts_set_default_gid + * @since 0.6.2 + */ +int iso_write_opts_set_replace_mode(IsoWriteOpts *opts, int dir_mode, + int file_mode, int uid, int gid); + +/** + * Set the mode to use on dirs when you set the replace_mode of dirs to 2. + * + * @see iso_write_opts_set_replace_mode + * @since 0.6.2 + */ +int iso_write_opts_set_default_dir_mode(IsoWriteOpts *opts, mode_t dir_mode); + +/** + * Set the mode to use on files when you set the replace_mode of files to 2. + * + * @see iso_write_opts_set_replace_mode + * @since 0.6.2 + */ +int iso_write_opts_set_default_file_mode(IsoWriteOpts *opts, mode_t file_mode); + +/** + * Set the uid to use when you set the replace_uid to 2. + * + * @see iso_write_opts_set_replace_mode + * @since 0.6.2 + */ +int iso_write_opts_set_default_uid(IsoWriteOpts *opts, uid_t uid); + +/** + * Set the gid to use when you set the replace_gid to 2. + * + * @see iso_write_opts_set_replace_mode + * @since 0.6.2 + */ +int iso_write_opts_set_default_gid(IsoWriteOpts *opts, gid_t gid); + +/** + * 0 to use IsoNode timestamps, 1 to use recording time, 2 to use + * values from timestamp field. This has only meaning if RR extensions + * are enabled. + * + * @see iso_write_opts_set_default_timestamp + * @since 0.6.2 + */ +int iso_write_opts_set_replace_timestamps(IsoWriteOpts *opts, int replace); + +/** + * Set the timestamp to use when you set the replace_timestamps to 2. + * + * @see iso_write_opts_set_replace_timestamps + * @since 0.6.2 + */ +int iso_write_opts_set_default_timestamp(IsoWriteOpts *opts, time_t timestamp); + +/** + * Whether to always record timestamps in GMT. + * + * By default, libisofs stores local time information on image. You can set + * this to always store timestamps in GMT. This is useful if you want to hide + * your timezone, or you live in a timezone that can't be represented in + * ECMA-119. These are timezones whose offset from GMT is greater than +13 + * hours, lower than -12 hours, or not a multiple of 15 minutes. + * + * @since 0.6.2 + */ +int iso_write_opts_set_always_gmt(IsoWriteOpts *opts, int gmt); + +/** + * Set the charset to use for the RR names of the files that will be created + * on the image. + * NULL to use default charset, that is the locale charset. + * You can obtain the list of charsets supported on your system executing + * "iconv -l" in a shell. + * + * @since 0.6.2 + */ +int iso_write_opts_set_output_charset(IsoWriteOpts *opts, const char *charset); + +/** + * Set the type of the image to create. Libisofs support two kind of images: + * stand-alone and appendable. + * + * A stand-alone image is an image that is valid alone, and that can be + * mounted by its own. This is the kind of image you will want to create + * in most cases. A stand-alone image can be burned in an empty CD or DVD, + * or write to an .iso file for future burning or distribution. + * + * On the other side, an appendable image is not self contained, it refers + * to serveral files that are stored outside the image. Its usage is for + * multisession discs, where you add data in a new session, while the + * previous session data can still be accessed. In those cases, the old + * data is not written again. Instead, the new image refers to it, and thus + * it's only valid when appended to the original. Note that in those cases + * the image will be written after the original, and thus you will want + * to use a ms_block greater than 0. + * + * Note that if you haven't import a previous image (by means of + * iso_image_import()), the image will always be a stand-alone image, as + * there is no previous data to refer to. + * + * @param appendable + * 1 to create an appendable image, 0 for an stand-alone one. + * + * @since 0.6.2 + */ +int iso_write_opts_set_appendable(IsoWriteOpts *opts, int appendable); + +/** + * Set the start block of the image. It is supposed to be the lba where the + * first block of the image will be written on disc. All references inside the + * ISO image will take this into account, thus providing a mountable image. + * + * For appendable images, that are written to a new session, you should + * pass here the lba of the next writable address on disc. + * + * In stand alone images this is usually 0. However, you may want to + * provide a different ms_block if you don't plan to burn the image in the + * first session on disc, such as in some CD-Extra disc whether the data + * image is written in a new session after some audio tracks. + * + * @since 0.6.2 + */ +int iso_write_opts_set_ms_block(IsoWriteOpts *opts, uint32_t ms_block); + +/** + * Sets the buffer where to store the descriptors that need to be written + * at the beginning of a overwriteable media to grow the image. + * + * @param overwrite + * When not NULL, it should point to a buffer of at least 64KiB, where + * libisofs will write the contents that should be written at the + * beginning of a overwriteable media, to grow the image. The growing + * of an image is a way, used by first time in growisofs by Andy Polyakov, + * to allow the appending of new data to non-multisession media, such + * as DVD+RW, in the same way you append a new session to a multisession + * disc, i.e., without need to write again the contents of the previous + * image. + * + * Note that if you want this kind of image growing, you will also need to + * set appendable to "1" and provide a valid ms_block after the previous + * image. + * + * You should initialize the buffer either with 0s, or with the contents of + * the first blocks of the image you're growing. In most cases, 0 is good + * enought. + * + * If you don't need this information, for example because you're creating a + * new image from scratch of because you will create an image for a true + * multisession media, just don't set this buffer or set it to NULL. + * + * @since 0.6.2 + */ +int iso_write_opts_set_overwrite_buf(IsoWriteOpts *opts, uint8_t *overwrite); + +/** + * Set the size, in number of blocks, of the FIFO buffer used between the + * writer thread and the burn_source. You have to provide at least a 32 + * blocks buffer. Default value is set to 2MB, if that is ok for you, you + * don't need to call this function. + * + * @since 0.6.2 + */ +int iso_write_opts_set_fifo_size(IsoWriteOpts *opts, size_t fifo_size); + +/** + * Create a burn_source to actually write the image. That burn_source can be + * used with libburn as a data source for a track. + * + * @param image + * The image to write. + * @param opts + * The options for image generation. All needed data will be copied, so + * you can free the given struct once this function returns. + * @param burn_src + * Location where the pointer to the burn_source will be stored + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_image_create_burn_source(IsoImage *image, IsoWriteOpts *opts, + struct burn_source **burn_src); + +/** + * Creates an IsoReadOpts for reading an existent image. You should set the + * options desired with the correspondent setters. Note that you may want to + * set the start block value. + * + * Options by default are determined by the selected profile. + * + * @param opts + * Pointer to the location where the newly created IsoReadOpts will be + * stored. You should free it with iso_read_opts_free() when no more + * needed. + * @param profile + * Default profile for image reading. For now the following values are + * defined: + * ---> 0 [STANDARD] + * Suitable for most situations. All extension are read. When both + * Joliet and RR extension are present, RR is used. + * @return + * 1 success, < 0 error + * + * @since 0.6.2 + */ +int iso_read_opts_new(IsoReadOpts **opts, int profile); + +/** + * Free an IsoReadOpts previously allocated with iso_read_opts_new(). + * + * @since 0.6.2 + */ +void iso_read_opts_free(IsoReadOpts *opts); + +/** + * Set the block where the image begins. It is usually 0, but may be different + * on a multisession disc. + * + * @since 0.6.2 + */ +int iso_read_opts_set_start_block(IsoReadOpts *opts, uint32_t block); + +/** + * Do not read Rock Ridge extensions. + * In most cases you don't want to use this. It could be useful if RR info + * is damaged, or if you want to use the Joliet tree. + * + * @since 0.6.2 + */ +int iso_read_opts_set_no_rockridge(IsoReadOpts *opts, int norr); + +/** + * Do not read Joliet extensions. + * + * @since 0.6.2 + */ +int iso_read_opts_set_no_joliet(IsoReadOpts *opts, int nojoliet); + +/** + * Do not read ISO 9660:1999 enhanced tree + * + * @since 0.6.2 + */ +int iso_read_opts_set_no_iso1999(IsoReadOpts *opts, int noiso1999); + +/** + * Whether to prefer Joliet over RR. libisofs usually prefers RR over + * Joliet, as it give us much more info about files. So, if both extensions + * are present, RR is used. You can set this if you prefer Joliet, but + * note that this is not very recommended. This doesn't mean than RR + * extensions are not read: if no Joliet is present, libisofs will read + * RR tree. + * + * @since 0.6.2 + */ +int iso_read_opts_set_preferjoliet(IsoReadOpts *opts, int preferjoliet); + +/** + * Set default uid for files when RR extensions are not present. + * + * @since 0.6.2 + */ +int iso_read_opts_set_default_uid(IsoReadOpts *opts, uid_t uid); + +/** + * Set default gid for files when RR extensions are not present. + * + * @since 0.6.2 + */ +int iso_read_opts_set_default_gid(IsoReadOpts *opts, gid_t gid); + +/** + * Set default permissions for files when RR extensions are not present. + * + * @param file_perm + * Permissions for files. + * @param dir_perm + * Permissions for directories. + * + * @since 0.6.2 + */ +int iso_read_opts_set_default_permissions(IsoReadOpts *opts, mode_t file_perm, + mode_t dir_perm); + +/** + * Set the input charset of the file names on the image. NULL to use locale + * charset. You have to specify a charset if the image filenames are encoded + * in a charset different that the local one. This could happen, for example, + * if the image was created on a system with different charset. + * + * @param charset + * The charset to use as input charset. You can obtain the list of + * charsets supported on your system executing "iconv -l" in a shell. + * + * @since 0.6.2 + */ +int iso_read_opts_set_input_charset(IsoReadOpts *opts, const char *charset); + +/** + * Import a previous session or image, for growing or modify. + * + * @param image + * The image context to which old image will be imported. Note that all + * files added to image, and image attributes, will be replaced with the + * contents of the old image. + * TODO #00025 support for merging old image files + * @param src + * Data Source from which old image will be read. A extra reference is + * added, so you still need to iso_data_source_unref() yours. + * @param opts + * Options for image import. All needed data will be copied, so you + * can free the given struct once this function returns. + * @param features + * If not NULL, a new IsoReadImageFeatures will be allocated and filled + * with the features of the old image. It should be freed with + * iso_read_image_features_destroy() when no more needed. You can pass + * NULL if you're not interested on them. + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_image_import(IsoImage *image, IsoDataSource *src, IsoReadOpts *opts, + IsoReadImageFeatures **features); + +/** + * Destroy an IsoReadImageFeatures object obtained with iso_image_import. + * + * @since 0.6.2 + */ +void iso_read_image_features_destroy(IsoReadImageFeatures *f); + +/** + * Get the size (in 2048 byte block) of the image, as reported in the PVM. + * + * @since 0.6.2 + */ +uint32_t iso_read_image_features_get_size(IsoReadImageFeatures *f); + +/** + * Whether RockRidge extensions are present in the image imported. + * + * @since 0.6.2 + */ +int iso_read_image_features_has_rockridge(IsoReadImageFeatures *f); + +/** + * Whether Joliet extensions are present in the image imported. + * + * @since 0.6.2 + */ +int iso_read_image_features_has_joliet(IsoReadImageFeatures *f); + +/** + * Whether the image is recorded according to ISO 9660:1999, i.e. it has + * a version 2 Enhanced Volume Descriptor. + * + * @since 0.6.2 + */ +int iso_read_image_features_has_iso1999(IsoReadImageFeatures *f); + +/** + * Whether El-Torito boot record is present present in the image imported. + * + * @since 0.6.2 + */ +int iso_read_image_features_has_eltorito(IsoReadImageFeatures *f); + +/** + * Increments the reference counting of the given image. + * + * @since 0.6.2 + */ +void iso_image_ref(IsoImage *image); + +/** + * Decrements the reference couting of the given image. + * If it reaches 0, the image is free, together with its tree nodes (whether + * their refcount reach 0 too, of course). + * + * @since 0.6.2 + */ +void iso_image_unref(IsoImage *image); + +/** + * Attach user defined data to the image. Use this if your application needs + * to store addition info together with the IsoImage. If the image already + * has data attached, the old data will be freed. + * + * @param data + * Pointer to application defined data that will be attached to the + * image. You can pass NULL to remove any already attached data. + * @param give_up + * Function that will be called when the image does not need the data + * any more. It receives the data pointer as an argumente, and eventually + * causes data to be freed. It can be NULL if you don't need it. + * @return + * 1 on succes, < 0 on error + * + * @since 0.6.2 + */ +int iso_image_attach_data(IsoImage *image, void *data, void (*give_up)(void*)); + +/** + * The the data previously attached with iso_image_attach_data() + * + * @since 0.6.2 + */ +void *iso_image_get_attached_data(IsoImage *image); + +/** + * Get the root directory of the image. + * No extra ref is added to it, so you musn't unref it. Use iso_node_ref() + * if you want to get your own reference. + * + * @since 0.6.2 + */ +IsoDir *iso_image_get_root(const IsoImage *image); + +/** + * Fill in the volset identifier for a image. + * + * @since 0.6.2 + */ +void iso_image_set_volset_id(IsoImage *image, const char *volset_id); + +/** + * Get the volset identifier. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_volset_id(const IsoImage *image); + +/** + * Fill in the volume identifier for a image. + * + * @since 0.6.2 + */ +void iso_image_set_volume_id(IsoImage *image, const char *volume_id); + +/** + * Get the volume identifier. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_volume_id(const IsoImage *image); + +/** + * Fill in the publisher for a image. + * + * @since 0.6.2 + */ +void iso_image_set_publisher_id(IsoImage *image, const char *publisher_id); + +/** + * Get the publisher of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_publisher_id(const IsoImage *image); + +/** + * Fill in the data preparer for a image. + * + * @since 0.6.2 + */ +void iso_image_set_data_preparer_id(IsoImage *image, + const char *data_preparer_id); + +/** + * Get the data preparer of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_data_preparer_id(const IsoImage *image); + +/** + * Fill in the system id for a image. Up to 32 characters. + * + * @since 0.6.2 + */ +void iso_image_set_system_id(IsoImage *image, const char *system_id); + +/** + * Get the system id of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_system_id(const IsoImage *image); + +/** + * Fill in the application id for a image. Up to 128 chars. + * + * @since 0.6.2 + */ +void iso_image_set_application_id(IsoImage *image, const char *application_id); + +/** + * Get the application id of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_application_id(const IsoImage *image); + +/** + * Fill copyright information for the image. Usually this refers + * to a file on disc. Up to 37 characters. + * + * @since 0.6.2 + */ +void iso_image_set_copyright_file_id(IsoImage *image, + const char *copyright_file_id); + +/** + * Get the copyright information of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_copyright_file_id(const IsoImage *image); + +/** + * Fill abstract information for the image. Usually this refers + * to a file on disc. Up to 37 characters. + * + * @since 0.6.2 + */ +void iso_image_set_abstract_file_id(IsoImage *image, + const char *abstract_file_id); + +/** + * Get the abstract information of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_abstract_file_id(const IsoImage *image); + +/** + * Fill biblio information for the image. Usually this refers + * to a file on disc. Up to 37 characters. + * + * @since 0.6.2 + */ +void iso_image_set_biblio_file_id(IsoImage *image, const char *biblio_file_id); + +/** + * Get the biblio information of a image. + * The returned string is owned by the image and should not be freed nor + * changed. + * + * @since 0.6.2 + */ +const char *iso_image_get_biblio_file_id(const IsoImage *image); + +/** + * Create a bootable image by adding a El-Torito boot image. + * + * This also add a catalog boot node to the image filesystem tree. + * + * @param image + * The image to make bootable. If it was already bootable this function + * returns an error and the image remains unmodified. + * @param image_path + * The path on the image tree of a regular file to use as default boot + * image. + * @param type + * The boot media type. This can be one of 3 types: + * - Floppy emulation: Boot image file must be exactly + * 1200 kB, 1440 kB or 2880 kB. + * - Hard disc emulation: The image must begin with a master + * boot record with a single image. + * - No emulation. You should specify load segment and load size + * of image. + * @param catalog_path + * The path on the image tree where the catalog will be stored. The + * directory component of this path must be a directory existent on the + * image tree, and the filename component must be unique among all + * children of that directory on image. Otherwise a correspodent error + * code will be returned. This function will add an IsoBoot node that acts + * as a placeholder for the real catalog, that will be generated at image + * creation time. + * @param boot + * Location where a pointer to the added boot image will be stored. That + * object is owned by the IsoImage and should not be freed by the user, + * nor dereferenced once the last reference to the IsoImage was disposed + * via iso_image_unref(). A NULL value is allowed if you don't need a + * reference to the boot image. + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_image_set_boot_image(IsoImage *image, const char *image_path, + enum eltorito_boot_media_type type, + const char *catalog_path, + ElToritoBootImage **boot); + +/* TODO #00026 : add support for "hidden" bootable images. */ + +/** + * Get El-Torito boot image of an ISO image, if any. + * + * This can be useful, for example, to check if a volume read from a previous + * session or an existing image is bootable. It can also be useful to get + * the image and catalog tree nodes. An application would want those, for + * example, to prevent the user removing it. + * + * Both nodes are owned by libisofs and should not be freed. You can get your + * own ref with iso_node_ref(). You can can also check if the node is already + * on the tree by getting its parent (note that when reading El-Torito info + * from a previous image, the nodes might not be on the tree even if you haven't + * removed them). Remember that you'll need to get a new ref + * (with iso_node_ref()) before inserting them again to the tree, and probably + * you will also need to set the name or permissions. + * + * @param image + * The image from which to get the boot image. + * @param boot + * If not NULL, it will be filled with a pointer to the boot image, if + * any. That object is owned by the IsoImage and should not be freed by + * the user, nor dereferenced once the last reference to the IsoImage was + * disposed via iso_image_unref(). + * @param imgnode + * When not NULL, it will be filled with the image tree node. No extra ref + * is added, you can use iso_node_ref() to get one if you need it. + * @param catnode + * When not NULL, it will be filled with the catnode tree node. No extra + * ref is added, you can use iso_node_ref() to get one if you need it. + * @return + * 1 on success, 0 is the image is not bootable (i.e., it has no El-Torito + * image), < 0 error. + * + * @since 0.6.2 + */ +int iso_image_get_boot_image(IsoImage *image, ElToritoBootImage **boot, + IsoFile **imgnode, IsoBoot **catnode); + +/** + * Removes the El-Torito bootable image. + * + * The IsoBoot node that acts as placeholder for the catalog is also removed + * for the image tree, if there. + * If the image is not bootable (don't have el-torito boot image) this function + * just returns. + * + * @since 0.6.2 + */ +void iso_image_remove_boot_image(IsoImage *image); + +/** + * Sets the load segment for the initial boot image. This is only for + * no emulation boot images, and is a NOP for other image types. + * + * @since 0.6.2 + */ +void el_torito_set_load_seg(ElToritoBootImage *bootimg, short segment); + +/** + * Sets the number of sectors (512b) to be load at load segment during + * the initial boot procedure. This is only for + * no emulation boot images, and is a NOP for other image types. + * + * @since 0.6.2 + */ +void el_torito_set_load_size(ElToritoBootImage *bootimg, short sectors); + +/** + * Marks the specified boot image as not bootable + * + * @since 0.6.2 + */ +void el_torito_set_no_bootable(ElToritoBootImage *bootimg); + +/** + * Specifies that this image needs to be patched. This involves the writting + * of a 56 bytes boot information table at offset 8 of the boot image file. + * The original boot image file won't be modified. + * This is needed for isolinux boot images. + * + * @since 0.6.2 + */ +void el_torito_patch_isolinux_image(ElToritoBootImage *bootimg); + +/** + * Increments the reference counting of the given node. + * + * @since 0.6.2 + */ +void iso_node_ref(IsoNode *node); + +/** + * Decrements the reference couting of the given node. + * If it reach 0, the node is free, and, if the node is a directory, + * its children will be unref() too. + * + * @since 0.6.2 + */ +void iso_node_unref(IsoNode *node); + +/** + * Get the type of an IsoNode. + * + * @since 0.6.2 + */ +enum IsoNodeType iso_node_get_type(IsoNode *node); + +/** + * Set the name of a node. Note that if the node is already added to a dir + * this can fail if dir already contains a node with the new name. + * + * @param node + * The node whose name you want to change. Note that you can't change + * the name of the root. + * @param name + * The name for the node. If you supply an empty string or a + * name greater than 255 characters this returns with failure, and + * node name is not modified. + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_node_set_name(IsoNode *node, const char *name); + +/** + * Get the name of a node. + * The returned string belongs to the node and should not be modified nor + * freed. Use strdup if you really need your own copy. + * + * @since 0.6.2 + */ +const char *iso_node_get_name(const IsoNode *node); + +/** + * Set the permissions for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + * + * @param mode + * bitmask with the permissions of the node, as specified in 'man 2 stat'. + * The file type bitfields will be ignored, only file permissions will be + * modified. + * + * @since 0.6.2 + */ +void iso_node_set_permissions(IsoNode *node, mode_t mode); + +/** + * Get the permissions for the node + * + * @since 0.6.2 + */ +mode_t iso_node_get_permissions(const IsoNode *node); + +/** + * Get the mode of the node, both permissions and file type, as specified in + * 'man 2 stat'. + * + * @since 0.6.2 + */ +mode_t iso_node_get_mode(const IsoNode *node); + +/** + * Set the user id for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + * + * @since 0.6.2 + */ +void iso_node_set_uid(IsoNode *node, uid_t uid); + +/** + * Get the user id of the node. + * + * @since 0.6.2 + */ +uid_t iso_node_get_uid(const IsoNode *node); + +/** + * Set the group id for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + * + * @since 0.6.2 + */ +void iso_node_set_gid(IsoNode *node, gid_t gid); + +/** + * Get the group id of the node. + * + * @since 0.6.2 + */ +gid_t iso_node_get_gid(const IsoNode *node); + +/** + * Set the time of last modification of the file + * + * @since 0.6.2 + */ +void iso_node_set_mtime(IsoNode *node, time_t time); + +/** + * Get the time of last modification of the file + * + * @since 0.6.2 + */ +time_t iso_node_get_mtime(const IsoNode *node); + +/** + * Set the time of last access to the file + * + * @since 0.6.2 + */ +void iso_node_set_atime(IsoNode *node, time_t time); + +/** + * Get the time of last access to the file + * + * @since 0.6.2 + */ +time_t iso_node_get_atime(const IsoNode *node); + +/** + * Set the time of last status change of the file + * + * @since 0.6.2 + */ +void iso_node_set_ctime(IsoNode *node, time_t time); + +/** + * Get the time of last status change of the file + * + * @since 0.6.2 + */ +time_t iso_node_get_ctime(const IsoNode *node); + +/** + * Set if the node will be hidden in RR/ISO tree, Joliet tree or both. + * + * If the file is setted as hidden in one tree, it won't be included there, so + * it won't be visible in a OS accessing CD using that tree. For example, + * GNU/Linux systems access to Rock Ridge / ISO9960 tree in order to see + * what is recorded on CD, while MS Windows make use of the Joliet tree. If a + * file is hidden only in Joliet, it won't be visible in Windows systems, + * while still visible in Linux. + * + * If a file is hidden in both trees, it won't be written to image. + * + * @param node + * The node that is to be hidden. + * @param hide_attrs + * IsoHideNodeFlag's to set the trees in which file will be hidden. + * + * @since 0.6.2 + */ +void iso_node_set_hidden(IsoNode *node, int hide_attrs); + +/** + * Add a new node to a dir. Note that this function don't add a new ref to + * the node, so you don't need to free it, it will be automatically freed + * when the dir is deleted. Of course, if you want to keep using the node + * after the dir life, you need to iso_node_ref() it. + * + * @param dir + * the dir where to add the node + * @param child + * the node to add. You must ensure that the node hasn't previously added + * to other dir, and that the node name is unique inside the child. + * Otherwise this function will return a failure, and the child won't be + * inserted. + * @param replace + * if the dir already contains a node with the same name, whether to + * replace or not the old node with this. + * @return + * number of nodes in dir if succes, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if dir or child are NULL + * ISO_NODE_ALREADY_ADDED, if child is already added to other dir + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_WRONG_ARG_VALUE, if child == dir, or replace != (0,1) + * + * @since 0.6.2 + */ +int iso_dir_add_node(IsoDir *dir, IsoNode *child, + enum iso_replace_mode replace); + +/** + * Locate a node inside a given dir. + * + * @param dir + * The dir where to look for the node. + * @param name + * The name of the node + * @param node + * Location for a pointer to the node, it will filled with NULL if the dir + * doesn't have a child with the given name. + * The node will be owned by the dir and shouldn't be unref(). Just call + * iso_node_ref() to get your own reference to the node. + * Note that you can pass NULL is the only thing you want to do is check + * if a node with such name already exists on dir. + * @return + * 1 node found, 0 child has no such node, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if dir or name are NULL + * + * @since 0.6.2 + */ +int iso_dir_get_node(IsoDir *dir, const char *name, IsoNode **node); + +/** + * Get the number of children of a directory. + * + * @return + * >= 0 number of items, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if dir is NULL + * + * @since 0.6.2 + */ +int iso_dir_get_children_count(IsoDir *dir); + +/** + * Removes a child from a directory. + * The child is not freed, so you will become the owner of the node. Later + * you can add the node to another dir (calling iso_dir_add_node), or free + * it if you don't need it (with iso_node_unref). + * + * @return + * 1 on success, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if node is NULL + * ISO_NODE_NOT_ADDED_TO_DIR, if node doesn't belong to a dir + * + * @since 0.6.2 + */ +int iso_node_take(IsoNode *node); + +/** + * Removes a child from a directory and free (unref) it. + * If you want to keep the child alive, you need to iso_node_ref() it + * before this call, but in that case iso_node_take() is a better + * alternative. + * + * @return + * 1 on success, < 0 error + * + * @since 0.6.2 + */ +int iso_node_remove(IsoNode *node); + +/* + * Get the parent of the given iso tree node. No extra ref is added to the + * returned directory, you must take your ref. with iso_node_ref() if you + * need it. + * + * If node is the root node, the same node will be returned as its parent. + * + * This returns NULL if the node doesn't pertain to any tree + * (it was removed/take). + * + * @since 0.6.2 + */ +IsoDir *iso_node_get_parent(IsoNode *node); + +/** + * Get an iterator for the children of the given dir. + * + * You can iterate over the children with iso_dir_iter_next. When finished, + * you should free the iterator with iso_dir_iter_free. + * You musn't delete a child of the same dir, using iso_node_take() or + * iso_node_remove(), while you're using the iterator. You can use + * iso_node_take_iter() or iso_node_remove_iter() instead. + * + * You can use the iterator in the way like this + * + * IsoDirIter *iter; + * IsoNode *node; + * if ( iso_dir_get_children(dir, &iter) != 1 ) { + * // handle error + * } + * while ( iso_dir_iter_next(iter, &node) == 1 ) { + * // do something with the child + * } + * iso_dir_iter_free(iter); + * + * An iterator is intended to be used in a single iteration over the + * children of a dir. Thus, it should be treated as a temporary object, + * and free as soon as possible. + * + * @return + * 1 success, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if dir or iter are NULL + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_dir_get_children(const IsoDir *dir, IsoDirIter **iter); + +/** + * Get the next child. + * Take care that the node is owned by its parent, and will be unref() when + * the parent is freed. If you want your own ref to it, call iso_node_ref() + * on it. + * + * @return + * 1 success, 0 if dir has no more elements, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if node or iter are NULL + * ISO_ERROR, on wrong iter usage, usual caused by modiying the + * dir during iteration + * + * @since 0.6.2 + */ +int iso_dir_iter_next(IsoDirIter *iter, IsoNode **node); + +/** + * Check if there're more children. + * + * @return + * 1 dir has more elements, 0 no, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if iter is NULL + * + * @since 0.6.2 + */ +int iso_dir_iter_has_next(IsoDirIter *iter); + +/** + * Free a dir iterator. + * + * @since 0.6.2 + */ +void iso_dir_iter_free(IsoDirIter *iter); + +/** + * Removes a child from a directory during an iteration, without freeing it. + * It's like iso_node_take(), but to be used during a directory iteration. + * The node removed will be the last returned by the iteration. + * + * The behavior on two call to this function without calling iso_dir_iter_next + * between then is undefined, and should never occur. (TODO protect against this?) + * + * @return + * 1 on succes, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if iter is NULL + * ISO_ERROR, on wrong iter usage, for example by call this before + * iso_dir_iter_next. + * + * @since 0.6.2 + */ +int iso_dir_iter_take(IsoDirIter *iter); + +/** + * Removes a child from a directory during an iteration and unref() it. + * It's like iso_node_remove(), but to be used during a directory iteration. + * The node removed will be the last returned by the iteration. + * + * The behavior on two call to this function without calling iso_tree_iter_next + * between then is undefined, and should never occur. (TODO protect against this?) + * + * @return + * 1 on succes, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if iter is NULL + * ISO_ERROR, on wrong iter usage, for example by call this before + * iso_dir_iter_next. + * + * @since 0.6.2 + */ +int iso_dir_iter_remove(IsoDirIter *iter); + +/** + * Get the destination of a node. + * The returned string belongs to the node and should not be modified nor + * freed. Use strdup if you really need your own copy. + * + * @since 0.6.2 + */ +const char *iso_symlink_get_dest(const IsoSymlink *link); + +/** + * Set the destination of a link. + * + * @param dest + * New destination for the link. It must be a non-empty string, otherwise + * this function doesn't modify previous destination. + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_symlink_set_dest(IsoSymlink *link, const char *dest); + +/** + * Sets the order in which a node will be written on image. High weihted files + * will be written first, so in a disc them will be written near the center. + * + * @param node + * The node which weight will be changed. If it's a dir, this function + * will change the weight of all its children. For nodes other that dirs + * or regular files, this function has no effect. + * @param w + * The weight as a integer number, the greater this value is, the + * closer from the begining of image the file will be written. + * + * @since 0.6.2 + */ +void iso_node_set_sort_weight(IsoNode *node, int w); + +/** + * Get the sort weight of a file. + * + * @since 0.6.2 + */ +int iso_file_get_sort_weight(IsoFile *file); + +/** + * Get the size of the file, in bytes + * + * @since 0.6.2 + */ +off_t iso_file_get_size(IsoFile *file); + +/** + * Add a new directory to the iso tree. Permissions, owner and hidden atts + * are taken from parent, you can modify them later. + * + * @param parent + * the dir where the new directory will be created + * @param name + * name for the new dir. If a node with same name already exists on + * parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param dir + * place where to store a pointer to the newly created dir. No extra + * ref is addded, so you will need to call iso_node_ref() if you really + * need it. You can pass NULL in this parameter if you don't need the + * pointer. + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent or name are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_tree_add_new_dir(IsoDir *parent, const char *name, IsoDir **dir); + +/* + TODO #00007 expose Stream and this function: + int iso_tree_add_new_file(IsoDir *parent, const char *name, stream, file) + */ + +/** + * Add a new symlink to the directory tree. Permissions are set to 0777, + * owner and hidden atts are taken from parent. You can modify any of them + * later. + * + * @param parent + * the dir where the new symlink will be created + * @param name + * name for the new symlink. If a node with same name already exists on + * parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param dest + * destination of the link + * @param link + * place where to store a pointer to the newly created link. No extra + * ref is addded, so you will need to call iso_node_ref() if you really + * need it. You can pass NULL in this parameter if you don't need the + * pointer + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent, name or dest are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_tree_add_new_symlink(IsoDir *parent, const char *name, + const char *dest, IsoSymlink **link); + +/** + * Add a new special file to the directory tree. As far as libisofs concerns, + * an special file is a block device, a character device, a FIFO (named pipe) + * or a socket. You can choose the specific kind of file you want to add + * by setting mode propertly (see man 2 stat). + * + * Note that special files are only written to image when Rock Ridge + * extensions are enabled. Moreover, a special file is just a directory entry + * in the image tree, no data is written beyond that. + * + * Owner and hidden atts are taken from parent. You can modify any of them + * later. + * + * @param parent + * the dir where the new special file will be created + * @param name + * name for the new special file. If a node with same name already exists + * on parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param mode + * file type and permissions for the new node. Note that you can't + * specify any kind of file here, only special types are allowed. i.e, + * S_IFSOCK, S_IFBLK, S_IFCHR and S_IFIFO are valid types; S_IFLNK, + * S_IFREG and S_IFDIR aren't. + * @param dev + * device ID, equivalent to the st_rdev field in man 2 stat. + * @param special + * place where to store a pointer to the newly created special file. No + * extra ref is addded, so you will need to call iso_node_ref() if you + * really need it. You can pass NULL in this parameter if you don't need + * the pointer. + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent, name or dest are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_WRONG_ARG_VALUE if you select a incorrect mode + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_tree_add_new_special(IsoDir *parent, const char *name, mode_t mode, + dev_t dev, IsoSpecial **special); + +/** + * Set whether to follow or not symbolic links when added a file from a source + * to IsoImage. Default behavior is to not follow symlinks. + * + * @since 0.6.2 + */ +void iso_tree_set_follow_symlinks(IsoImage *image, int follow); + +/** + * Get current setting for follow_symlinks. + * + * @see iso_tree_set_follow_symlinks + * @since 0.6.2 + */ +int iso_tree_get_follow_symlinks(IsoImage *image); + +/** + * Set whether to skip or not hidden files when adding a directory recursibely. + * Default behavior is to not ignore them, i.e., to add hidden files to image. + * + * @since 0.6.2 + */ +void iso_tree_set_ignore_hidden(IsoImage *image, int skip); + +/** + * Get current setting for ignore_hidden. + * + * @see iso_tree_set_ignore_hidden + * @since 0.6.2 + */ +int iso_tree_get_ignore_hidden(IsoImage *image); + +/** + * Set the replace mode, that defines the behavior of libisofs when adding + * a node whit the same name that an existent one, during a recursive + * directory addition. + * + * @since 0.6.2 + */ +void iso_tree_set_replace_mode(IsoImage *image, enum iso_replace_mode mode); + +/** + * Get current setting for replace_mode. + * + * @see iso_tree_set_replace_mode + * @since 0.6.2 + */ +enum iso_replace_mode iso_tree_get_replace_mode(IsoImage *image); + +/** + * Set whether to skip or not special files. Default behavior is to not skip + * them. Note that, despite of this setting, special files won't never be added + * to an image unless RR extensions were enabled. + * + * @param skip + * Bitmask to determine what kind of special files will be skipped: + * bit0: ignore FIFOs + * bit1: ignore Sockets + * bit2: ignore char devices + * bit3: ignore block devices + * + * @since 0.6.2 + */ +void iso_tree_set_ignore_special(IsoImage *image, int skip); + +/** + * Get current setting for ignore_special. + * + * @see iso_tree_set_ignore_special + * @since 0.6.2 + */ +int iso_tree_get_ignore_special(IsoImage *image); + +/** + * Add a excluded path. These are paths that won't never added to image, + * and will be excluded even when adding recursively its parent directory. + * + * For example, in + * + * iso_tree_add_exclude(image, "/home/user/data/private"); + * iso_tree_add_dir_rec(image, root, "/home/user/data"); + * + * the directory /home/user/data/private won't be added to image. + * + * However, if you explicity add a deeper dir, it won't be excluded. i.e., + * in the following example. + * + * iso_tree_add_exclude(image, "/home/user/data"); + * iso_tree_add_dir_rec(image, root, "/home/user/data/private"); + * + * the directory /home/user/data/private is added. On the other, side, and + * foollowing the the example above, + * + * iso_tree_add_dir_rec(image, root, "/home/user"); + * + * will exclude the directory "/home/user/data". + * + * Absolute paths are not mandatory, you can, for example, add a relative + * path such as: + * + * iso_tree_add_exclude(image, "private"); + * iso_tree_add_exclude(image, "user/data"); + * + * to excluve, respectively, all files or dirs named private, and also all + * files or dirs named data that belong to a folder named "user". Not that the + * above rule about deeper dirs is still valid. i.e., if you call + * + * iso_tree_add_dir_rec(image, root, "/home/user/data/music"); + * + * it is included even containing "user/data" string. However, a possible + * "/home/user/data/music/user/data" is not added. + * + * Usual wildcards, such as * or ? are also supported, with the usual meaning + * as stated in "man 7 glob". For example + * + * // to exclude backup text files + * iso_tree_add_exclude(image, "*.~"); + * + * @return + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_tree_add_exclude(IsoImage *image, const char *path); + +/** + * Remove a previously added exclude. + * + * @see iso_tree_add_exclude + * @return + * 1 on success, 0 exclude do not exists, < 0 on error + * + * @since 0.6.2 + */ +int iso_tree_remove_exclude(IsoImage *image, const char *path); + +/** + * Set a callback function that libisofs will call for each file that is + * added to the given image by a recursive addition function. This includes + * image import. + * + * @param report + * pointer to a function that will be called just before a file will be + * added to the image. You can control whether the file will be in fact + * added or ignored. + * This function should return 1 to add the file, 0 to ignore it and + * continue, < 0 to abort the process + * NULL is allowed if you don't want any callback. + * + * @since 0.6.2 + */ +void iso_tree_set_report_callback(IsoImage *image, + int (*report)(IsoImage*, IsoFileSource*)); + +/** + * Add a new node to the image tree, from an existing file. + * + * TODO comment Builder and Filesystem related issues when exposing both + * + * All attributes will be taken from the source file. The appropriate file + * type will be created. + * + * @param image + * The image + * @param parent + * The directory in the image tree where the node will be added. + * @param path + * The path of the file to add in the filesystem. + * @param node + * place where to store a pointer to the newly added file. No + * extra ref is addded, so you will need to call iso_node_ref() if you + * really need it. You can pass NULL in this parameter if you don't need + * the pointer. + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if image, parent or path are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_tree_add_node(IsoImage *image, IsoDir *parent, const char *path, + IsoNode **node); + +/** + * Add the contents of a dir to a given directory of the iso tree. + * + * There are several options to control what files are added or how they are + * managed. Take a look at iso_tree_set_* functions to see diferent options + * for recursive directory addition. + * + * TODO comment Builder and Filesystem related issues when exposing both + * + * @param image + * The image to which the directory belong. + * @param parent + * Directory on the image tree where to add the contents of the dir + * @param dir + * Path to a dir in the filesystem + * @return + * number of nodes in parent if success, < 0 otherwise + * + * @since 0.6.2 + */ +int iso_tree_add_dir_rec(IsoImage *image, IsoDir *parent, const char *dir); + +/** + * Locate a node by its path on image. + * + * @param node + * Location for a pointer to the node, it will filled with NULL if the + * given path does not exists on image. + * The node will be owned by the image and shouldn't be unref(). Just call + * iso_node_ref() to get your own reference to the node. + * Note that you can pass NULL is the only thing you want to do is check + * if a node with such path really exists. + * @return + * 1 found, 0 not found, < 0 error + * + * @since 0.6.2 + */ +int iso_tree_path_to_node(IsoImage *image, const char *path, IsoNode **node); + +/** + * Increments the reference counting of the given IsoDataSource. + * + * @since 0.6.2 + */ +void iso_data_source_ref(IsoDataSource *src); + +/** + * Decrements the reference counting of the given IsoDataSource, freeing it + * if refcount reach 0. + * + * @since 0.6.2 + */ +void iso_data_source_unref(IsoDataSource *src); + +/** + * Create a new IsoDataSource from a local file. This is suitable for + * accessing regular .iso images, or to acces drives via its block device + * and standard POSIX I/O calls. + * + * @param path + * The path of the file + * @param src + * Will be filled with the pointer to the newly created data source. + * @return + * 1 on success, < 0 on error. + * + * @since 0.6.2 + */ +int iso_data_source_new_from_file(const char *path, IsoDataSource **src); + +/** + * Get the status of the buffer used by a burn_source. + * + * @param b + * A burn_source previously obtained with + * iso_image_create_burn_source(). + * @param size + * Will be filled with the total size of the buffer, in bytes + * @param free_bytes + * Will be filled with the bytes currently available in buffer + * @return + * < 0 error, > 0 state: + * 1="active" : input and consumption are active + * 2="ending" : input has ended without error + * 3="failing" : input had error and ended, + * 5="abandoned" : consumption has ended prematurely + * 6="ended" : consumption has ended without input error + * 7="aborted" : consumption has ended after input error + * + * @since 0.6.2 + */ +int iso_ring_buffer_get_status(struct burn_source *b, size_t *size, + size_t *free_bytes); + +#define ISO_MSGS_MESSAGE_LEN 4096 + +/** + * Control queueing and stderr printing of messages from libisofs. + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". + * + * @param queue_severity Gives the minimum limit for messages to be queued. + * Default: "NEVER". If you queue messages then you + * must consume them by iso_msgs_obtain(). + * @param print_severity Does the same for messages to be printed directly + * to stderr. + * @param print_id A text prefix to be printed before the message. + * @return >0 for success, <=0 for error + * + * @since 0.6.2 + */ +int iso_set_msgs_severities(char *queue_severity, char *print_severity, + char *print_id); + +/** + * Obtain the oldest pending libisofs message from the queue which has at + * least the given minimum_severity. This message and any older message of + * lower severity will get discarded from the queue and is then lost forever. + * + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". To call with minimum_severity "NEVER" + * will discard the whole queue. + * + * @param error_code + * Will become a unique error code as listed at the end of this header + * @param imgid + * Id of the image that was issued the message. + * @param msg_text + * Must provide at least ISO_MSGS_MESSAGE_LEN bytes. + * @param severity + * Will become the severity related to the message and should provide at + * least 80 bytes. + * @return + * 1 if a matching item was found, 0 if not, <0 for severe errors + * + * @since 0.6.2 + */ +int iso_obtain_msgs(char *minimum_severity, int *error_code, int *imgid, + char msg_text[], char severity[]); + + +/** Submit a message to the libisofs queueing system. It will be queued or + printed as if it was generated by libburn itself. + @param error_code The unique error code of your message. + Submit 0 if you do not have reserved error codes within + the libburnia project. + @param msg_text Not more than ISO_MSGS_MESSAGE_LEN characters of + message text. + @param os_errno Eventual errno related to the message. Submit 0 if + the message is not related to a operating system error. + @param severity One of "ABORT", "FATAL", "FAILURE", "SORRY", "WARNING", + "HINT", "NOTE", "UPDATE", "DEBUG". Defaults to "FATAL". + @param origin Submit 0 for now. + @return 1 if message was delivered, <=0 if failure + + @since 0.6.4 +*/ +int iso_msgs_submit(int error_code, char msg_text[], int os_errno, + char severity[], int origin); + + +/** Convert a severity name into a severity number, which gives the severity + rank of the name. + @since 0.6.4 + @param severity_name A name as with iso_msgs_submit(), e.g. "SORRY". + @param severity_number The rank number: the higher, the more severe. + @param flag Bitfield for control purposes (unused yet, submit 0) + @return >0 success, <=0 failure + + @since 0.6.4 +*/ +int iso_text_to_sev(char *severity_name, int *severity_number, int flag); + + +/** Convert a severity number into a severity name + @param severity_number The rank number: the higher, the more severe. + @param severity_name A name as with iso_msgs_submit(), e.g. "SORRY". + @param flag Bitfield for control purposes (unused yet, submit 0) + + @since 0.6.4 +*/ +int iso_sev_to_text(int severity_number, char **severity_name, int flag); + + +/** + * Get the id of an IsoImage, used for message reporting. This message id, + * retrieved with iso_obtain_msgs(), can be used to distinguish what + * IsoImage has isssued a given message. + * + * @since 0.6.2 + */ +int iso_image_get_msg_id(IsoImage *image); + +/** + * Get a textual description of a libisofs error. + * + * @since 0.6.2 + */ +const char *iso_error_to_msg(int errcode); + +/** + * Get the severity of a given error code + * @return + * 0x10000000 -> DEBUG + * 0x20000000 -> UPDATE + * 0x30000000 -> NOTE + * 0x40000000 -> HINT + * 0x50000000 -> WARNING + * 0x60000000 -> SORRY + * 0x64000000 -> MISHAP + * 0x68000000 -> FAILURE + * 0x70000000 -> FATAL + * 0x71000000 -> ABORT + * + * @since 0.6.2 + */ +int iso_error_get_severity(int e); + +/** + * Get the priority of a given error. + * @return + * 0x00000000 -> ZERO + * 0x10000000 -> LOW + * 0x20000000 -> MEDIUM + * 0x30000000 -> HIGH + * + * @since 0.6.2 + */ +int iso_error_get_priority(int e); + +/** + * Get the message queue code of a libisofs error. + */ +int iso_error_get_code(int e); + +/** + * Set the minimum error severity that causes a libisofs operation to + * be aborted as soon as possible. + * + * @param severity + * one of "FAILURE", "MISHAP", "SORRY", "WARNING", "HINT", "NOTE". + * Severities greater or equal than FAILURE always cause program to abort. + * Severities under NOTE won't never cause function abort. + * @return + * Previous abort priority on success, < 0 on error. + * + * @since 0.6.2 + */ +int iso_set_abort_severity(char *severity); + +/** + * Return the messenger object handle used by libisofs. This handle + * may be used by related libraries to their own compatible + * messenger objects and thus to direct their messages to the libisofs + * message queue. See also: libburn, API function burn_set_messenger(). + * + * @return the handle. Do only use with compatible + * + * @since 0.6.2 + */ +void *iso_get_messenger(); + +/** + * Take a ref to the given IsoFileSource. + * + * @since 0.6.2 + */ +void iso_file_source_ref(IsoFileSource *src); + +/** + * Drop your ref to the given IsoFileSource, eventually freeing the associated + * system resources. + * + * @since 0.6.2 + */ +void iso_file_source_unref(IsoFileSource *src); + +/* + * this are just helpers to invoque methods in class + */ + +/** + * Get the path, relative to the filesystem this file source + * belongs to. + * + * @return + * the path of the FileSource inside the filesystem, it should be + * freed when no more needed. + * + * @since 0.6.2 + */ +char* iso_file_source_get_path(IsoFileSource *src); + +/** + * Get the name of the file, with the dir component of the path. + * + * @return + * the name of the file, it should be freed when no more needed. + * + * @since 0.6.2 + */ +char* iso_file_source_get_name(IsoFileSource *src); + +/** + * Get information about the file. + * @return + * 1 success, < 0 error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * + * @since 0.6.2 + */ +int iso_file_source_lstat(IsoFileSource *src, struct stat *info); + +/** + * Check if the process has access to read file contents. Note that this + * is not necessarily related with (l)stat functions. For example, in a + * filesystem implementation to deal with an ISO image, if the user has + * read access to the image it will be able to read all files inside it, + * despite of the particular permission of each file in the RR tree, that + * are what the above functions return. + * + * @return + * 1 if process has read access, < 0 on error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * + * @since 0.6.2 + */ +int iso_file_source_access(IsoFileSource *src); + +/** + * Get information about the file. If the file is a symlink, the info + * returned refers to the destination. + * + * @return + * 1 success, < 0 error + * Error codes: + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * + * @since 0.6.2 + */ +int iso_file_source_stat(IsoFileSource *src, struct stat *info); + +/** + * Opens the source. + * @return 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ALREADY_OPENNED + * ISO_FILE_ACCESS_DENIED + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * ISO_OUT_OF_MEM + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * + * @since 0.6.2 + */ +int iso_file_source_open(IsoFileSource *src); + +/** + * Close a previuously openned file + * @return 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * + * @since 0.6.2 + */ +int iso_file_source_close(IsoFileSource *src); + +/** + * Attempts to read up to count bytes from the given source into + * the buffer starting at buf. + * + * The file src must be open() before calling this, and close() when no + * more needed. Not valid for dirs. On symlinks it reads the destination + * file. + * + * @param src + * The given source + * @param buf + * Pointer to a buffer of at least count bytes where the read data will be + * stored + * @param count + * Bytes to read + * @return + * number of bytes read, 0 if EOF, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * ISO_WRONG_ARG_VALUE -> if count == 0 + * ISO_FILE_IS_DIR + * ISO_OUT_OF_MEM + * ISO_INTERRUPTED + * + * @since 0.6.2 + */ +int iso_file_source_read(IsoFileSource *src, void *buf, size_t count); + +/** + * Read a directory. + * + * Each call to this function will return a new children, until we reach + * the end of file (i.e, no more children), in that case it returns 0. + * + * The dir must be open() before calling this, and close() when no more + * needed. Only valid for dirs. + * + * Note that "." and ".." children MUST NOT BE returned. + * + * @param child + * pointer to be filled with the given child. Undefined on error or OEF + * @return + * 1 on success, 0 if EOF (no more children), < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_FILE_NOT_OPENNED + * ISO_FILE_IS_NOT_DIR + * ISO_OUT_OF_MEM + * + * @since 0.6.2 + */ +int iso_file_source_readdir(IsoFileSource *src, IsoFileSource **child); + +/** + * Read the destination of a symlink. You don't need to open the file + * to call this. + * + * @param src + * An IsoFileSource corresponding to a symbolic link. + * @param buf + * allocated buffer of at least bufsiz bytes. + * The dest. will be copied there, and it will be NULL-terminated + * @param bufsiz + * characters to be copied. Destination link will be truncated if + * it is larger than given size. This include the '\0' character. + * @return + * 1 on success, < 0 on error + * Error codes: + * ISO_FILE_ERROR + * ISO_NULL_POINTER + * ISO_WRONG_ARG_VALUE -> if bufsiz <= 0 + * ISO_FILE_IS_NOT_SYMLINK + * ISO_OUT_OF_MEM + * ISO_FILE_BAD_PATH + * ISO_FILE_DOESNT_EXIST + * + * @since 0.6.2 + */ +int iso_file_source_readlink(IsoFileSource *src, char *buf, size_t bufsiz); + +/** + * Get the filesystem for this source. No extra ref is added, so you + * musn't unref the IsoFilesystem. + * + * @return + * The filesystem, NULL on error + * + * @since 0.6.2 + */ +IsoFilesystem* iso_file_source_get_filesystem(IsoFileSource *src); + +/** + * Take a ref to the given IsoFilesystem + * + * @since 0.6.2 + */ +void iso_filesystem_ref(IsoFilesystem *fs); + +/** + * Drop your ref to the given IsoFilesystem, evetually freeing associated + * resources. + * + * @since 0.6.2 + */ +void iso_filesystem_unref(IsoFilesystem *fs); + +/** + * Create a new IsoFilesystem to access a existent ISO image. + * + * @param src + * Data source to access data. + * @param opts + * Image read options + * @param msgid + * An image identifer, obtained with iso_image_get_msg_id(), used to + * associated messages issued by the filesystem implementation with an + * existent image. If you are not using this filesystem in relation with + * any image context, just use 0x1fffff as the value for this parameter. + * @param fs + * Will be filled with a pointer to the filesystem that can be used + * to access image contents. + * @param + * 1 on success, < 0 on error + * + * @since 0.6.2 + */ +int iso_image_filesystem_new(IsoDataSource *src, IsoReadOpts *opts, int msgid, + IsoImageFilesystem **fs); + +/** + * Get the volset identifier for an existent image. The returned string belong + * to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_volset_id(IsoImageFilesystem *fs); + +/** + * Get the volume identifier for an existent image. The returned string belong + * to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_volume_id(IsoImageFilesystem *fs); + +/** + * Get the publisher identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_publisher_id(IsoImageFilesystem *fs); + +/** + * Get the data preparer identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_data_preparer_id(IsoImageFilesystem *fs); + +/** + * Get the system identifier for an existent image. The returned string belong + * to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_system_id(IsoImageFilesystem *fs); + +/** + * Get the application identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_application_id(IsoImageFilesystem *fs); + +/** + * Get the copyright file identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_copyright_file_id(IsoImageFilesystem *fs); + +/** + * Get the abstract file identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_abstract_file_id(IsoImageFilesystem *fs); + +/** + * Get the biblio file identifier for an existent image. The returned string + * belong to the IsoImageFilesystem and shouldn't be free() nor modified. + * + * @since 0.6.2 + */ +const char *iso_image_fs_get_biblio_file_id(IsoImageFilesystem *fs); + +/************ Error codes and return values for libisofs ********************/ + +/** successfully execution */ +#define ISO_SUCCESS 1 + +/** + * special return value, it could be or not an error depending on the + * context. + */ +#define ISO_NONE 0 + +/** Operation canceled (FAILURE,HIGH, -1) */ +#define ISO_CANCELED 0xE830FFFF + +/** Unknown or unexpected fatal error (FATAL,HIGH, -2) */ +#define ISO_FATAL_ERROR 0xF030FFFE + +/** Unknown or unexpected error (FAILURE,HIGH, -3) */ +#define ISO_ERROR 0xE830FFFD + +/** Internal programming error. Please report this bug (FATAL,HIGH, -4) */ +#define ISO_ASSERT_FAILURE 0xF030FFFC + +/** + * NULL pointer as value for an arg. that doesn't allow NULL (FAILURE,HIGH, -5) + */ +#define ISO_NULL_POINTER 0xE830FFFB + +/** Memory allocation error (FATAL,HIGH, -6) */ +#define ISO_OUT_OF_MEM 0xF030FFFA + +/** Interrupted by a signal (FATAL,HIGH, -7) */ +#define ISO_INTERRUPTED 0xF030FFF9 + +/** Invalid parameter value (FAILURE,HIGH, -8) */ +#define ISO_WRONG_ARG_VALUE 0xE830FFF8 + +/** Can't create a needed thread (FATAL,HIGH, -9) */ +#define ISO_THREAD_ERROR 0xF030FFF7 + +/** Write error (FAILURE,HIGH, -10) */ +#define ISO_WRITE_ERROR 0xE830FFF6 + +/** Buffer read error (FAILURE,HIGH, -11) */ +#define ISO_BUF_READ_ERROR 0xE830FFF5 + +/** Trying to add to a dir a node already added to a dir (FAILURE,HIGH, -64) */ +#define ISO_NODE_ALREADY_ADDED 0xE830FFC0 + +/** Node with same name already exists (FAILURE,HIGH, -65) */ +#define ISO_NODE_NAME_NOT_UNIQUE 0xE830FFBF + +/** Trying to remove a node that was not added to dir (FAILURE,HIGH, -65) */ +#define ISO_NODE_NOT_ADDED_TO_DIR 0xE830FFBE + +/** A requested node does not exist (FAILURE,HIGH, -66) */ +#define ISO_NODE_DOESNT_EXIST 0xE830FFBD + +/** + * Try to set the boot image of an already bootable image (FAILURE,HIGH, -67) + */ +#define ISO_IMAGE_ALREADY_BOOTABLE 0xE830FFBC + +/** Trying to use an invalid file as boot image (FAILURE,HIGH, -68) */ +#define ISO_BOOT_IMAGE_NOT_VALID 0xE830FFBB + +/** + * Error on file operation (FAILURE,HIGH, -128) + * (take a look at more specified error codes below) + */ +#define ISO_FILE_ERROR 0xE830FF80 + +/** Trying to open an already openned file (FAILURE,HIGH, -129) */ +#define ISO_FILE_ALREADY_OPENNED 0xE830FF7F + +/** Access to file is not allowed (FAILURE,HIGH, -130) */ +#define ISO_FILE_ACCESS_DENIED 0xE830FF7E + +/** Incorrect path to file (FAILURE,HIGH, -131) */ +#define ISO_FILE_BAD_PATH 0xE830FF7D + +/** The file does not exist in the filesystem (FAILURE,HIGH, -132) */ +#define ISO_FILE_DOESNT_EXIST 0xE830FF7C + +/** Trying to read or close a file not openned (FAILURE,HIGH, -133) */ +#define ISO_FILE_NOT_OPENNED 0xE830FF7B + +/** Directory used where no dir is expected (FAILURE,HIGH, -134) */ +#define ISO_FILE_IS_DIR 0xE830FF7A + +/** Read error (FAILURE,HIGH, -135) */ +#define ISO_FILE_READ_ERROR 0xE830FF79 + +/** Not dir used where a dir is expected (FAILURE,HIGH, -136) */ +#define ISO_FILE_IS_NOT_DIR 0xE830FF78 + +/** Not symlink used where a symlink is expected (FAILURE,HIGH, -137) */ +#define ISO_FILE_IS_NOT_SYMLINK 0xE830FF77 + +/** Can't seek to specified location (FAILURE,HIGH, -138) */ +#define ISO_FILE_SEEK_ERROR 0xE830FF76 + +/** File not supported in ECMA-119 tree and thus ignored (HINT,MEDIUM, -139) */ +#define ISO_FILE_IGNORED 0xC020FF75 + +/* A file is bigger than supported by used standard (HINT,MEDIUM, -140) */ +#define ISO_FILE_TOO_BIG 0xC020FF74 + +/* File read error during image creation (MISHAP,HIGH, -141) */ +#define ISO_FILE_CANT_WRITE 0xE430FF73 + +/* Can't convert filename to requested charset (HINT,MEDIUM, -142) */ +#define ISO_FILENAME_WRONG_CHARSET 0xC020FF72 + +/* File can't be added to the tree (SORRY,HIGH, -143) */ +#define ISO_FILE_CANT_ADD 0xE030FF71 + +/** + * File path break specification constraints and will be ignored + * (HINT,MEDIUM, -141) + */ +#define ISO_FILE_IMGPATH_WRONG 0xC020FF70 + +/** Charset conversion error (FAILURE,HIGH, -256) */ +#define ISO_CHARSET_CONV_ERROR 0xE830FF00 + +/** + * Too much files to mangle, i.e. we cannot guarantee unique file names + * (FAILURE,HIGH, -257) + */ +#define ISO_MANGLE_TOO_MUCH_FILES 0xE830FEFF + +/* image related errors */ + +/** + * Wrong or damaged Primary Volume Descriptor (FAILURE,HIGH, -320) + * This could mean that the file is not a valid ISO image. + */ +#define ISO_WRONG_PVD 0xE830FEC0 + +/** Wrong or damaged RR entry (SORRY,HIGH, -321) */ +#define ISO_WRONG_RR 0xE030FEBF + +/** Unsupported RR feature (SORRY,HIGH, -322) */ +#define ISO_UNSUPPORTED_RR 0xE030FEBE + +/** Wrong or damaged ECMA-119 (FAILURE,HIGH, -323) */ +#define ISO_WRONG_ECMA119 0xE830FEBD + +/** Unsupported ECMA-119 feature (FAILURE,HIGH, -324) */ +#define ISO_UNSUPPORTED_ECMA119 0xE830FEBC + +/** Wrong or damaged El-Torito catalog (SORRY,HIGH, -325) */ +#define ISO_WRONG_EL_TORITO 0xE030FEBB + +/** Unsupported El-Torito feature (SORRY,HIGH, -326) */ +#define ISO_UNSUPPORTED_EL_TORITO 0xE030FEBA + +/** Can't patch an isolinux boot image (SORRY,HIGH, -327) */ +#define ISO_ISOLINUX_CANT_PATCH 0xE030FEB9 + +/** Unsupported SUSP feature (SORRY,HIGH, -328) */ +#define ISO_UNSUPPORTED_SUSP 0xE030FEB8 + +/** Error on a RR entry that can be ignored (WARNING,HIGH, -329) */ +#define ISO_WRONG_RR_WARN 0xD030FEB7 + +/** Error on a RR entry that can be ignored (HINT,MEDIUM, -330) */ +#define ISO_SUSP_UNHANDLED 0xC020FEB6 + +/** Multiple ER SUSP entries found (WARNING,HIGH, -331) */ +#define ISO_SUSP_MULTIPLE_ER 0xD030FEB5 + +/** Unsupported volume descriptor found (HINT,MEDIUM, -332) */ +#define ISO_UNSUPPORTED_VD 0xC020FEB4 + +/** El-Torito related warning (WARNING,HIGH, -333) */ +#define ISO_EL_TORITO_WARN 0xD030FEB3 + +/** Image write cancelled (MISHAP,HIGH, -334) */ +#define ISO_IMAGE_WRITE_CANCELED 0xE430FEB2 + +/** El-Torito image is hidden (WARNING,HIGH, -335) */ +#define ISO_EL_TORITO_HIDDEN 0xD030FEB1 + +#endif /*LIBISO_LIBISOFS_H_*/ diff --git a/libisofs/messages.c b/libisofs/messages.c new file mode 100644 index 0000000..064c441 --- /dev/null +++ b/libisofs/messages.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#include +#include +#include +#include + +#include "libiso_msgs.h" +#include "libisofs.h" +#include "messages.h" + +/* + * error codes are 32 bit numbers, that follow the following conventions: + * + * bit 31 (MSB) -> 1 (to make the value always negative) + * bits 30-24 -> Encoded severity (Use ISO_ERR_SEV to translate an error code + * to a LIBISO_MSGS_SEV_* constant) + * = 0x10 -> DEBUG + * = 0x20 -> UPDATE + * = 0x30 -> NOTE + * = 0x40 -> HINT + * = 0x50 -> WARNING + * = 0x60 -> SORRY + * = 0x64 -> MISHAP + * = 0x68 -> FAILURE + * = 0x70 -> FATAL + * = 0x71 -> ABORT + * bits 23-20 -> Encoded priority (Use ISO_ERR_PRIO to translate an error code + * to a LIBISO_MSGS_PRIO_* constant) + * = 0x0 -> ZERO + * = 0x1 -> LOW + * = 0x2 -> MEDIUM + * = 0x3 -> HIGH + * bits 19-16 -> Reserved for future usage (maybe message ranges) + * bits 15-0 -> Error code + */ +#define ISO_ERR_SEV(e) (e & 0x7F000000) +#define ISO_ERR_PRIO(e) ((e & 0x00F00000) << 8) +#define ISO_ERR_CODE(e) ((e & 0x0000FFFF) | 0x00030000) + +int iso_message_id = LIBISO_MSGS_ORIGIN_IMAGE_BASE; + +/** + * Threshold for aborting. + */ +int abort_threshold = LIBISO_MSGS_SEV_FAILURE; + +#define MAX_MSG_LEN 4096 + +struct libiso_msgs *libiso_msgr = NULL; + +int iso_init() +{ + if (libiso_msgr == NULL) { + if (libiso_msgs_new(&libiso_msgr, 0) <= 0) + return ISO_FATAL_ERROR; + } + libiso_msgs_set_severities(libiso_msgr, LIBISO_MSGS_SEV_NEVER, + LIBISO_MSGS_SEV_FATAL, "libisofs: ", 0); + return 1; +} + +void iso_finish() +{ + libiso_msgs_destroy(&libiso_msgr, 0); +} + +int iso_set_abort_severity(char *severity) +{ + int ret, sevno; + + ret = libiso_msgs__text_to_sev(severity, &sevno, 0); + if (ret <= 0) + return ISO_WRONG_ARG_VALUE; + if (sevno > LIBISO_MSGS_SEV_FAILURE || sevno < LIBISO_MSGS_SEV_NOTE) + return ISO_WRONG_ARG_VALUE; + ret = abort_threshold; + abort_threshold = sevno; + return ret; +} + +void iso_msg_debug(int imgid, const char *fmt, ...) +{ + char msg[MAX_MSG_LEN]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(msg, MAX_MSG_LEN, fmt, ap); + va_end(ap); + + libiso_msgs_submit(libiso_msgr, imgid, 0x00000002, LIBISO_MSGS_SEV_DEBUG, + LIBISO_MSGS_PRIO_ZERO, msg, 0, 0); +} + +const char *iso_error_to_msg(int errcode) +{ + switch(errcode) { + case ISO_CANCELED: + return "Operation canceled"; + case ISO_FATAL_ERROR: + return "Unknown or unexpected fatal error"; + case ISO_ERROR: + return "Unknown or unexpected error"; + case ISO_ASSERT_FAILURE: + return "Internal programming error. Please report this bug"; + case ISO_NULL_POINTER: + return "NULL pointer as value for an arg. that doesn't allow NULL"; + case ISO_OUT_OF_MEM: + return "Memory allocation error"; + case ISO_INTERRUPTED: + return "Interrupted by a signal"; + case ISO_WRONG_ARG_VALUE: + return "Invalid parameter value"; + case ISO_THREAD_ERROR: + return "Can't create a needed thread"; + case ISO_WRITE_ERROR: + return "Write error"; + case ISO_BUF_READ_ERROR: + return "Buffer read error"; + case ISO_NODE_ALREADY_ADDED: + return "Trying to add to a dir a node already added to a dir"; + case ISO_NODE_NAME_NOT_UNIQUE: + return "Node with same name already exists"; + case ISO_NODE_NOT_ADDED_TO_DIR: + return "Trying to remove a node that was not added to dir"; + case ISO_NODE_DOESNT_EXIST: + return "A requested node does not exist"; + case ISO_IMAGE_ALREADY_BOOTABLE: + return "Try to set the boot image of an already bootable image"; + case ISO_BOOT_IMAGE_NOT_VALID: + return "Trying to use an invalid file as boot image"; + case ISO_FILE_ERROR: + return "Error on file operation"; + case ISO_FILE_ALREADY_OPENNED: + return "Trying to open an already openned file"; + case ISO_FILE_ACCESS_DENIED: + return "Access to file is not allowed"; + case ISO_FILE_BAD_PATH: + return "Incorrect path to file"; + case ISO_FILE_DOESNT_EXIST: + return "The file does not exist in the filesystem"; + case ISO_FILE_NOT_OPENNED: + return "Trying to read or close a file not openned"; + case ISO_FILE_IS_DIR: + return "Directory used where no dir is expected"; + case ISO_FILE_READ_ERROR: + return "Read error"; + case ISO_FILE_IS_NOT_DIR: + return "Not dir used where a dir is expected"; + case ISO_FILE_IS_NOT_SYMLINK: + return "Not symlink used where a symlink is expected"; + case ISO_FILE_SEEK_ERROR: + return "Can't seek to specified location"; + case ISO_FILE_IGNORED: + return "File not supported in ECMA-119 tree and thus ignored"; + case ISO_FILE_TOO_BIG: + return "A file is bigger than supported by used standard"; + case ISO_FILE_CANT_WRITE: + return "File read error during image creation"; + case ISO_FILENAME_WRONG_CHARSET: + return "Can't convert filename to requested charset"; + case ISO_FILE_CANT_ADD: + return "File can't be added to the tree"; + case ISO_FILE_IMGPATH_WRONG: + return "File path break specification constraints and will be ignored"; + case ISO_CHARSET_CONV_ERROR: + return "Charset conversion error"; + case ISO_MANGLE_TOO_MUCH_FILES: + return "Too much files to mangle, can't guarantee unique file names"; + case ISO_WRONG_PVD: + return "Wrong or damaged Primary Volume Descriptor"; + case ISO_WRONG_RR: + return "Wrong or damaged RR entry"; + case ISO_UNSUPPORTED_RR: + return "Unsupported RR feature"; + case ISO_WRONG_ECMA119: + return "Wrong or damaged ECMA-119"; + case ISO_UNSUPPORTED_ECMA119: + return "Unsupported ECMA-119 feature"; + case ISO_WRONG_EL_TORITO: + return "Wrong or damaged El-Torito catalog"; + case ISO_UNSUPPORTED_EL_TORITO: + return "Unsupported El-Torito feature"; + case ISO_ISOLINUX_CANT_PATCH: + return "Can't patch isolinux boot image"; + case ISO_UNSUPPORTED_SUSP: + return "Unsupported SUSP feature"; + case ISO_WRONG_RR_WARN: + return "Error on a RR entry that can be ignored"; + case ISO_SUSP_UNHANDLED: + return "Error on a RR entry that can be ignored"; + case ISO_SUSP_MULTIPLE_ER: + return "Multiple ER SUSP entries found"; + case ISO_UNSUPPORTED_VD: + return "Unsupported volume descriptor found"; + case ISO_EL_TORITO_WARN: + return "El-Torito related warning"; + case ISO_IMAGE_WRITE_CANCELED: + return "Image write cancelled"; + case ISO_EL_TORITO_HIDDEN: + return "El-Torito image is hidden"; + default: + return "Unknown error"; + } +} + +int iso_msg_submit(int imgid, int errcode, int causedby, const char *fmt, ...) +{ + char msg[MAX_MSG_LEN]; + va_list ap; + + /* when called with ISO_CANCELED, we don't need to submit any message */ + if (errcode == ISO_CANCELED && fmt == NULL) { + return ISO_CANCELED; + } + + if (fmt) { + va_start(ap, fmt); + vsnprintf(msg, MAX_MSG_LEN, fmt, ap); + va_end(ap); + } else { + strncpy(msg, iso_error_to_msg(errcode), MAX_MSG_LEN); + } + + libiso_msgs_submit(libiso_msgr, imgid, ISO_ERR_CODE(errcode), + ISO_ERR_SEV(errcode), ISO_ERR_PRIO(errcode), msg, 0, 0); + if (causedby != 0) { + snprintf(msg, MAX_MSG_LEN, " > Caused by: %s", + iso_error_to_msg(causedby)); + libiso_msgs_submit(libiso_msgr, imgid, ISO_ERR_CODE(causedby), + LIBISO_MSGS_SEV_NOTE, LIBISO_MSGS_PRIO_LOW, msg, 0, 0); + if (ISO_ERR_SEV(causedby) == LIBISO_MSGS_SEV_FATAL) { + return ISO_CANCELED; + } + } + + if (ISO_ERR_SEV(errcode) >= abort_threshold) { + return ISO_CANCELED; + } else { + return 0; + } +} + +/** + * Control queueing and stderr printing of messages from libisofs. + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". + * + * @param queue_severity Gives the minimum limit for messages to be queued. + * Default: "NEVER". If you queue messages then you + * must consume them by iso_msgs_obtain(). + * @param print_severity Does the same for messages to be printed directly + * to stderr. + * @param print_id A text prefix to be printed before the message. + * @return >0 for success, <=0 for error + */ +int iso_set_msgs_severities(char *queue_severity, char *print_severity, + char *print_id) +{ + int ret, queue_sevno, print_sevno; + + ret = libiso_msgs__text_to_sev(queue_severity, &queue_sevno, 0); + if (ret <= 0) + return 0; + ret = libiso_msgs__text_to_sev(print_severity, &print_sevno, 0); + if (ret <= 0) + return 0; + ret = libiso_msgs_set_severities(libiso_msgr, queue_sevno, print_sevno, + print_id, 0); + if (ret <= 0) + return 0; + return 1; +} + +/** + * Obtain the oldest pending libisofs message from the queue which has at + * least the given minimum_severity. This message and any older message of + * lower severity will get discarded from the queue and is then lost forever. + * + * Severity may be one of "NEVER", "FATAL", "SORRY", "WARNING", "HINT", + * "NOTE", "UPDATE", "DEBUG", "ALL". To call with minimum_severity "NEVER" + * will discard the whole queue. + * + * @param error_code Will become a unique error code as listed in messages.h + * @param imgid Id of the image that was issued the message. + * @param msg_text Must provide at least ISO_MSGS_MESSAGE_LEN bytes. + * @param severity Will become the severity related to the message and + * should provide at least 80 bytes. + * @return 1 if a matching item was found, 0 if not, <0 for severe errors + */ +int iso_obtain_msgs(char *minimum_severity, int *error_code, int *imgid, + char msg_text[], char severity[]) +{ + int ret, minimum_sevno, sevno, priority, os_errno; + double timestamp; + pid_t pid; + char *textpt, *sev_name; + struct libiso_msgs_item *item= NULL; + + ret = libiso_msgs__text_to_sev(minimum_severity, &minimum_sevno, 0); + if (ret <= 0) + return 0; + ret = libiso_msgs_obtain(libiso_msgr, &item, minimum_sevno, + LIBISO_MSGS_PRIO_ZERO, 0); + if (ret <= 0) + goto ex; + ret = libiso_msgs_item_get_msg(item, error_code, &textpt, &os_errno, 0); + if (ret <= 0) + goto ex; + strncpy(msg_text, textpt, ISO_MSGS_MESSAGE_LEN-1); + if (strlen(textpt) >= ISO_MSGS_MESSAGE_LEN) + msg_text[ISO_MSGS_MESSAGE_LEN-1] = 0; + + ret = libiso_msgs_item_get_origin(item, ×tamp, &pid, imgid, 0); + if (ret <= 0) + goto ex; + + severity[0]= 0; + ret = libiso_msgs_item_get_rank(item, &sevno, &priority, 0); + if (ret <= 0) + goto ex; + ret = libiso_msgs__sev_to_text(sevno, &sev_name, 0); + if (ret <= 0) + goto ex; + strcpy(severity, sev_name); + + ret = 1; + ex: ; + libiso_msgs_destroy_item(libiso_msgr, &item, 0); + return ret; +} + + +/* ts A80222 : derived from libburn/init.c:burn_msgs_submit() +*/ +int iso_msgs_submit(int error_code, char msg_text[], int os_errno, + char severity[], int origin) +{ + int ret, sevno; + + ret = libiso_msgs__text_to_sev(severity, &sevno, 0); + if (ret <= 0) + sevno = LIBISO_MSGS_SEV_ALL; + if (error_code <= 0) { + switch(sevno) { + case LIBISO_MSGS_SEV_ABORT: error_code = 0x00040000; + break; case LIBISO_MSGS_SEV_FATAL: error_code = 0x00040001; + break; case LIBISO_MSGS_SEV_SORRY: error_code = 0x00040002; + break; case LIBISO_MSGS_SEV_WARNING: error_code = 0x00040003; + break; case LIBISO_MSGS_SEV_HINT: error_code = 0x00040004; + break; case LIBISO_MSGS_SEV_NOTE: error_code = 0x00040005; + break; case LIBISO_MSGS_SEV_UPDATE: error_code = 0x00040006; + break; case LIBISO_MSGS_SEV_DEBUG: error_code = 0x00040007; + break; default: error_code = 0x00040008; + } + } + ret = libiso_msgs_submit(libiso_msgr, origin, error_code, + sevno, LIBISO_MSGS_PRIO_HIGH, msg_text, os_errno, 0); + return ret; +} + + +/* ts A80222 : derived from libburn/init.c:burn_text_to_sev() +*/ +int iso_text_to_sev(char *severity_name, int *sevno, int flag) +{ + int ret; + + ret = libiso_msgs__text_to_sev(severity_name, sevno, 0); + if (ret <= 0) + *sevno = LIBISO_MSGS_SEV_FATAL; + return ret; +} + + +/* ts A80222 : derived from libburn/init.c:burn_sev_to_text() +*/ +int iso_sev_to_text(int severity_number, char **severity_name, int flag) +{ + int ret; + + ret = libiso_msgs__sev_to_text(severity_number, severity_name, 0); + return ret; +} + + +/** + * Return the messenger object handle used by libisofs. This handle + * may be used by related libraries to their own compatible + * messenger objects and thus to direct their messages to the libisofs + * message queue. See also: libburn, API function burn_set_messenger(). + * + * @return the handle. Do only use with compatible + */ +void *iso_get_messenger() +{ + return libiso_msgr; +} + +int iso_error_get_severity(int e) +{ + return ISO_ERR_SEV(e); +} + +int iso_error_get_priority(int e) +{ + return ISO_ERR_PRIO(e); +} + +int iso_error_get_code(int e) +{ + return ISO_ERR_CODE(e); +} + + +/* ts A80222 */ +int iso_report_errfile(char *path, int error_code, int os_errno, int flag) +{ + libiso_msgs_submit(libiso_msgr, 0, error_code, + LIBISO_MSGS_SEV_ERRFILE, LIBISO_MSGS_PRIO_HIGH, + path, os_errno, 0); + return(1); +} diff --git a/libisofs/messages.h b/libisofs/messages.h new file mode 100644 index 0000000..dc8b98c --- /dev/null +++ b/libisofs/messages.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * Message handling for libisofs + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +/** + * Take and increment this variable to get a valid identifier for message + * origin. + */ +extern int iso_message_id; + +/** + * Submit a debug message. + */ +void iso_msg_debug(int imgid, const char *fmt, ...); + +/** + * + * @param errcode + * The error code. + * @param causedby + * Error that was caused the errcode. If this error is a FATAL error, + * < 0 will be returned in any case. Use 0 if there is no previous + * cause for the error. + * @return + * 1 on success, < 0 if function must abort asap. + */ +int iso_msg_submit(int imgid, int errcode, int causedby, const char *fmt, ...); + + +/* ts A80222 */ +/* To be called with events which report incidents with individual input + files from the local filesystem. Not with image nodes, files containing an + image or similar file-like objects. +*/ +int iso_report_errfile(char *path, int error_code, int os_errno, int flag); + + +#endif /*MESSAGES_H_*/ diff --git a/libisofs/node.c b/libisofs/node.c new file mode 100644 index 0000000..25dfcc0 --- /dev/null +++ b/libisofs/node.c @@ -0,0 +1,884 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "node.h" +#include "stream.h" + +#include +#include +#include +#include + +/** + * Increments the reference counting of the given node. + */ +void iso_node_ref(IsoNode *node) +{ + ++node->refcount; +} + +/** + * Decrements the reference couting of the given node. + * If it reach 0, the node is free, and, if the node is a directory, + * its children will be unref() too. + */ +void iso_node_unref(IsoNode *node) +{ + if (--node->refcount == 0) { + switch (node->type) { + case LIBISO_DIR: + { + IsoNode *child = ((IsoDir*)node)->children; + while (child != NULL) { + IsoNode *tmp = child->next; + child->parent = NULL; + iso_node_unref(child); + child = tmp; + } + } + break; + case LIBISO_FILE: + { + IsoFile *file = (IsoFile*) node; + iso_stream_unref(file->stream); + } + break; + case LIBISO_SYMLINK: + { + IsoSymlink *link = (IsoSymlink*) node; + free(link->dest); + } + default: + /* other kind of nodes does not need to delete anything here */ + break; + } + +#ifdef LIBISO_EXTENDED_INFORMATION + if (node->xinfo) { + /* free extended info */ + node->xinfo->process(node->xinfo->data, 1); + free(node->xinfo); + } +#endif + free(node->name); + free(node); + } +} + +/** + * Get the type of an IsoNode. + */ +enum IsoNodeType iso_node_get_type(IsoNode *node) +{ + return node->type; +} + +/** + * Set the name of a node. + * + * @param name The name in UTF-8 encoding + */ +int iso_node_set_name(IsoNode *node, const char *name) +{ + char *new; + + if ((IsoNode*)node->parent == node) { + /* you can't change name of the root node */ + return ISO_WRONG_ARG_VALUE; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + if (node->parent != NULL) { + /* check if parent already has a node with same name */ + if (iso_dir_get_node(node->parent, name, NULL) == 1) { + return ISO_NODE_NAME_NOT_UNIQUE; + } + } + + new = strdup(name); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + free(node->name); + node->name = new; + if (node->parent != NULL) { + IsoDir *parent; + int res; + /* take and add again to ensure correct children order */ + parent = node->parent; + iso_node_take(node); + res = iso_dir_add_node(parent, node, 0); + if (res < 0) { + return res; + } + } + return ISO_SUCCESS; +} + +/** + * Get the name of a node (in UTF-8). + * The returned string belongs to the node and should not be modified nor + * freed. Use strdup if you really need your own copy. + */ +const char *iso_node_get_name(const IsoNode *node) +{ + return node->name; +} + +/** + * Set the permissions for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + * + * @param mode + * bitmask with the permissions of the node, as specified in 'man 2 stat'. + * The file type bitfields will be ignored, only file permissions will be + * modified. + */ +void iso_node_set_permissions(IsoNode *node, mode_t mode) +{ + node->mode = (node->mode & S_IFMT) | (mode & ~S_IFMT); +} + +/** + * Get the permissions for the node + */ +mode_t iso_node_get_permissions(const IsoNode *node) +{ + return node->mode & ~S_IFMT; +} + +/** + * Get the mode of the node, both permissions and file type, as specified in + * 'man 2 stat'. + */ +mode_t iso_node_get_mode(const IsoNode *node) +{ + return node->mode; +} + +/** + * Set the user id for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + */ +void iso_node_set_uid(IsoNode *node, uid_t uid) +{ + node->uid = uid; +} + +/** + * Get the user id of the node. + */ +uid_t iso_node_get_uid(const IsoNode *node) +{ + return node->uid; +} + +/** + * Set the group id for the node. This attribute is only useful when + * Rock Ridge extensions are enabled. + */ +void iso_node_set_gid(IsoNode *node, gid_t gid) +{ + node->gid = gid; +} + +/** + * Get the group id of the node. + */ +gid_t iso_node_get_gid(const IsoNode *node) +{ + return node->gid; +} + +/** + * Set the time of last modification of the file + */ +void iso_node_set_mtime(IsoNode *node, time_t time) +{ + node->mtime = time; +} + +/** + * Get the time of last modification of the file + */ +time_t iso_node_get_mtime(const IsoNode *node) +{ + return node->mtime; +} + +/** + * Set the time of last access to the file + */ +void iso_node_set_atime(IsoNode *node, time_t time) +{ + node->atime = time; +} + +/** + * Get the time of last access to the file + */ +time_t iso_node_get_atime(const IsoNode *node) +{ + return node->atime; +} + +/** + * Set the time of last status change of the file + */ +void iso_node_set_ctime(IsoNode *node, time_t time) +{ + node->ctime = time; +} + +/** + * Get the time of last status change of the file + */ +time_t iso_node_get_ctime(const IsoNode *node) +{ + return node->ctime; +} + +void iso_node_set_hidden(IsoNode *node, int hide_attrs) +{ + /* you can't hide root node */ + if ((IsoNode*)node->parent != node) { + node->hidden = hide_attrs; + } +} + +/** + * Add a new node to a dir. Note that this function don't add a new ref to + * the node, so you don't need to free it, it will be automatically freed + * when the dir is deleted. Of course, if you want to keep using the node + * after the dir life, you need to iso_node_ref() it. + * + * @param dir + * the dir where to add the node + * @param child + * the node to add. You must ensure that the node hasn't previously added + * to other dir, and that the node name is unique inside the child. + * Otherwise this function will return a failure, and the child won't be + * inserted. + * @param replace + * if the dir already contains a node with the same name, whether to + * replace or not the old node with this. + * @return + * number of nodes in dir if succes, < 0 otherwise + */ +int iso_dir_add_node(IsoDir *dir, IsoNode *child, + enum iso_replace_mode replace) +{ + IsoNode **pos; + + if (dir == NULL || child == NULL) { + return ISO_NULL_POINTER; + } + if ((IsoNode*)dir == child) { + return ISO_WRONG_ARG_VALUE; + } + + /* + * check if child is already added to another dir, or if child + * is the root node, where parent == itself + */ + if (child->parent != NULL || child->parent == (IsoDir*)child) { + return ISO_NODE_ALREADY_ADDED; + } + + iso_dir_find(dir, child->name, &pos); + return iso_dir_insert(dir, child, pos, replace); +} + +/** + * Locate a node inside a given dir. + * + * @param name + * The name of the node + * @param node + * Location for a pointer to the node, it will filled with NULL if the dir + * doesn't have a child with the given name. + * The node will be owned by the dir and shouldn't be unref(). Just call + * iso_node_ref() to get your own reference to the node. + * Note that you can pass NULL is the only thing you want to do is check + * if a node with such name already exists on dir. + * @return + * 1 node found, 0 child has no such node, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if dir or name are NULL + */ +int iso_dir_get_node(IsoDir *dir, const char *name, IsoNode **node) +{ + int ret; + IsoNode **pos; + if (dir == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + + ret = iso_dir_exists(dir, name, &pos); + if (ret == 0) { + if (node) { + *node = NULL; + } + return 0; /* node not found */ + } + + if (node) { + *node = *pos; + } + return 1; +} + +/** + * Get the number of children of a directory. + * + * @return + * >= 0 number of items, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if dir is NULL + */ +int iso_dir_get_children_count(IsoDir *dir) +{ + if (dir == NULL) { + return ISO_NULL_POINTER; + } + return dir->nchildren; +} + +int iso_dir_get_children(const IsoDir *dir, IsoDirIter **iter) +{ + IsoDirIter *it; + + if (dir == NULL || iter == NULL) { + return ISO_NULL_POINTER; + } + it = malloc(sizeof(IsoDirIter)); + if (it == NULL) { + return ISO_OUT_OF_MEM; + } + + it->dir = dir; + it->pos = dir->children; + + *iter = it; + return ISO_SUCCESS; +} + +int iso_dir_iter_next(IsoDirIter *iter, IsoNode **node) +{ + IsoNode *n; + if (iter == NULL || node == NULL) { + return ISO_NULL_POINTER; + } + n = iter->pos; + if (n == NULL) { + *node = NULL; + return 0; + } + if (n->parent != iter->dir) { + /* this can happen if the node has been moved to another dir */ + return ISO_ERROR; + } + *node = n; + iter->pos = n->next; + return ISO_SUCCESS; +} + +/** + * Check if there're more children. + * + * @return + * 1 dir has more elements, 0 no, < 0 error + * Possible errors: + * ISO_NULL_POINTER, if iter is NULL + */ +int iso_dir_iter_has_next(IsoDirIter *iter) +{ + if (iter == NULL) { + return ISO_NULL_POINTER; + } + return iter->pos == NULL ? 0 : 1; +} + +void iso_dir_iter_free(IsoDirIter *iter) +{ + free(iter); +} + +static IsoNode** iso_dir_find_node(IsoDir *dir, IsoNode *node) +{ + IsoNode **pos; + pos = &(dir->children); + while (*pos != NULL && *pos != node) { + pos = &((*pos)->next); + } + return pos; +} + +/** + * Removes a child from a directory. + * The child is not freed, so you will become the owner of the node. Later + * you can add the node to another dir (calling iso_dir_add_node), or free + * it if you don't need it (with iso_node_unref). + * + * @return + * 1 on success, < 0 error + */ +int iso_node_take(IsoNode *node) +{ + IsoNode **pos; + IsoDir* dir; + + if (node == NULL) { + return ISO_NULL_POINTER; + } + dir = node->parent; + if (dir == NULL) { + return ISO_NODE_NOT_ADDED_TO_DIR; + } + pos = iso_dir_find_node(dir, node); + if (pos == NULL) { + /* should never occur */ + return ISO_ERROR; + } + *pos = node->next; + node->parent = NULL; + node->next = NULL; + dir->nchildren--; + return ISO_SUCCESS; +} + +/** + * Removes a child from a directory and free (unref) it. + * If you want to keep the child alive, you need to iso_node_ref() it + * before this call, but in that case iso_node_take() is a better + * alternative. + * + * @return + * 1 on success, < 0 error + */ +int iso_node_remove(IsoNode *node) +{ + int ret; + ret = iso_node_take(node); + if (ret == ISO_SUCCESS) { + iso_node_unref(node); + } + return ret; +} + +/* + * Get the parent of the given iso tree node. No extra ref is added to the + * returned directory, you must take your ref. with iso_node_ref() if you + * need it. + * + * If node is the root node, the same node will be returned as its parent. + * + * This returns NULL if the node doesn't pertain to any tree + * (it was removed/take). + */ +IsoDir *iso_node_get_parent(IsoNode *node) +{ + return node->parent; +} + +/* TODO #00005 optimize iso_dir_iter_take */ +int iso_dir_iter_take(IsoDirIter *iter) +{ + IsoNode *pos; + if (iter == NULL) { + return ISO_NULL_POINTER; + } + + pos = iter->dir->children; + if (iter->pos == pos) { + return ISO_ERROR; + } + while (pos != NULL && pos->next == iter->pos) { + pos = pos->next; + } + if (pos == NULL) { + return ISO_ERROR; + } + return iso_node_take(pos); +} + +int iso_dir_iter_remove(IsoDirIter *iter) +{ + IsoNode *pos; + if (iter == NULL) { + return ISO_NULL_POINTER; + } + pos = iter->dir->children; + if (iter->pos == pos) { + return ISO_ERROR; + } + while (pos != NULL && pos->next == iter->pos) { + pos = pos->next; + } + if (pos == NULL) { + return ISO_ERROR; + } + return iso_node_remove(pos); +} + +/** + * Get the destination of a node. + * The returned string belongs to the node and should not be modified nor + * freed. Use strdup if you really need your own copy. + */ +const char *iso_symlink_get_dest(const IsoSymlink *link) +{ + return link->dest; +} + +/** + * Set the destination of a link. + */ +int iso_symlink_set_dest(IsoSymlink *link, const char *dest) +{ + char *d; + if (!iso_node_is_valid_link_dest(dest)) { + /* guard against null or empty dest */ + return ISO_WRONG_ARG_VALUE; + } + d = strdup(dest); + if (d == NULL) { + return ISO_OUT_OF_MEM; + } + free(link->dest); + link->dest = d; + return ISO_SUCCESS; +} + +/** + * Sets the order in which a node will be written on image. High weihted files + * will be written first, so in a disc them will be written near the center. + * + * @param node + * The node which weight will be changed. If it's a dir, this function + * will change the weight of all its children. For nodes other that dirs + * or regular files, this function has no effect. + * @param w + * The weight as a integer number, the greater this value is, the + * closer from the begining of image the file will be written. + */ +void iso_node_set_sort_weight(IsoNode *node, int w) +{ + if (node->type == LIBISO_DIR) { + IsoNode *child = ((IsoDir*)node)->children; + while (child) { + iso_node_set_sort_weight(child, w); + child = child->next; + } + } else if (node->type == LIBISO_FILE) { + ((IsoFile*)node)->sort_weight = w; + } +} + +/** + * Get the sort weight of a file. + */ +int iso_file_get_sort_weight(IsoFile *file) +{ + return file->sort_weight; +} + +/** + * Get the size of the file, in bytes + */ +off_t iso_file_get_size(IsoFile *file) +{ + return iso_stream_get_size(file->stream); +} + +/** + * Check if a given name is valid for an iso node. + * + * @return + * 1 if yes, 0 if not + */ +int iso_node_is_valid_name(const char *name) +{ + /* a name can't be NULL */ + if (name == NULL) { + return 0; + } + + /* guard against the empty string or big names... */ + if (name[0] == '\0' || strlen(name) > 255) { + return 0; + } + + /* ...against "." and ".." names... */ + if (!strcmp(name, ".") || !strcmp(name, "..")) { + return 0; + } + + /* ...and against names with '/' */ + if (strchr(name, '/') != NULL) { + return 0; + } + return 1; +} + +/** + * Check if a given path is valid for the destination of a link. + * + * @return + * 1 if yes, 0 if not + */ +int iso_node_is_valid_link_dest(const char *dest) +{ + int ret; + char *ptr, *brk_info, *component; + + /* a dest can't be NULL */ + if (dest == NULL) { + return 0; + } + + /* guard against the empty string or big dest... */ + if (dest[0] == '\0' || strlen(dest) > PATH_MAX) { + return 0; + } + + /* check that all components are valid */ + if (!strcmp(dest, "/")) { + /* "/" is a valid component */ + return 1; + } + + ptr = strdup(dest); + if (ptr == NULL) { + return 0; + } + + ret = 1; + component = strtok_r(ptr, "/", &brk_info); + while (component) { + if (strcmp(component, ".") && strcmp(component, "..")) { + ret = iso_node_is_valid_name(component); + if (ret == 0) { + break; + } + } + component = strtok_r(NULL, "/", &brk_info); + } + free(ptr); + + return ret; +} + +void iso_dir_find(IsoDir *dir, const char *name, IsoNode ***pos) +{ + *pos = &(dir->children); + while (**pos != NULL && strcmp((**pos)->name, name) < 0) { + *pos = &((**pos)->next); + } +} + +int iso_dir_exists(IsoDir *dir, const char *name, IsoNode ***pos) +{ + IsoNode **node; + + iso_dir_find(dir, name, &node); + if (pos) { + *pos = node; + } + return (*node != NULL && !strcmp((*node)->name, name)) ? 1 : 0; +} + +int iso_dir_insert(IsoDir *dir, IsoNode *node, IsoNode **pos, + enum iso_replace_mode replace) +{ + if (*pos != NULL && !strcmp((*pos)->name, node->name)) { + /* a node with same name already exists */ + switch(replace) { + case ISO_REPLACE_NEVER: + return ISO_NODE_NAME_NOT_UNIQUE; + case ISO_REPLACE_IF_NEWER: + if ((*pos)->mtime >= node->mtime) { + /* old file is newer */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + break; + case ISO_REPLACE_IF_SAME_TYPE_AND_NEWER: + if ((*pos)->mtime >= node->mtime) { + /* old file is newer */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + /* fall down */ + case ISO_REPLACE_IF_SAME_TYPE: + if ((node->mode & S_IFMT) != ((*pos)->mode & S_IFMT)) { + /* different file types */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + break; + case ISO_REPLACE_ALWAYS: + break; + default: + /* CAN'T HAPPEN */ + return ISO_ASSERT_FAILURE; + } + + /* if we are reach here we have to replace */ + node->next = (*pos)->next; + (*pos)->parent = NULL; + (*pos)->next = NULL; + iso_node_unref(*pos); + *pos = node; + node->parent = dir; + return dir->nchildren; + } + + node->next = *pos; + *pos = node; + node->parent = dir; + + return ++dir->nchildren; +} + +int iso_node_new_root(IsoDir **root) +{ + IsoDir *dir; + + dir = calloc(1, sizeof(IsoDir)); + if (dir == NULL) { + return ISO_OUT_OF_MEM; + } + dir->node.refcount = 1; + dir->node.type = LIBISO_DIR; + dir->node.atime = dir->node.ctime = dir->node.mtime = time(NULL); + dir->node.mode = S_IFDIR | 0555; + + /* set parent to itself, to prevent root to be added to another dir */ + dir->node.parent = dir; + *root = dir; + return ISO_SUCCESS; +} + +int iso_node_new_dir(char *name, IsoDir **dir) +{ + IsoDir *new; + + if (dir == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + new = calloc(1, sizeof(IsoDir)); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + new->node.refcount = 1; + new->node.type = LIBISO_DIR; + new->node.name = name; + new->node.mode = S_IFDIR; + *dir = new; + return ISO_SUCCESS; +} + +int iso_node_new_file(char *name, IsoStream *stream, IsoFile **file) +{ + IsoFile *new; + + if (file == NULL || name == NULL || stream == NULL) { + return ISO_NULL_POINTER; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + new = calloc(1, sizeof(IsoFile)); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + new->node.refcount = 1; + new->node.type = LIBISO_FILE; + new->node.name = name; + new->node.mode = S_IFREG; + new->stream = stream; + + *file = new; + return ISO_SUCCESS; +} + +int iso_node_new_symlink(char *name, char *dest, IsoSymlink **link) +{ + IsoSymlink *new; + + if (link == NULL || name == NULL || dest == NULL) { + return ISO_NULL_POINTER; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + /* check if destination is valid */ + if (!iso_node_is_valid_link_dest(dest)) { + /* guard against null or empty dest */ + return ISO_WRONG_ARG_VALUE; + } + + new = calloc(1, sizeof(IsoSymlink)); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + new->node.refcount = 1; + new->node.type = LIBISO_SYMLINK; + new->node.name = name; + new->dest = dest; + new->node.mode = S_IFLNK; + *link = new; + return ISO_SUCCESS; +} + +int iso_node_new_special(char *name, mode_t mode, dev_t dev, + IsoSpecial **special) +{ + IsoSpecial *new; + + if (special == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + if (S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode)) { + return ISO_WRONG_ARG_VALUE; + } + + /* check if the name is valid */ + if (!iso_node_is_valid_name(name)) { + return ISO_WRONG_ARG_VALUE; + } + + new = calloc(1, sizeof(IsoSpecial)); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + new->node.refcount = 1; + new->node.type = LIBISO_SPECIAL; + new->node.name = name; + + new->node.mode = mode; + new->dev = dev; + + *special = new; + return ISO_SUCCESS; +} diff --git a/libisofs/node.h b/libisofs/node.h new file mode 100644 index 0000000..b2764f9 --- /dev/null +++ b/libisofs/node.h @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_NODE_H_ +#define LIBISO_NODE_H_ + +/* + * Definitions for the public iso tree + */ + +#include "libisofs.h" +#include "stream.h" + +#include +#include +#include +#include + +/* #define LIBISO_EXTENDED_INFORMATION */ +#ifdef LIBISO_EXTENDED_INFORMATION + +/** + * The extended information is a way to attach additional information to each + * IsoNode. External applications may want to use this extension system to + * store application speficic information related to each node. On the other + * side, libisofs may make use of this struct to attach information to nodes in + * some particular, uncommon, cases, without incrementing the size of the + * IsoNode struct. + * + * It is implemented like a chained list. + */ +typedef struct iso_extended_info IsoExtendedInfo; + +struct iso_extended_info { + /** + * Next struct in the chain. NULL if it is the last item + */ + IsoExtendedInfo *next; + + /** + * Function to handle this particular extended information. The function + * pointer acts as an identifier for the type of the information. Structs + * with same information type must use the same function. + * + * @param data + * Attached data + * @param flag + * What to do with the data. At this time the following values are + * defined: + * -> 1 the data must be freed + * @return + * 1 + */ + int (*process)(void *data, int flag); + + /** + * Pointer to information specific data. + */ + void *data; +}; + +#endif + +/** + * + */ +struct Iso_Node +{ + /* + * Initilized to 1, originally owned by user, until added to another node. + * Then it is owned by the parent node, so the user must take his own ref + * if needed. With the exception of the creation functions, none of the + * other libisofs functions that return an IsoNode increment its + * refcount. This is responsablity of the client, if (s)he needs it. + */ + int refcount; + + /** Type of the IsoNode, do not confuse with mode */ + enum IsoNodeType type; + + char *name; /**< Real name, in default charset */ + + mode_t mode; /**< protection */ + uid_t uid; /**< user ID of owner */ + gid_t gid; /**< group ID of owner */ + + /* TODO #00001 : consider adding new timestamps */ + time_t atime; /**< time of last access */ + time_t mtime; /**< time of last modification */ + time_t ctime; /**< time of last status change */ + + int hidden; /**< whether the node will be hidden, see IsoHideNodeFlag */ + + IsoDir *parent; /**< parent node, NULL for root */ + + /* + * Pointer to the linked list of children in a dir. + */ + IsoNode *next; + +#ifdef LIBISO_EXTENDED_INFORMATION + /** + * Extended information for the node. + */ + IsoExtendedInfo *xinfo; +#endif +}; + +struct Iso_Dir +{ + IsoNode node; + + size_t nchildren; /**< The number of children of this directory. */ + IsoNode *children; /**< list of children. ptr to first child */ +}; + +struct Iso_File +{ + IsoNode node; + + /** + * Location of a file extent in a ms disc, 0 for newly added file + */ + uint32_t msblock; + + /** + * It sorts the order in which the file data is written to the CD image. + * Higher weighting files are written at the beginning of image + */ + int sort_weight; + IsoStream *stream; +}; + +struct Iso_Symlink +{ + IsoNode node; + + char *dest; +}; + +struct Iso_Special +{ + IsoNode node; + dev_t dev; +}; + +/** + * An iterator for directory children. + */ +struct Iso_Dir_Iter +{ + const IsoDir *dir; + IsoNode *pos; +}; + +int iso_node_new_root(IsoDir **root); + +/** + * Create a new IsoDir. Attributes, uid/gid, timestamps, etc are set to + * default (0) values. You must set them. + * + * @param name + * Name for the node. It is not strdup() so you shouldn't use this + * reference when this function returns successfully. NULL is not + * allowed. + * @param dir + * + * @return + * 1 on success, < 0 on error. + */ +int iso_node_new_dir(char *name, IsoDir **dir); + +/** + * Create a new file node. Attributes, uid/gid, timestamps, etc are set to + * default (0) values. You must set them. + * + * @param name + * Name for the node. It is not strdup() so you shouldn't use this + * reference when this function returns successfully. NULL is not + * allowed. + * @param stream + * Source for file contents. The reference is taken by the node, + * you must call iso_stream_ref() if you need your own ref. + * @return + * 1 on success, < 0 on error. + */ +int iso_node_new_file(char *name, IsoStream *stream, IsoFile **file); + +/** + * Creates a new IsoSymlink node. Attributes, uid/gid, timestamps, etc are set + * to default (0) values. You must set them. + * + * @param name + * name for the new symlink. It is not strdup() so you shouldn't use this + * reference when this function returns successfully. NULL is not + * allowed. + * @param dest + * destination of the link. It is not strdup() so you shouldn't use this + * reference when this function returns successfully. NULL is not + * allowed. + * @param link + * place where to store a pointer to the newly created link. + * @return + * 1 on success, < 0 otherwise + */ +int iso_node_new_symlink(char *name, char *dest, IsoSymlink **link); + +/** + * Create a new special file node. As far as libisofs concerns, + * an special file is a block device, a character device, a FIFO (named pipe) + * or a socket. You can choose the specific kind of file you want to add + * by setting mode propertly (see man 2 stat). + * + * Note that special files are only written to image when Rock Ridge + * extensions are enabled. Moreover, a special file is just a directory entry + * in the image tree, no data is written beyond that. + * + * Owner and hidden atts are taken from parent. You can modify any of them + * later. + * + * @param name + * name for the new special file. It is not strdup() so you shouldn't use + * this reference when this function returns successfully. NULL is not + * allowed. + * @param mode + * file type and permissions for the new node. Note that you can't + * specify any kind of file here, only special types are allowed. i.e, + * S_IFSOCK, S_IFBLK, S_IFCHR and S_IFIFO are valid types; S_IFLNK, + * S_IFREG and S_IFDIR aren't. + * @param dev + * device ID, equivalent to the st_rdev field in man 2 stat. + * @param special + * place where to store a pointer to the newly created special file. + * @return + * 1 on success, < 0 otherwise + */ +int iso_node_new_special(char *name, mode_t mode, dev_t dev, + IsoSpecial **special); + +/** + * Check if a given name is valid for an iso node. + * + * @return + * 1 if yes, 0 if not + */ +int iso_node_is_valid_name(const char *name); + +/** + * Check if a given path is valid for the destination of a link. + * + * @return + * 1 if yes, 0 if not + */ +int iso_node_is_valid_link_dest(const char *dest); + +/** + * Find the position where to insert a node + * + * @param dir + * A valid dir. It can't be NULL + * @param name + * The node name to search for. It can't be NULL + * @param pos + * Will be filled with the position where to insert. It can't be NULL + */ +void iso_dir_find(IsoDir *dir, const char *name, IsoNode ***pos); + +/** + * Check if a node with the given name exists in a dir. + * + * @param dir + * A valid dir. It can't be NULL + * @param name + * The node name to search for. It can't be NULL + * @param pos + * If not NULL, will be filled with the position where to insert. If the + * node exists, (**pos) will refer to the given node. + * @return + * 1 if node exists, 0 if not + */ +int iso_dir_exists(IsoDir *dir, const char *name, IsoNode ***pos); + +/** + * Inserts a given node in a dir, at the specified position. + * + * @param dir + * Dir where to insert. It can't be NULL + * @param node + * The node to insert. It can't be NULL + * @param pos + * Position where the node will be inserted. It is a pointer previously + * obtained with a call to iso_dir_exists() or iso_dir_find(). + * It can't be NULL. + * @param replace + * Whether to replace an old node with the same name with the new node. + * @return + * If success, number of children in dir. < 0 on error + */ +int iso_dir_insert(IsoDir *dir, IsoNode *node, IsoNode **pos, + enum iso_replace_mode replace); + +#endif /*LIBISO_NODE_H_*/ diff --git a/libisofs/rockridge.c b/libisofs/rockridge.c new file mode 100644 index 0000000..e57ed3d --- /dev/null +++ b/libisofs/rockridge.c @@ -0,0 +1,1208 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "rockridge.h" +#include "node.h" +#include "ecma119_tree.h" +#include "writer.h" +#include "messages.h" +#include "image.h" + +#include + +static +int susp_append(Ecma119Image *t, struct susp_info *susp, uint8_t *data) +{ + susp->n_susp_fields++; + susp->susp_fields = realloc(susp->susp_fields, sizeof(void*) + * susp->n_susp_fields); + if (susp->susp_fields == NULL) { + return ISO_OUT_OF_MEM; + } + susp->susp_fields[susp->n_susp_fields - 1] = data; + susp->suf_len += data[2]; + return ISO_SUCCESS; +} + +static +int susp_append_ce(Ecma119Image *t, struct susp_info *susp, uint8_t *data) +{ + susp->n_ce_susp_fields++; + susp->ce_susp_fields = realloc(susp->ce_susp_fields, sizeof(void*) + * susp->n_ce_susp_fields); + if (susp->ce_susp_fields == NULL) { + return ISO_OUT_OF_MEM; + } + susp->ce_susp_fields[susp->n_ce_susp_fields - 1] = data; + susp->ce_len += data[2]; + return ISO_SUCCESS; +} + +static +uid_t px_get_uid(Ecma119Image *t, Ecma119Node *n) +{ + if (t->replace_uid) { + return t->uid; + } else { + return n->node->uid; + } +} + +static +uid_t px_get_gid(Ecma119Image *t, Ecma119Node *n) +{ + if (t->replace_gid) { + return t->gid; + } else { + return n->node->gid; + } +} + +static +mode_t px_get_mode(Ecma119Image *t, Ecma119Node *n) +{ + if ((n->type == ECMA119_DIR || n->type == ECMA119_PLACEHOLDER)) { + if (t->replace_dir_mode) { + return (n->node->mode & S_IFMT) | t->dir_mode; + } + } else { + if (t->replace_file_mode) { + return (n->node->mode & S_IFMT) | t->file_mode; + } + } + return n->node->mode; +} + +/** + * Add a PX System Use Entry. The PX System Use Entry is used to add POSIX + * file attributes, such as access permissions or user and group id, to a + * ECMA 119 directory record. (RRIP, 4.1.1) + */ +static +int rrip_add_PX(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + uint8_t *PX = malloc(44); + if (PX == NULL) { + return ISO_OUT_OF_MEM; + } + + PX[0] = 'P'; + PX[1] = 'X'; + PX[2] = 44; + PX[3] = 1; + iso_bb(&PX[4], px_get_mode(t, n), 4); + iso_bb(&PX[12], n->nlink, 4); + iso_bb(&PX[20], px_get_uid(t, n), 4); + iso_bb(&PX[28], px_get_gid(t, n), 4); + iso_bb(&PX[36], n->ino, 4); + + return susp_append(t, susp, PX); +} + +/** + * Add to the given tree node a TF System Use Entry, used to record some + * time stamps related to the file (RRIP, 4.1.6). + */ +static +int rrip_add_TF(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + IsoNode *iso; + uint8_t *TF = malloc(5 + 3 * 7); + if (TF == NULL) { + return ISO_OUT_OF_MEM; + } + + TF[0] = 'T'; + TF[1] = 'F'; + TF[2] = 5 + 3 * 7; + TF[3] = 1; + TF[4] = (1 << 1) | (1 << 2) | (1 << 3); + + iso = n->node; + iso_datetime_7(&TF[5], t->replace_timestamps ? t->timestamp : iso->mtime, + t->always_gmt); + iso_datetime_7(&TF[12], t->replace_timestamps ? t->timestamp : iso->atime, + t->always_gmt); + iso_datetime_7(&TF[19], t->replace_timestamps ? t->timestamp : iso->ctime, + t->always_gmt); + return susp_append(t, susp, TF); +} + +/** + * Add a PL System Use Entry, used to record the location of the original + * parent directory of a directory which has been relocated. + * + * This is special because it doesn't modify the susp fields of the directory + * that gets passed to it; it modifies the susp fields of the ".." entry in + * that directory. + * + * See RRIP, 4.1.5.2 for more details. + */ +static +int rrip_add_PL(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + uint8_t *PL; + + if (n->type != ECMA119_DIR || n->info.dir->real_parent == NULL) { + /* should never occur */ + return ISO_ASSERT_FAILURE; + } + + PL = malloc(12); + if (PL == NULL) { + return ISO_OUT_OF_MEM; + } + + PL[0] = 'P'; + PL[1] = 'L'; + PL[2] = 12; + PL[3] = 1; + + /* write the location of the real parent, already computed */ + iso_bb(&PL[4], n->info.dir->real_parent->info.dir->block, 4); + return susp_append(t, susp, PL); +} + +/** + * Add a RE System Use Entry to the given tree node. The purpose of the + * this System Use Entry is to indicate to an RRIP-compliant receiving + * system that the Directory Record in which an "RE" System Use Entry is + * recorded has been relocated from another position in the original + * Directory Hierarchy. + * + * See RRIP, 4.1.5.3 for more details. + */ +static +int rrip_add_RE(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + uint8_t *RE = malloc(4); + if (RE == NULL) { + return ISO_OUT_OF_MEM; + } + + RE[0] = 'R'; + RE[1] = 'E'; + RE[2] = 4; + RE[3] = 1; + return susp_append(t, susp, RE); +} + +/** + * Add a PN System Use Entry to the given tree node. + * The PN System Use Entry is used to store the device number, and it's + * mandatory if the tree node corresponds to a character or block device. + * + * See RRIP, 4.1.2 for more details. + */ +static +int rrip_add_PN(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + IsoSpecial *node; + uint8_t *PN; + + node = (IsoSpecial*)n->node; + if (node->node.type != LIBISO_SPECIAL) { + /* should never occur */ + return ISO_ASSERT_FAILURE; + } + + PN = malloc(20); + if (PN == NULL) { + return ISO_OUT_OF_MEM; + } + + PN[0] = 'P'; + PN[1] = 'N'; + PN[2] = 20; + PN[3] = 1; + iso_bb(&PN[4], node->dev >> 32, 4); + iso_bb(&PN[12], node->dev & 0xffffffff, 4); + return susp_append(t, susp, PN); +} + +/** + * Add to the given tree node a CL System Use Entry, that is used to record + * the new location of a directory which has been relocated. + * + * See RRIP, 4.1.5.1 for more details. + */ +static +int rrip_add_CL(Ecma119Image *t, Ecma119Node *n, struct susp_info *susp) +{ + uint8_t *CL; + if (n->type != ECMA119_PLACEHOLDER) { + /* should never occur */ + return ISO_ASSERT_FAILURE; + } + CL = malloc(12); + if (CL == NULL) { + return ISO_OUT_OF_MEM; + } + + CL[0] = 'C'; + CL[1] = 'L'; + CL[2] = 12; + CL[3] = 1; + iso_bb(&CL[4], n->info.real_me->info.dir->block, 4); + return susp_append(t, susp, CL); +} + +/** + * Convert a RR filename to the requested charset. On any conversion error, + * the original name will be used. + */ +static +char *get_rr_fname(Ecma119Image *t, const char *str) +{ + int ret; + char *name; + + if (!strcmp(t->input_charset, t->output_charset)) { + /* no conversion needed */ + return strdup(str); + } + + ret = strconv(str, t->input_charset, t->output_charset, &name); + if (ret < 0) { + /* TODO we should check for possible cancelation */ + iso_msg_submit(t->image->id, ISO_FILENAME_WRONG_CHARSET, ret, + "Charset conversion error. Can't convert %s from %s to %s", + str, t->input_charset, t->output_charset); + + /* use the original name, it's the best we can do */ + name = strdup(str); + } + + return name; +} + +/** + * Add a NM System Use Entry to the given tree node. The purpose of this + * System Use Entry is to store the content of an Alternate Name to support + * POSIX-style or other names. + * + * See RRIP, 4.1.4 for more details. + * + * @param size + * Length of the name to be included into the NM + * @param flags + * @param ce + * Whether to add or not to CE + */ +static +int rrip_add_NM(Ecma119Image *t, struct susp_info *susp, char *name, int size, + int flags, int ce) +{ + uint8_t *NM = malloc(size + 5); + if (NM == NULL) { + return ISO_OUT_OF_MEM; + } + + NM[0] = 'N'; + NM[1] = 'M'; + NM[2] = size + 5; + NM[3] = 1; + NM[4] = flags; + if (size) { + memcpy(&NM[5], name, size); + } + if (ce) { + return susp_append_ce(t, susp, NM); + } else { + return susp_append(t, susp, NM); + } +} + +/** + * Add a new SL component (RRIP, 4.1.3.1) to a list of components. + * + * @param n + * Number of components. It will be updated. + * @param compos + * Pointer to the list of components. + * @param s + * The component content + * @param size + * Size of the component content + * @param fl + * Flags + * @return + * 1 on success, < 0 on error + */ +static +int rrip_SL_append_comp(size_t *n, uint8_t ***comps, char *s, int size, char fl) +{ + uint8_t *comp = malloc(size + 2); + if (comp == NULL) { + return ISO_OUT_OF_MEM; + } + + (*n)++; + comp[0] = fl; + comp[1] = size; + *comps = realloc(*comps, (*n) * sizeof(void*)); + if (*comps == NULL) { + free(comp); + return ISO_OUT_OF_MEM; + } + (*comps)[(*n) - 1] = comp; + + if (size) { + memcpy(&comp[2], s, size); + } + return ISO_SUCCESS; +} + +/** + * Add a SL System Use Entry to the given tree node. This is used to store + * the content of a symbolic link, and is mandatory if the tree node + * indicates a symbolic link (RRIP, 4.1.3). + * + * @param comp + * Components of the SL System Use Entry. If they don't fit in a single + * SL, more than one SL will be added. + * @param n + * Number of components in comp + * @param ce + * Whether to add to a continuation area or system use field. + */ +static +int rrip_add_SL(Ecma119Image *t, struct susp_info *susp, uint8_t **comp, + size_t n, int ce) +{ + int ret, i, j; + + int total_comp_len = 0; + size_t pos, written = 0; + + uint8_t *SL; + + for (i = 0; i < n; i++) { + + total_comp_len += comp[i][1] + 2; + if (total_comp_len > 250) { + /* we need a new SL entry */ + total_comp_len -= comp[i][1] + 2; + SL = malloc(total_comp_len + 5); + if (SL == NULL) { + return ISO_OUT_OF_MEM; + } + + SL[0] = 'S'; + SL[1] = 'L'; + SL[2] = total_comp_len + 5; + SL[3] = 1; + SL[4] = 1; /* CONTINUE */ + pos = 5; + for (j = written; j < i; j++) { + memcpy(&SL[pos], comp[j], comp[j][1] + 2); + pos += comp[j][1] + 2; + } + + /* + * In this case we are sure we're writting to CE. Check for + * debug purposes + */ + if (ce == 0) { + return ISO_ASSERT_FAILURE; + } + ret = susp_append_ce(t, susp, SL); + if (ret < 0) { + return ret; + } + written = i; + total_comp_len = comp[i][1] + 2; + } + } + + SL = malloc(total_comp_len + 5); + if (SL == NULL) { + return ISO_OUT_OF_MEM; + } + + SL[0] = 'S'; + SL[1] = 'L'; + SL[2] = total_comp_len + 5; + SL[3] = 1; + SL[4] = 0; + pos = 5; + + for (j = written; j < n; j++) { + memcpy(&SL[pos], comp[j], comp[j][1] + 2); + pos += comp[j][1] + 2; + } + if (ce) { + ret = susp_append_ce(t, susp, SL); + } else { + ret = susp_append(t, susp, SL); + } + return ret; +} + +/** + * Add a SUSP "ER" System Use Entry to identify the Rock Ridge specification. + * + * The "ER" System Use Entry is used to uniquely identify a specification + * compliant with SUSP. This method adds to the given tree node "." entry + * the "ER" corresponding to the RR protocol. + * + * See SUSP, 5.5 and RRIP, 4.3 for more details. + */ +static +int rrip_add_ER(Ecma119Image *t, struct susp_info *susp) +{ + unsigned char *ER = malloc(182); + if (ER == NULL) { + return ISO_OUT_OF_MEM; + } + + ER[0] = 'E'; + ER[1] = 'R'; + ER[2] = 182; + ER[3] = 1; + ER[4] = 9; + ER[5] = 72; + ER[6] = 93; + ER[7] = 1; + memcpy(&ER[8], "IEEE_1282", 9); + memcpy(&ER[17], "THE IEEE 1282 PROTOCOL PROVIDES SUPPORT FOR POSIX " + "FILE SYSTEM SEMANTICS.", 72); + memcpy(&ER[89], "PLEASE CONTACT THE IEEE STANDARDS DEPARTMENT, " + "PISCATAWAY, NJ, USA FOR THE 1282 SPECIFICATION.", 93); + + /** This always goes to continuation area */ + return susp_append_ce(t, susp, ER); +} + +/** + * Add a CE System Use Entry to the given tree node. A "CE" is used to add + * a continuation area, where additional System Use Entry can be written. + * (SUSP, 5.1). + */ +static +int susp_add_CE(Ecma119Image *t, size_t ce_len, struct susp_info *susp) +{ + uint8_t *CE = malloc(28); + if (CE == NULL) { + return ISO_OUT_OF_MEM; + } + + CE[0] = 'C'; + CE[1] = 'E'; + CE[2] = 28; + CE[3] = 1; + iso_bb(&CE[4], susp->ce_block, 4); + iso_bb(&CE[12], susp->ce_len, 4); + iso_bb(&CE[20], ce_len, 4); + + return susp_append(t, susp, CE); +} + +/** + * Add a SP System Use Entry. The SP provide an identifier that the SUSP is + * used within the volume. The SP shall be recorded in the "." entry of the + * root directory. See SUSP, 5.3 for more details. + */ +static +int susp_add_SP(Ecma119Image *t, struct susp_info *susp) +{ + unsigned char *SP = malloc(7); + if (SP == NULL) { + return ISO_OUT_OF_MEM; + } + + SP[0] = 'S'; + SP[1] = 'P'; + SP[2] = (char)7; + SP[3] = (char)1; + SP[4] = 0xbe; + SP[5] = 0xef; + SP[6] = 0; + return susp_append(t, susp, SP); +} + +/** + * Compute the length needed for write all RR and SUSP entries for a given + * node. + * + * @param type + * 0 normal entry, 1 "." entry for that node (it is a dir), 2 ".." + * for that node (i.e., it will refer to the parent) + * @param space + * Available space in the System Use Area for the directory record. + * @param ce + * Will be filled with the space needed in a CE + * @return + * The size needed for the RR entries in the System Use Area + */ +size_t rrip_calc_len(Ecma119Image *t, Ecma119Node *n, int type, size_t space, + size_t *ce) +{ + size_t su_size; + + /* space min is 255 - 33 - 37 = 185 + * At the same time, it is always an odd number, but we need to pad it + * propertly to ensure the length of a directory record is a even number + * (ECMA-119, 9.1.13). Thus, in fact the real space is always space - 1 + */ + space--; + *ce = 0; + + /* PX and TF, we are sure they always fit in SUA */ + su_size = 44 + 26; + + if (n->type == ECMA119_DIR) { + if (n->info.dir->real_parent != NULL) { + /* it is a reallocated entry */ + if (type == 2) { + /* we need to add a PL entry */ + su_size += 12; + } else if (type == 0) { + /* we need to add a RE entry */ + su_size += 4; + } + } + } else if (n->type == ECMA119_SPECIAL) { + if (S_ISBLK(n->node->mode) || S_ISCHR(n->node->mode)) { + /* block or char device, we need a PN entry */ + su_size += 20; + } + } else if (n->type == ECMA119_PLACEHOLDER) { + /* we need the CL entry */ + su_size += 12; + } + + if (type == 0) { + char *name = get_rr_fname(t, n->node->name); + size_t namelen = strlen(name); + free(name); + + /* NM entry */ + if (su_size + 5 + namelen <= space) { + /* ok, it fits in System Use Area */ + su_size += 5 + namelen; + } else { + /* the NM will be divided in a CE */ + namelen = namelen - (space - su_size - 5 - 28); + *ce = 5 + namelen; + su_size = space; + } + if (n->type == ECMA119_SYMLINK) { + /* + * for symlinks, we also need to write the SL + */ + char *dest, *cur, *prev; + size_t sl_len = 5; + int cew = (*ce != 0); /* are we writing to CE? */ + + dest = get_rr_fname(t, ((IsoSymlink*)n->node)->dest); + prev = dest; + cur = strchr(prev, '/'); + while (1) { + size_t clen; + if (cur) { + clen = cur - prev; + } else { + /* last component */ + clen = strlen(prev); + } + + if (clen == 1 && prev[0] == '.') { + clen = 0; + } else if (clen == 2 && prev[0] == '.' && prev[1] == '.') { + clen = 0; + } + + /* flags and len for each component record (RRIP, 4.1.3.1) */ + clen += 2; + + if (!cew) { + /* we are still writing to the SUA */ + if (su_size + sl_len + clen > space) { + /* + * ok, we need a Continuation Area anyway + * TODO this can be handled better, but for now SL + * will be completelly moved into the CA + */ + if (su_size + 28 <= space) { + /* the CE entry fills without reducing NM */ + su_size += 28; + } else { + /* we need to reduce NM */ + *ce = (28 - (space - su_size)) + 5; + su_size = space; + } + cew = 1; + } else { + sl_len += clen; + } + } + if (cew) { + if (sl_len + clen > 255) { + /* we need an additional SL entry */ + if (clen > 250) { + /* + * case 1, component too large to fit in a + * single SL entry. Thus, the component need + * to be divided anyway. + * Note than clen can be up to 255 + 2 = 257. + * + * First, we check how many bytes fit in current + * SL field + */ + int fit = 255 - sl_len - 2; + if (clen - 250 <= fit) { + /* + * the component can be divided between this + * and another SL entry + */ + *ce += 255; /* this SL, full */ + sl_len = 5 + (clen - fit); + } else { + /* + * the component will need a 2rd SL entry in + * any case, so we prefer to don't write + * anything in this SL + */ + *ce += sl_len + 255; + sl_len = 5 + (clen - 250) + 2; + } + } else { + /* case 2, create a new SL entry */ + *ce += sl_len; + sl_len = 5 + clen; + } + } else { + sl_len += clen; + } + } + + if (!cur || cur[1] == '\0') { + /* cur[1] can be \0 if dest ends with '/' */ + break; + } + prev = cur + 1; + cur = strchr(prev, '/'); + } + + free(dest); + + /* and finally write the pending SL field */ + if (!cew) { + /* the whole SL fits into the SUA */ + su_size += sl_len; + } else { + *ce += sl_len; + } + + } + } else { + + /* "." or ".." entry */ + su_size += 5; /* NM field */ + if (type == 1 && n->parent == NULL) { + /* + * "." for root directory + * we need to write SP and ER entries. The first fits in SUA, + * ER needs a Continuation Area, thus we also need a CE entry + */ + su_size += 7 + 28; /* SP + CE */ + *ce = 182; /* ER */ + } + } + + /* + * The System Use field inside the directory record must be padded if + * it is an odd number (ECMA-119, 9.1.13) + */ + su_size += (su_size % 2); + return su_size; +} + +/** + * Free all info in a struct susp_info. + */ +static +void susp_info_free(struct susp_info* susp) +{ + size_t i; + + for (i = 0; i < susp->n_susp_fields; ++i) { + free(susp->susp_fields[i]); + } + free(susp->susp_fields); + + for (i = 0; i < susp->n_ce_susp_fields; ++i) { + free(susp->ce_susp_fields[i]); + } + free(susp->ce_susp_fields); +} + +/** + * Fill a struct susp_info with the RR/SUSP entries needed for a given + * node. + * + * @param type + * 0 normal entry, 1 "." entry for that node (it is a dir), 2 ".." + * for that node (i.e., it will refer to the parent) + * @param space + * Available space in the System Use Area for the directory record. + * @param info + * Pointer to the struct susp_info where the entries will be stored. + * If some entries need to go to a Continuation Area, they will be added + * to the existing ce_susp_fields, and ce_len will be incremented + * propertly. Please ensure ce_block is initialized propertly. + * @return + * 1 success, < 0 error + */ +int rrip_get_susp_fields(Ecma119Image *t, Ecma119Node *n, int type, + size_t space, struct susp_info *info) +{ + int ret; + size_t i; + Ecma119Node *node; + char *name = NULL; + char *dest = NULL; + + if (t == NULL || n == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + if (type < 0 || type > 2 || space < 185) { + /* space min is 255 - 33 - 37 = 185 */ + return ISO_WRONG_ARG_VALUE; + } + + if (type == 2 && n->parent != NULL) { + node = n->parent; + } else { + node = n; + } + + /* space min is 255 - 33 - 37 = 185 + * At the same time, it is always an odd number, but we need to pad it + * propertly to ensure the length of a directory record is a even number + * (ECMA-119, 9.1.13). Thus, in fact the real space is always space - 1 + */ + space--; + + /* + * SP must be the first entry for the "." record of the root directory + * (SUSP, 5.3) + */ + if (type == 1 && n->parent == NULL) { + ret = susp_add_SP(t, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + + /* PX and TF, we are sure they always fit in SUA */ + ret = rrip_add_PX(t, node, info); + if (ret < 0) { + goto add_susp_cleanup; + } + ret = rrip_add_TF(t, node, info); + if (ret < 0) { + goto add_susp_cleanup; + } + + if (n->type == ECMA119_DIR) { + if (n->info.dir->real_parent != NULL) { + /* it is a reallocated entry */ + if (type == 2) { + /* + * we need to add a PL entry + * Note that we pass "n" as parameter, not "node" + */ + ret = rrip_add_PL(t, n, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } else if (type == 0) { + /* we need to add a RE entry */ + ret = rrip_add_RE(t, node, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + } + } else if (n->type == ECMA119_SPECIAL) { + if (S_ISBLK(n->node->mode) || S_ISCHR(n->node->mode)) { + /* block or char device, we need a PN entry */ + ret = rrip_add_PN(t, node, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + } else if (n->type == ECMA119_PLACEHOLDER) { + /* we need the CL entry */ + ret = rrip_add_CL(t, node, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + + if (type == 0) { + size_t sua_free; /* free space in the SUA */ + int nm_type = 0; /* 0 whole entry in SUA, 1 part in CE */ + size_t ce_len = 0; /* len of the CE */ + size_t namelen; + + /* this two are only defined for symlinks */ + uint8_t **comps= NULL; /* components of the SL field */ + size_t n_comp = 0; /* number of components */ + + name = get_rr_fname(t, n->node->name); + namelen = strlen(name); + + sua_free = space - info->suf_len; + + /* NM entry */ + if (5 + namelen <= sua_free) { + /* ok, it fits in System Use Area */ + sua_free -= (5 + namelen); + nm_type = 0; + } else { + /* the NM will be divided in a CE */ + nm_type = 1; + namelen = namelen - (sua_free - 5 - 28); + ce_len = 5 + namelen; + sua_free = 0; + } + if (n->type == ECMA119_SYMLINK) { + /* + * for symlinks, we also need to write the SL + */ + char *cur, *prev; + size_t sl_len = 5; + int cew = (nm_type == 1); /* are we writing to CE? */ + + dest = get_rr_fname(t, ((IsoSymlink*)n->node)->dest); + prev = dest; + cur = strchr(prev, '/'); + while (1) { + size_t clen; + char cflag = 0; /* component flag (RRIP, 4.1.3.1) */ + if (cur) { + clen = cur - prev; + } else { + /* last component */ + clen = strlen(prev); + } + + if (clen == 0) { + /* this refers to the roor directory, '/' */ + cflag = 1 << 3; + } + if (clen == 1 && prev[0] == '.') { + clen = 0; + cflag = 1 << 1; + } else if (clen == 2 && prev[0] == '.' && prev[1] == '.') { + clen = 0; + cflag = 1 << 2; + } + + /* flags and len for each component record (RRIP, 4.1.3.1) */ + clen += 2; + + if (!cew) { + /* we are still writing to the SUA */ + if (sl_len + clen > sua_free) { + /* + * ok, we need a Continuation Area anyway + * TODO this can be handled better, but for now SL + * will be completelly moved into the CA + */ + if (28 <= sua_free) { + /* the CE entry fills without reducing NM */ + sua_free -= 28; + cew = 1; + } else { + /* we need to reduce NM */ + nm_type = 1; + ce_len = (28 - sua_free) + 5; + sua_free = 0; + cew = 1; + } + } else { + /* add the component */ + ret = rrip_SL_append_comp(&n_comp, &comps, prev, + clen - 2, cflag); + if (ret < 0) { + goto add_susp_cleanup; + } + sl_len += clen; + } + } + if (cew) { + if (sl_len + clen > 255) { + /* we need an addition SL entry */ + if (clen > 250) { + /* + * case 1, component too large to fit in a + * single SL entry. Thus, the component need + * to be divided anyway. + * Note than clen can be up to 255 + 2 = 257. + * + * First, we check how many bytes fit in current + * SL field + */ + int fit = 255 - sl_len - 2; + if (clen - 250 <= fit) { + /* + * the component can be divided between this + * and another SL entry + */ + ret = rrip_SL_append_comp(&n_comp, &comps, + prev, fit, 0x01); + if (ret < 0) { + goto add_susp_cleanup; + } + /* + * and another component, that will go in + * other SL entry + */ + ret = rrip_SL_append_comp(&n_comp, &comps, prev + + fit, clen - fit - 2, 0); + if (ret < 0) { + goto add_susp_cleanup; + } + ce_len += 255; /* this SL, full */ + sl_len = 5 + (clen - fit); + } else { + /* + * the component will need a 2rd SL entry in + * any case, so we prefer to don't write + * anything in this SL + */ + ret = rrip_SL_append_comp(&n_comp, &comps, + prev, 248, 0x01); + if (ret < 0) { + goto add_susp_cleanup; + } + ret = rrip_SL_append_comp(&n_comp, &comps, prev + + 248, strlen(prev + 248), 0x00); + if (ret < 0) { + goto add_susp_cleanup; + } + ce_len += sl_len + 255; + sl_len = 5 + (clen - 250) + 2; + } + } else { + /* case 2, create a new SL entry */ + ret = rrip_SL_append_comp(&n_comp, &comps, prev, + clen - 2, cflag); + if (ret < 0) { + goto add_susp_cleanup; + } + ce_len += sl_len; + sl_len = 5 + clen; + } + } else { + /* the component fit in the SL entry */ + ret = rrip_SL_append_comp(&n_comp, &comps, prev, + clen - 2, cflag); + if (ret < 0) { + goto add_susp_cleanup; + } + sl_len += clen; + } + } + + if (!cur || cur[1] == '\0') { + /* cur[1] can be \0 if dest ends with '/' */ + break; + } + prev = cur + 1; + cur = strchr(prev, '/'); + } + + if (cew) { + ce_len += sl_len; + } + } + + /* + * We we reach here: + * - We know if NM fill in the SUA (nm_type == 0) + * - If SL needs an to be written in CE (ce_len > 0) + * - The components for SL entry (or entries) + */ + + if (nm_type == 0) { + /* the full NM fills in SUA */ + ret = rrip_add_NM(t, info, name, strlen(name), 0, 0); + if (ret < 0) { + goto add_susp_cleanup; + } + } else { + /* + * Write the NM part that fits in SUA... Note that CE + * entry and NM in the continuation area is added below + */ + namelen = space - info->suf_len - 28 - 5; + ret = rrip_add_NM(t, info, name, namelen, 1, 0); + if (ret < 0) { + goto add_susp_cleanup; + } + } + + if (ce_len > 0) { + /* Add the CE entry */ + ret = susp_add_CE(t, ce_len, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + + if (nm_type == 1) { + /* + * ..and the part that goes to continuation area. + */ + ret = rrip_add_NM(t, info, name + namelen, strlen(name + namelen), + 0, 1); + if (ret < 0) { + goto add_susp_cleanup; + } + } + + if (n->type == ECMA119_SYMLINK) { + + /* add the SL entry (or entries) */ + ret = rrip_add_SL(t, info, comps, n_comp, (ce_len > 0)); + + /* free the components */ + for (i = 0; i < n_comp; i++) { + free(comps[i]); + } + free(comps); + + if (ret < 0) { + goto add_susp_cleanup; + } + } + + } else { + + /* "." or ".." entry */ + + /* write the NM entry */ + ret = rrip_add_NM(t, info, NULL, 0, 1 << type, 0); + if (ret < 0) { + goto add_susp_cleanup; + } + if (type == 1 && n->parent == NULL) { + /* + * "." for root directory + * we need to write SP and ER entries. The first fits in SUA, + * ER needs a Continuation Area, thus we also need a CE entry. + * Note that SP entry was already added above + */ + ret = susp_add_CE(t, 182, info); /* 182 is ER length */ + if (ret < 0) { + goto add_susp_cleanup; + } + ret = rrip_add_ER(t, info); + if (ret < 0) { + goto add_susp_cleanup; + } + } + } + + /* + * The System Use field inside the directory record must be padded if + * it is an odd number (ECMA-119, 9.1.13) + */ + info->suf_len += (info->suf_len % 2); + + free(name); + free(dest); + return ISO_SUCCESS; + + add_susp_cleanup: ; + free(name); + free(dest); + susp_info_free(info); + return ret; +} + +/** + * Write the given SUSP fields into buf. Note that Continuation Area + * fields are not written. + * If info does not contain any SUSP entry this function just return. + * After written, the info susp_fields array will be freed, and the counters + * updated propertly. + */ +void rrip_write_susp_fields(Ecma119Image *t, struct susp_info *info, + uint8_t *buf) +{ + size_t i; + size_t pos = 0; + + if (info->n_susp_fields == 0) { + return; + } + + for (i = 0; i < info->n_susp_fields; i++) { + memcpy(buf + pos, info->susp_fields[i], info->susp_fields[i][2]); + pos += info->susp_fields[i][2]; + } + + /* free susp_fields */ + for (i = 0; i < info->n_susp_fields; ++i) { + free(info->susp_fields[i]); + } + free(info->susp_fields); + info->susp_fields = NULL; + info->n_susp_fields = 0; + info->suf_len = 0; +} + +/** + * Write the Continuation Area entries for the given struct susp_info, using + * the iso_write() function. + * After written, the ce_susp_fields array will be freed. + */ +int rrip_write_ce_fields(Ecma119Image *t, struct susp_info *info) +{ + size_t i; + uint8_t padding[BLOCK_SIZE]; + int ret= ISO_SUCCESS; + + if (info->n_ce_susp_fields == 0) { + return ret; + } + + for (i = 0; i < info->n_ce_susp_fields; i++) { + ret = iso_write(t, info->ce_susp_fields[i], + info->ce_susp_fields[i][2]); + if (ret < 0) { + goto write_ce_field_cleanup; + } + } + + /* pad continuation area until block size */ + i = BLOCK_SIZE - (info->ce_len % BLOCK_SIZE); + if (i > 0 && i < BLOCK_SIZE) { + memset(padding, 0, i); + ret = iso_write(t, padding, i); + } + + write_ce_field_cleanup: ; + /* free ce_susp_fields */ + for (i = 0; i < info->n_ce_susp_fields; ++i) { + free(info->ce_susp_fields[i]); + } + free(info->ce_susp_fields); + info->ce_susp_fields = NULL; + info->n_ce_susp_fields = 0; + info->ce_len = 0; + return ret; +} + diff --git a/libisofs/rockridge.h b/libisofs/rockridge.h new file mode 100644 index 0000000..c21a954 --- /dev/null +++ b/libisofs/rockridge.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/** + * This header defines the functions and structures needed to add RockRidge + * extensions to an ISO image. + * + * References: + * + * - SUSP (IEEE 1281). + * System Use Sharing Protocol, draft standard version 1.12. + * + * - RRIP (IEEE 1282) + * Rock Ridge Interchange Protocol, Draft Standard version 1.12. + * + * - ECMA-119 (ISO-9660) + * Volume and File Structure of CDROM for Information Interchange. + */ + +#ifndef LIBISO_ROCKRIDGE_H +#define LIBISO_ROCKRIDGE_H + +#include "ecma119.h" + +#define SUSP_SIG(entry, a, b) ((entry->sig[0] == a) && (entry->sig[1] == b)) + +/** + * This contains the information about the System Use Fields (SUSP, 4.1), + * that will be written in the System Use Areas, both in the ISO directory + * record System Use field (ECMA-119, 9.1.13) or in a Continuation Area as + * defined by SUSP. + */ +struct susp_info +{ + /** Number of SUSP fields in the System Use field */ + size_t n_susp_fields; + uint8_t **susp_fields; + + /** Length of the part of the SUSP area that fits in the dirent. */ + int suf_len; + + /** Length of the part of the SUSP area that will go in a CE area. */ + uint32_t ce_block; + uint32_t ce_len; + + size_t n_ce_susp_fields; + uint8_t **ce_susp_fields; +}; + +/* SUSP 5.1 */ +struct susp_CE { + uint8_t block[8]; + uint8_t offset[8]; + uint8_t len[8]; +}; + +/* SUSP 5.3 */ +struct susp_SP { + uint8_t be[1]; + uint8_t ef[1]; + uint8_t len_skp[1]; +}; + +/* SUSP 5.5 */ +struct susp_ER { + uint8_t len_id[1]; + uint8_t len_des[1]; + uint8_t len_src[1]; + uint8_t ext_ver[1]; + uint8_t ext_id[1]; /*< up to len_id bytes */ + /* ext_des, ext_src */ +}; + +/** POSIX file attributes (RRIP, 4.1.1) */ +struct rr_PX { + uint8_t mode[8]; + uint8_t links[8]; + uint8_t uid[8]; + uint8_t gid[8]; + uint8_t serial[8]; +}; + +/** Time stamps for a file (RRIP, 4.1.6) */ +struct rr_TF { + uint8_t flags[1]; + uint8_t t_stamps[1]; +}; + +/** Info for character and block device (RRIP, 4.1.2) */ +struct rr_PN { + uint8_t high[8]; + uint8_t low[8]; +}; + +/** Alternate name (RRIP, 4.1.4) */ +struct rr_NM { + uint8_t flags[1]; + uint8_t name[1]; +}; + +/** Link for a relocated directory (RRIP, 4.1.5.1) */ +struct rr_CL { + uint8_t child_loc[8]; +}; + +/** Sim link (RRIP, 4.1.3) */ +struct rr_SL { + uint8_t flags[1]; + uint8_t comps[1]; +}; + +/** + * Struct for a SUSP System User Entry (SUSP, 4.1) + */ +struct susp_sys_user_entry +{ + uint8_t sig[2]; + uint8_t len_sue[1]; + uint8_t version[1]; + union { + struct susp_CE CE; + struct susp_SP SP; + struct susp_ER ER; + struct rr_PX PX; + struct rr_TF TF; + struct rr_PN PN; + struct rr_NM NM; + struct rr_CL CL; + struct rr_SL SL; + } data; /* 5 to 4+len_sue */ +}; + +/** + * Compute the length needed for write all RR and SUSP entries for a given + * node. + * + * @param type + * 0 normal entry, 1 "." entry for that node (it is a dir), 2 ".." + * for that node (i.e., it will refer to the parent) + * @param space + * Available space in the System Use Area for the directory record. + * @param ce + * Will be filled with the space needed in a CE + * @return + * The size needed for the RR entries in the System Use Area + */ +size_t rrip_calc_len(Ecma119Image *t, Ecma119Node *n, int type, size_t space, + size_t *ce); + +/** + * Fill a struct susp_info with the RR/SUSP entries needed for a given + * node. + * + * @param type + * 0 normal entry, 1 "." entry for that node (it is a dir), 2 ".." + * for that node (i.e., it will refer to the parent) + * @param space + * Available space in the System Use Area for the directory record. + * @param info + * Pointer to the struct susp_info where the entries will be stored. + * If some entries need to go to a Continuation Area, they will be added + * to the existing ce_susp_fields, and ce_len will be incremented + * propertly. Please ensure ce_block is initialized propertly. + * @return + * 1 success, < 0 error + */ +int rrip_get_susp_fields(Ecma119Image *t, Ecma119Node *n, int type, + size_t space, struct susp_info *info); + +/** + * Write the given SUSP fields into buf. Note that Continuation Area + * fields are not written. + * If info does not contain any SUSP entry this function just return. + * After written, the info susp_fields array will be freed, and the counters + * updated propertly. + */ +void rrip_write_susp_fields(Ecma119Image *t, struct susp_info *info, + uint8_t *buf); + +/** + * Write the Continuation Area entries for the given struct susp_info, using + * the iso_write() function. + * After written, the ce_susp_fields array will be freed. + */ +int rrip_write_ce_fields(Ecma119Image *t, struct susp_info *info); + +/** + * The SUSP iterator is used to iterate over the System User Entries + * of a ECMA-168 directory record. + * It takes care about Continuation Areas, handles the end of the different + * system user entries and skip padding areas. Thus, using an iteration + * we are accessing just to the meaning entries. + */ +typedef struct susp_iterator SuspIterator; + +SuspIterator * +susp_iter_new(IsoDataSource *src, struct ecma119_dir_record *record, + uint8_t len_skp, int msgid); + +/** + * Get the next SUSP System User Entry using given iterator. + * + * @param sue + * Pointer to the next susp entry. It refers to an internal buffer and + * it's not guaranteed to be allocated after calling susp_iter_next() + * again. Thus, if you need to keep some entry you have to do a copy. + * @return + * 1 on success, 0 if no more entries, < 0 error + */ +int susp_iter_next(SuspIterator *iter, struct susp_sys_user_entry **sue); + +/** + * Free a given susp iterator. + */ +void susp_iter_free(SuspIterator *iter); + + +/** + * Fills a struct stat with the values of a Rock Ridge PX entry (RRIP, 4.1.1). + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_PX(struct susp_sys_user_entry *px, struct stat *st); + +/** + * Fills a struct stat with the values of a Rock Ridge TF entry (RRIP, 4.1.6) + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_TF(struct susp_sys_user_entry *tf, struct stat *st); + +/** + * Read a RR NM entry (RRIP, 4.1.4), and appends the name stored there to + * the given name. You can pass a pointer to NULL as name. + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_NM(struct susp_sys_user_entry *nm, char **name, int *cont); + +/** + * Read a SL RR entry (RRIP, 4.1.3), checking if the destination continues. + * + * @param cont + * 0 not continue, 1 continue, 2 continue component + * @return + * 1 on success, < 0 on error + */ +int read_rr_SL(struct susp_sys_user_entry *sl, char **dest, int *cont); + +/** + * Fills a struct stat with the values of a Rock Ridge PN entry (RRIP, 4.1.2). + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_PN(struct susp_sys_user_entry *pn, struct stat *st); + +#endif /* LIBISO_ROCKRIDGE_H */ diff --git a/libisofs/rockridge_read.c b/libisofs/rockridge_read.c new file mode 100644 index 0000000..02dbc86 --- /dev/null +++ b/libisofs/rockridge_read.c @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * This file contains functions related to the reading of SUSP and + * Rock Ridge extensions on an ECMA-119 image. + */ + +#include "libisofs.h" +#include "ecma119.h" +#include "util.h" +#include "rockridge.h" +#include "messages.h" + +#include +#include +#include + +struct susp_iterator +{ + uint8_t* base; + int pos; + int size; + IsoDataSource *src; + int msgid; + + /* block and offset for next continuation area */ + uint32_t ce_block; + uint32_t ce_off; + + /** Length of the next continuation area, 0 if no more CA are specified */ + uint32_t ce_len; + + uint8_t *buffer; /*< If there are continuation areas */ +}; + +SuspIterator* +susp_iter_new(IsoDataSource *src, struct ecma119_dir_record *record, + uint8_t len_skp, int msgid) +{ + int pad = (record->len_fi[0] + 1) % 2; + struct susp_iterator *iter = malloc(sizeof(struct susp_iterator)); + if (iter == NULL) { + return NULL; + } + + iter->base = record->file_id + record->len_fi[0] + pad; + iter->pos = len_skp; /* 0 in most cases */ + iter->size = record->len_dr[0] - record->len_fi[0] - 33 - pad; + iter->src = src; + iter->msgid = msgid; + + iter->ce_len = 0; + iter->buffer = NULL; + + return iter; +} + +int susp_iter_next(SuspIterator *iter, struct susp_sys_user_entry **sue) +{ + struct susp_sys_user_entry *entry; + + entry = (struct susp_sys_user_entry*)(iter->base + iter->pos); + + if ( (iter->pos + 4 > iter->size) || (SUSP_SIG(entry, 'S', 'T'))) { + + /* + * End of the System Use Area or Continuation Area. + * Note that ST is not needed when the space left is less than 4. + * (IEEE 1281, SUSP. section 4) + */ + if (iter->ce_len) { + uint32_t block; + int nblocks; + + /* A CE has found, there is another continuation area */ + nblocks = DIV_UP(iter->ce_off + iter->ce_len, BLOCK_SIZE); + iter->buffer = realloc(iter->buffer, nblocks * BLOCK_SIZE); + + /* read all blocks needed to cache the full CE */ + for (block = 0; block < nblocks; ++block) { + int ret; + ret = iter->src->read_block(iter->src, iter->ce_block + block, + iter->buffer + block * BLOCK_SIZE); + if (ret < 0) { + return ret; + } + } + iter->base = iter->buffer + iter->ce_off; + iter->pos = 0; + iter->size = iter->ce_len; + iter->ce_len = 0; + entry = (struct susp_sys_user_entry*)iter->base; + } else { + return 0; + } + } + + if (entry->len_sue[0] == 0) { + /* a wrong image with this lead us to a infinity loop */ + iso_msg_submit(iter->msgid, ISO_WRONG_RR, 0, + "Damaged RR/SUSP information."); + return ISO_WRONG_RR; + } + + iter->pos += entry->len_sue[0]; + + if (SUSP_SIG(entry, 'C', 'E')) { + /* Continuation entry */ + if (iter->ce_len) { + int ret; + ret = iso_msg_submit(iter->msgid, ISO_UNSUPPORTED_SUSP, 0, + "More than one CE System user entry has found in a single " + "System Use field or continuation area. This breaks SUSP " + "standard and it's not supported. Ignoring last CE. Maybe " + "the image is damaged."); + if (ret < 0) { + return ret; + } + } else { + iter->ce_block = iso_read_bb(entry->data.CE.block, 4, NULL); + iter->ce_off = iso_read_bb(entry->data.CE.offset, 4, NULL); + iter->ce_len = iso_read_bb(entry->data.CE.len, 4, NULL); + } + + /* we don't want to return CE entry to the user */ + return susp_iter_next(iter, sue); + } else if (SUSP_SIG(entry, 'P', 'D')) { + /* skip padding */ + return susp_iter_next(iter, sue); + } + + *sue = entry; + return ISO_SUCCESS; +} + +void susp_iter_free(SuspIterator *iter) +{ + free(iter->buffer); + free(iter); +} + +/** + * Fills a struct stat with the values of a Rock Ridge PX entry (RRIP, 4.1.1). + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_PX(struct susp_sys_user_entry *px, struct stat *st) +{ + if (px == NULL || st == NULL) { + return ISO_NULL_POINTER; + } + if (px->sig[0] != 'P' || px->sig[1] != 'X') { + return ISO_WRONG_ARG_VALUE; + } + + if (px->len_sue[0] != 44 && px->len_sue[0] != 36) { + return ISO_WRONG_RR; + } + + st->st_mode = iso_read_bb(px->data.PX.mode, 4, NULL); + st->st_nlink = iso_read_bb(px->data.PX.links, 4, NULL); + st->st_uid = iso_read_bb(px->data.PX.uid, 4, NULL); + st->st_gid = iso_read_bb(px->data.PX.gid, 4, NULL); + if (px->len_sue[0] == 44) { + /* this corresponds to RRIP 1.12, so we have inode serial number */ + st->st_ino = iso_read_bb(px->data.PX.serial, 4, NULL); + } + return ISO_SUCCESS; +} + +/** + * Fills a struct stat with the values of a Rock Ridge TF entry (RRIP, 4.1.6) + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_TF(struct susp_sys_user_entry *tf, struct stat *st) +{ + time_t time; + int s; + int nts = 0; + + if (tf == NULL || st == NULL) { + return ISO_NULL_POINTER; + } + if (tf->sig[0] != 'T' || tf->sig[1] != 'F') { + return ISO_WRONG_ARG_VALUE; + } + + if (tf->data.TF.flags[0] & (1 << 7)) { + /* long form */ + s = 17; + } else { + s = 7; + } + + /* 1. Creation time */ + if (tf->data.TF.flags[0] & (1 << 0)) { + + /* the creation is the recording time. we ignore this */ + /* TODO maybe it would be good to manage it in ms discs, where + * the recording time could be different than now!! */ + ++nts; + } + + /* 2. modify time */ + if (tf->data.TF.flags[0] & (1 << 1)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + /* RR TF entry too short. */ + return ISO_WRONG_RR; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_mtime = time; + ++nts; + } + + /* 3. access time */ + if (tf->data.TF.flags[0] & (1 << 2)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + /* RR TF entry too short. */ + return ISO_WRONG_RR; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_atime = time; + ++nts; + } + + /* 4. attributes time */ + if (tf->data.TF.flags[0] & (1 << 3)) { + if (tf->len_sue[0] < 5 + (nts+1) * s) { + /* RR TF entry too short. */ + return ISO_WRONG_RR; + } + if (s == 7) { + time = iso_datetime_read_7(&tf->data.TF.t_stamps[nts*7]); + } else { + time = iso_datetime_read_17(&tf->data.TF.t_stamps[nts*17]); + } + st->st_ctime = time; + ++nts; + } + + /* we ignore backup, expire and effect times */ + + return ISO_SUCCESS; +} + +/** + * Read a RR NM entry (RRIP, 4.1.4), and appends the name stored there to + * the given name. You can pass a pointer to NULL as name. + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_NM(struct susp_sys_user_entry *nm, char **name, int *cont) +{ + if (nm == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + if (nm->sig[0] != 'N' || nm->sig[1] != 'M') { + return ISO_WRONG_ARG_VALUE; + } + + if (nm->len_sue[0] == 5) { + if (nm->data.NM.flags[0] & 0x2) { + /* it is a "." entry */ + if (*name == NULL) { + return ISO_SUCCESS; + } else { + /* we can't have a previous not-NULL name */ + return ISO_WRONG_RR; + } + } + } + + if (nm->len_sue[0] <= 5) { + /* ".." entry is an error, as we will never call it */ + return ISO_WRONG_RR; + } + + /* concatenate the results */ + if (*cont) { + *name = realloc(*name, strlen(*name) + nm->len_sue[0] - 5 + 1); + strncat(*name, (char*)nm->data.NM.name, nm->len_sue[0] - 5); + } else { + *name = strcopy((char*)nm->data.NM.name, nm->len_sue[0] - 5); + } + if (*name == NULL) { + return ISO_OUT_OF_MEM; + } + + /* and set cond according to the value of CONTINUE flag */ + *cont = nm->data.NM.flags[0] & 0x01; + return ISO_SUCCESS; +} + +/** + * Read a SL RR entry (RRIP, 4.1.3), checking if the destination continues. + * + * @param cont + * 0 not continue, 1 continue, 2 continue component + * @return + * 1 on success, < 0 on error + */ +int read_rr_SL(struct susp_sys_user_entry *sl, char **dest, int *cont) +{ + int pos; + + if (sl == NULL || dest == NULL) { + return ISO_NULL_POINTER; + } + if (sl->sig[0] != 'S' || sl->sig[1] != 'L') { + return ISO_WRONG_ARG_VALUE; + } + + for (pos = 0; pos + 5 < sl->len_sue[0]; + pos += 2 + sl->data.SL.comps[pos + 1]) { + char *comp; + uint8_t len; + uint8_t flags = sl->data.SL.comps[pos]; + + if (flags & 0x2) { + /* current directory */ + len = 1; + comp = "."; + } else if (flags & 0x4) { + /* parent directory */ + len = 2; + comp = ".."; + } else if (flags & 0x8) { + /* root directory */ + len = 1; + comp = "/"; + } else if (flags & ~0x01) { + /* unsupported flag component */ + return ISO_UNSUPPORTED_RR; + } else { + len = sl->data.SL.comps[pos + 1]; + comp = (char*)&sl->data.SL.comps[pos + 2]; + } + + if (*cont == 1) { + /* new component */ + size_t size = strlen(*dest); + *dest = realloc(*dest, strlen(*dest) + len + 2); + if (*dest == NULL) { + return ISO_OUT_OF_MEM; + } + /* it is a new compoenent, add the '/' */ + if ((*dest)[size-1] != '/') { + (*dest)[size] = '/'; + (*dest)[size+1] = '\0'; + } + strncat(*dest, comp, len); + } else if (*cont == 2) { + /* the component continues */ + *dest = realloc(*dest, strlen(*dest) + len + 1); + if (*dest == NULL) { + return ISO_OUT_OF_MEM; + } + /* we don't have to add the '/' */ + strncat(*dest, comp, len); + } else { + *dest = strcopy(comp, len); + } + if (*dest == NULL) { + return ISO_OUT_OF_MEM; + } + /* do the component continue or not? */ + *cont = (flags & 0x01) ? 2 : 1; + } + + if (*cont == 2) { + /* TODO check that SL flag is set to continute too ?*/ + } else { + *cont = sl->data.SL.flags[0] & 0x1 ? 1 : 0; + } + + return ISO_SUCCESS; +} + +/** + * Fills a struct stat with the values of a Rock Ridge PN entry (RRIP, 4.1.2). + * + * @return + * 1 on success, < 0 on error + */ +int read_rr_PN(struct susp_sys_user_entry *pn, struct stat *st) +{ + if (pn == NULL || pn == NULL) { + return ISO_NULL_POINTER; + } + if (pn->sig[0] != 'P' || pn->sig[1] != 'N') { + return ISO_WRONG_ARG_VALUE; + } + + if (pn->len_sue[0] != 20) { + return ISO_WRONG_RR; + } + + st->st_rdev = (dev_t)((dev_t)iso_read_bb(pn->data.PN.high, 4, NULL) << 32) + || (dev_t)iso_read_bb(pn->data.PN.low, 4, NULL); + return ISO_SUCCESS; +} diff --git a/libisofs/stream.c b/libisofs/stream.c new file mode 100644 index 0000000..710ed8f --- /dev/null +++ b/libisofs/stream.c @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "libisofs.h" +#include "stream.h" +#include "fsource.h" +#include "util.h" + +#include +#include + +ino_t serial_id = (ino_t)1; +ino_t mem_serial_id = (ino_t)1; + +typedef struct +{ + IsoFileSource *src; + + /* key for file identification inside filesystem */ + dev_t dev_id; + ino_t ino_id; + off_t size; /**< size of this file */ +} FSrcStreamData; + +static +int fsrc_open(IsoStream *stream) +{ + int ret; + struct stat info; + off_t esize; + IsoFileSource *src; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + src = ((FSrcStreamData*)stream->data)->src; + ret = iso_file_source_stat(src, &info); + if (ret < 0) { + return ret; + } + ret = iso_file_source_open(src); + if (ret < 0) { + return ret; + } + esize = ((FSrcStreamData*)stream->data)->size; + if (info.st_size == esize) { + return ISO_SUCCESS; + } else { + return (esize > info.st_size) ? 3 : 2; + } +} + +static +int fsrc_close(IsoStream *stream) +{ + IsoFileSource *src; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + src = ((FSrcStreamData*)stream->data)->src; + return iso_file_source_close(src); +} + +static +off_t fsrc_get_size(IsoStream *stream) +{ + FSrcStreamData *data; + data = (FSrcStreamData*)stream->data; + + return data->size; +} + +static +int fsrc_read(IsoStream *stream, void *buf, size_t count) +{ + IsoFileSource *src; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + src = ((FSrcStreamData*)stream->data)->src; + return iso_file_source_read(src, buf, count); +} + +static +int fsrc_is_repeatable(IsoStream *stream) +{ + int ret; + struct stat info; + FSrcStreamData *data; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + data = (FSrcStreamData*)stream->data; + + /* mode is not cached, this function is only useful for filters */ + ret = iso_file_source_stat(data->src, &info); + if (ret < 0) { + return ret; + } + if (S_ISREG(info.st_mode) || S_ISBLK(info.st_mode)) { + return 1; + } else { + return 0; + } +} + +static +void fsrc_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id) +{ + FSrcStreamData *data; + IsoFilesystem *fs; + + data = (FSrcStreamData*)stream->data; + fs = iso_file_source_get_filesystem(data->src); + + *fs_id = fs->get_id(fs); + *dev_id = data->dev_id; + *ino_id = data->ino_id; +} + +static +char *fsrc_get_name(IsoStream *stream) +{ + FSrcStreamData *data; + data = (FSrcStreamData*)stream->data; + return iso_file_source_get_path(data->src); +} + +static +void fsrc_free(IsoStream *stream) +{ + FSrcStreamData *data; + data = (FSrcStreamData*)stream->data; + iso_file_source_unref(data->src); + free(data); +} + +IsoStreamIface fsrc_stream_class = { + fsrc_open, + fsrc_close, + fsrc_get_size, + fsrc_read, + fsrc_is_repeatable, + fsrc_get_id, + fsrc_get_name, + fsrc_free +}; + +int iso_file_source_stream_new(IsoFileSource *src, IsoStream **stream) +{ + int r; + struct stat info; + IsoStream *str; + FSrcStreamData *data; + + if (src == NULL || stream == NULL) { + return ISO_NULL_POINTER; + } + + r = iso_file_source_stat(src, &info); + if (r < 0) { + return r; + } + if (S_ISDIR(info.st_mode)) { + return ISO_FILE_IS_DIR; + } + + /* check for read access to contents */ + r = iso_file_source_access(src); + if (r < 0) { + return r; + } + + str = malloc(sizeof(IsoStream)); + if (str == NULL) { + return ISO_OUT_OF_MEM; + } + data = malloc(sizeof(FSrcStreamData)); + if (str == NULL) { + free(str); + return ISO_OUT_OF_MEM; + } + + /* take the ref to IsoFileSource */ + data->src = src; + data->size = info.st_size; + + /* get the id numbers */ + { + IsoFilesystem *fs; + unsigned int fs_id; + fs = iso_file_source_get_filesystem(data->src); + + fs_id = fs->get_id(fs); + if (fs_id == 0) { + /* + * the filesystem implementation is unable to provide valid + * st_dev and st_ino fields. Use serial_id. + */ + data->dev_id = (dev_t) 0; + data->ino_id = serial_id++; + } else { + data->dev_id = info.st_dev; + data->ino_id = info.st_ino; + } + } + + str->refcount = 1; + str->data = data; + str->class = &fsrc_stream_class; + + *stream = str; + return ISO_SUCCESS; +} + + + +typedef struct +{ + uint8_t *buf; + ssize_t offset; /* -1 if stream closed */ + ino_t ino_id; + size_t size; +} MemStreamData; + +static +int mem_open(IsoStream *stream) +{ + MemStreamData *data; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + data = (MemStreamData*)stream->data; + if (data->offset != -1) { + return ISO_FILE_ALREADY_OPENNED; + } + data->offset = 0; + return ISO_SUCCESS; +} + +static +int mem_close(IsoStream *stream) +{ + MemStreamData *data; + if (stream == NULL) { + return ISO_NULL_POINTER; + } + data = (MemStreamData*)stream->data; + if (data->offset == -1) { + return ISO_FILE_NOT_OPENNED; + } + data->offset = -1; + return ISO_SUCCESS; +} + +static +off_t mem_get_size(IsoStream *stream) +{ + MemStreamData *data; + data = (MemStreamData*)stream->data; + + return (off_t)data->size; +} + +static +int mem_read(IsoStream *stream, void *buf, size_t count) +{ + size_t len; + MemStreamData *data; + if (stream == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + if (count == 0) { + return ISO_WRONG_ARG_VALUE; + } + data = stream->data; + + if (data->offset == -1) { + return ISO_FILE_NOT_OPENNED; + } + + if (data->offset >= data->size) { + return 0; /* EOF */ + } + + len = MIN(count, data->size - data->offset); + memcpy(buf, data->buf + data->offset, len); + data->offset += len; + return len; +} + +static +int mem_is_repeatable(IsoStream *stream) +{ + return 1; +} + +static +void mem_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id) +{ + MemStreamData *data; + data = (MemStreamData*)stream->data; + *fs_id = ISO_MEM_FS_ID; + *dev_id = 0; + *ino_id = data->ino_id; +} + +static +char *mem_get_name(IsoStream *stream) +{ + return strdup("[MEMORY SOURCE]"); +} + +static +void mem_free(IsoStream *stream) +{ + MemStreamData *data; + data = (MemStreamData*)stream->data; + free(data->buf); + free(data); +} + +IsoStreamIface mem_stream_class = { + mem_open, + mem_close, + mem_get_size, + mem_read, + mem_is_repeatable, + mem_get_id, + mem_get_name, + mem_free +}; + +/** + * Create a stream for reading from a arbitrary memory buffer. + * When the Stream refcount reach 0, the buffer is free(3). + * + * @return + * 1 sucess, < 0 error + */ +int iso_memory_stream_new(unsigned char *buf, size_t size, IsoStream **stream) +{ + IsoStream *str; + MemStreamData *data; + + if (buf == NULL || stream == NULL) { + return ISO_NULL_POINTER; + } + + str = malloc(sizeof(IsoStream)); + if (str == NULL) { + return ISO_OUT_OF_MEM; + } + data = malloc(sizeof(MemStreamData)); + if (str == NULL) { + free(str); + return ISO_OUT_OF_MEM; + } + + /* fill data */ + data->buf = buf; + data->size = size; + data->offset = -1; + data->ino_id = mem_serial_id++; + + str->refcount = 1; + str->data = data; + str->class = &mem_stream_class; + + *stream = str; + return ISO_SUCCESS; +} + +void iso_stream_ref(IsoStream *stream) +{ + ++stream->refcount; +} + +void iso_stream_unref(IsoStream *stream) +{ + if (--stream->refcount == 0) { + stream->class->free(stream); + free(stream); + } +} + +inline +int iso_stream_open(IsoStream *stream) +{ + return stream->class->open(stream); +} + +inline +int iso_stream_close(IsoStream *stream) +{ + return stream->class->close(stream); +} + +inline +off_t iso_stream_get_size(IsoStream *stream) +{ + return stream->class->get_size(stream); +} + +inline +int iso_stream_read(IsoStream *stream, void *buf, size_t count) +{ + return stream->class->read(stream, buf, count); +} + +inline +int iso_stream_is_repeatable(IsoStream *stream) +{ + return stream->class->is_repeatable(stream); +} + +inline +void iso_stream_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id) +{ + stream->class->get_id(stream, fs_id, dev_id, ino_id); +} + +inline +char *iso_stream_get_name(IsoStream *stream) +{ + return stream->class->get_name(stream); +} diff --git a/libisofs/stream.h b/libisofs/stream.h new file mode 100644 index 0000000..1261b37 --- /dev/null +++ b/libisofs/stream.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_STREAM_H_ +#define LIBISO_STREAM_H_ + +/* + * Definitions of streams. + */ +#include "fsource.h" + +/** + * serial number to be used when you can't get a valid id for a Stream by other + * means. If you use this, both fs_id and dev_id should be set to 0. + * This must be incremented each time you get a reference to it. + */ +extern ino_t serial_id; + +/* + * Some functions here will be moved to libisofs.h when we expose + * Streams. + */ + +typedef struct Iso_Stream IsoStream; + +typedef struct IsoStream_Iface +{ + /** + * Opens the stream. + * + * @return + * 1 on success, 2 file greater than expected, 3 file smaller than + * expected, < 0 on error + */ + int (*open)(IsoStream *stream); + + /** + * Close the Stream. + * @return 1 on success, < 0 on error + */ + int (*close)(IsoStream *stream); + + /** + * Get the size (in bytes) of the stream. This function should always + * return the same size, even if the underlying source size changes. + */ + off_t (*get_size)(IsoStream *stream); + + /** + * Attempts to read up to count bytes from the given stream into + * the buffer starting at buf. + * + * The stream must be open() before calling this, and close() when no + * more needed. + * + * @return + * number of bytes read, 0 if EOF, < 0 on error + */ + int (*read)(IsoStream *stream, void *buf, size_t count); + + /** + * Whether this Stram can be read several times, with the same results. + * For example, a regular file is repeatable, you can read it as many + * times as you want. However, a pipe isn't. + * + * This function doesn't take into account if the file has been modified + * between the two reads. + * + * @return + * 1 if stream is repeatable, 0 if not, < 0 on error + */ + int (*is_repeatable)(IsoStream *stream); + + /** + * Get an unique identifier for the IsoStream. + */ + void (*get_id)(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id); + + /** + * Get a name that identifies the Stream contents. It is used only for + * informational or debug purposes, so you can return anything you + * consider suitable for identification of the source, such as the path. + */ + char *(*get_name)(IsoStream *stream); + + /** + * Free implementation specific data. Should never be called by user. + * Use iso_stream_unref() instead. + */ + void (*free)(IsoStream *stream); +} IsoStreamIface; + +struct Iso_Stream +{ + IsoStreamIface *class; + int refcount; + void *data; +}; + +void iso_stream_ref(IsoStream *stream); +void iso_stream_unref(IsoStream *stream); + +int iso_stream_open(IsoStream *stream); + +int iso_stream_close(IsoStream *stream); + +off_t iso_stream_get_size(IsoStream *stream); + +int iso_stream_read(IsoStream *stream, void *buf, size_t count); + +int iso_stream_is_repeatable(IsoStream *stream); + +void iso_stream_get_id(IsoStream *stream, unsigned int *fs_id, dev_t *dev_id, + ino_t *ino_id); + +char *iso_stream_get_name(IsoStream *stream); + +/** + * Create a stream to read from a IsoFileSource. + * The stream will take the ref. to the IsoFileSource, so after a successfully + * exectution of this function, you musn't unref() the source, unless you + * take an extra ref. + * + * @return + * 1 sucess, < 0 error + * Possible errors: + * + */ +int iso_file_source_stream_new(IsoFileSource *src, IsoStream **stream); + +/** + * Create a stream for reading from a arbitrary memory buffer. + * When the Stream refcount reach 0, the buffer is free(3). + * + * @return + * 1 sucess, < 0 error + */ +int iso_memory_stream_new(unsigned char *buf, size_t size, IsoStream **stream); + +#endif /*STREAM_H_*/ diff --git a/libisofs/tree.c b/libisofs/tree.c new file mode 100644 index 0000000..5dc360e --- /dev/null +++ b/libisofs/tree.c @@ -0,0 +1,751 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +/* + * Functions that act on the iso tree. + */ + +#include "libisofs.h" +#include "node.h" +#include "image.h" +#include "fsource.h" +#include "builder.h" +#include "messages.h" +#include "tree.h" + +#include +#include +#include +#include +#include +#include + +/** + * Add a new directory to the iso tree. + * + * @param parent + * the dir where the new directory will be created + * @param name + * name for the new dir. If a node with same name already exists on + * parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param dir + * place where to store a pointer to the newly created dir. No extra + * ref is addded, so you will need to call iso_node_ref() if you really + * need it. You can pass NULL in this parameter if you don't need the + * pointer. + * @return + * number of nodes in dir if succes, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent or name are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + */ +int iso_tree_add_new_dir(IsoDir *parent, const char *name, IsoDir **dir) +{ + int ret; + char *n; + IsoDir *node; + IsoNode **pos; + time_t now; + + if (parent == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + if (dir) { + *dir = NULL; + } + + /* find place where to insert and check if it exists */ + if (iso_dir_exists(parent, name, &pos)) { + /* a node with same name already exists */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + + n = strdup(name); + ret = iso_node_new_dir(n, &node); + if (ret < 0) { + free(n); + return ret; + } + + /* permissions from parent */ + iso_node_set_permissions((IsoNode*)node, parent->node.mode); + iso_node_set_uid((IsoNode*)node, parent->node.uid); + iso_node_set_gid((IsoNode*)node, parent->node.gid); + iso_node_set_hidden((IsoNode*)node, parent->node.hidden); + + /* current time */ + now = time(NULL); + iso_node_set_atime((IsoNode*)node, now); + iso_node_set_ctime((IsoNode*)node, now); + iso_node_set_mtime((IsoNode*)node, now); + + if (dir) { + *dir = node; + } + + /* add to dir */ + return iso_dir_insert(parent, (IsoNode*)node, pos, ISO_REPLACE_NEVER); +} + +/** + * Add a new symlink to the directory tree. Permissions are set to 0777, + * owner and hidden atts are taken from parent. You can modify any of them + * later. + * + * @param parent + * the dir where the new symlink will be created + * @param name + * name for the new dir. If a node with same name already exists on + * parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param dest + * destination of the link + * @param link + * place where to store a pointer to the newly created link. No extra + * ref is addded, so you will need to call iso_node_ref() if you really + * need it. You can pass NULL in this parameter if you don't need the + * pointer + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent, name or dest are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_OUT_OF_MEM + */ +int iso_tree_add_new_symlink(IsoDir *parent, const char *name, + const char *dest, IsoSymlink **link) +{ + int ret; + char *n, *d; + IsoSymlink *node; + IsoNode **pos; + time_t now; + + if (parent == NULL || name == NULL || dest == NULL) { + return ISO_NULL_POINTER; + } + if (link) { + *link = NULL; + } + + /* find place where to insert */ + if (iso_dir_exists(parent, name, &pos)) { + /* a node with same name already exists */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + + n = strdup(name); + d = strdup(dest); + ret = iso_node_new_symlink(n, d, &node); + if (ret < 0) { + free(n); + free(d); + return ret; + } + + /* permissions from parent */ + iso_node_set_permissions((IsoNode*)node, 0777); + iso_node_set_uid((IsoNode*)node, parent->node.uid); + iso_node_set_gid((IsoNode*)node, parent->node.gid); + iso_node_set_hidden((IsoNode*)node, parent->node.hidden); + + /* current time */ + now = time(NULL); + iso_node_set_atime((IsoNode*)node, now); + iso_node_set_ctime((IsoNode*)node, now); + iso_node_set_mtime((IsoNode*)node, now); + + if (link) { + *link = node; + } + + /* add to dir */ + return iso_dir_insert(parent, (IsoNode*)node, pos, ISO_REPLACE_NEVER); +} + +/** + * Add a new special file to the directory tree. As far as libisofs concerns, + * an special file is a block device, a character device, a FIFO (named pipe) + * or a socket. You can choose the specific kind of file you want to add + * by setting mode propertly (see man 2 stat). + * + * Note that special files are only written to image when Rock Ridge + * extensions are enabled. Moreover, a special file is just a directory entry + * in the image tree, no data is written beyond that. + * + * Owner and hidden atts are taken from parent. You can modify any of them + * later. + * + * @param parent + * the dir where the new special file will be created + * @param name + * name for the new special file. If a node with same name already exists + * on parent, this functions fails with ISO_NODE_NAME_NOT_UNIQUE. + * @param mode + * file type and permissions for the new node. Note that you can't + * specify any kind of file here, only special types are allowed. i.e, + * S_IFSOCK, S_IFBLK, S_IFCHR and S_IFIFO are valid types; S_IFLNK, + * S_IFREG and S_IFDIR aren't. + * @param dev + * device ID, equivalent to the st_rdev field in man 2 stat. + * @param special + * place where to store a pointer to the newly created special file. No + * extra ref is addded, so you will need to call iso_node_ref() if you + * really need it. You can pass NULL in this parameter if you don't need + * the pointer. + * @return + * number of nodes in parent if success, < 0 otherwise + * Possible errors: + * ISO_NULL_POINTER, if parent, name or dest are NULL + * ISO_NODE_NAME_NOT_UNIQUE, a node with same name already exists + * ISO_OUT_OF_MEM + * + */ +int iso_tree_add_new_special(IsoDir *parent, const char *name, mode_t mode, + dev_t dev, IsoSpecial **special) +{ + int ret; + char *n; + IsoSpecial *node; + IsoNode **pos; + time_t now; + + if (parent == NULL || name == NULL) { + return ISO_NULL_POINTER; + } + if (S_ISLNK(mode) || S_ISREG(mode) || S_ISDIR(mode)) { + return ISO_WRONG_ARG_VALUE; + } + if (special) { + *special = NULL; + } + + /* find place where to insert */ + if (iso_dir_exists(parent, name, &pos)) { + /* a node with same name already exists */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + + n = strdup(name); + ret = iso_node_new_special(n, mode, dev, &node); + if (ret < 0) { + free(n); + return ret; + } + + /* atts from parent */ + iso_node_set_uid((IsoNode*)node, parent->node.uid); + iso_node_set_gid((IsoNode*)node, parent->node.gid); + iso_node_set_hidden((IsoNode*)node, parent->node.hidden); + + /* current time */ + now = time(NULL); + iso_node_set_atime((IsoNode*)node, now); + iso_node_set_ctime((IsoNode*)node, now); + iso_node_set_mtime((IsoNode*)node, now); + + if (special) { + *special = node; + } + + /* add to dir */ + return iso_dir_insert(parent, (IsoNode*)node, pos, ISO_REPLACE_NEVER); +} + +/** + * Set whether to follow or not symbolic links when added a file from a source + * to IsoImage. + */ +void iso_tree_set_follow_symlinks(IsoImage *image, int follow) +{ + image->follow_symlinks = follow ? 1 : 0; +} + +/** + * Get current setting for follow_symlinks. + * + * @see iso_tree_set_follow_symlinks + */ +int iso_tree_get_follow_symlinks(IsoImage *image) +{ + return image->follow_symlinks; +} + +/** + * Set whether to skip or not hidden files when adding a directory recursibely. + * Default behavior is to not ignore them, i.e., to add hidden files to image. + */ +void iso_tree_set_ignore_hidden(IsoImage *image, int skip) +{ + image->ignore_hidden = skip ? 1 : 0; +} + +/** + * Get current setting for ignore_hidden. + * + * @see iso_tree_set_ignore_hidden + */ +int iso_tree_get_ignore_hidden(IsoImage *image) +{ + return image->ignore_hidden; +} + +void iso_tree_set_replace_mode(IsoImage *image, enum iso_replace_mode mode) +{ + image->replace = mode; +} + +enum iso_replace_mode iso_tree_get_replace_mode(IsoImage *image) +{ + return image->replace; +} + +/** + * Set whether to skip or not special files. Default behavior is to not skip + * them. Note that, despite of this setting, special files won't never be added + * to an image unless RR extensions were enabled. + * + * @param skip + * Bitmask to determine what kind of special files will be skipped: + * bit0: ignore FIFOs + * bit1: ignore Sockets + * bit2: ignore char devices + * bit3: ignore block devices + */ +void iso_tree_set_ignore_special(IsoImage *image, int skip) +{ + image->ignore_special = skip & 0x0F; +} + +/** + * Get current setting for ignore_special. + * + * @see iso_tree_set_ignore_special + */ +int iso_tree_get_ignore_special(IsoImage *image) +{ + return image->ignore_special; +} + +/** + * Set a callback function that libisofs will call for each file that is + * added to the given image by a recursive addition function. This includes + * image import. + * + * @param report + * pointer to a function that will be called just before a file will be + * added to the image. You can control whether the file will be in fact + * added or ignored. + * This function should return 1 to add the file, 0 to ignore it and + * continue, < 0 to abort the process + * NULL is allowed if you don't want any callback. + */ +void iso_tree_set_report_callback(IsoImage *image, + int (*report)(IsoImage*, IsoFileSource*)) +{ + image->report = report; +} + +/** + * Add a excluded path. These are paths that won't never added to image, + * and will be excluded even when adding recursively its parent directory. + * + * For example, in + * + * iso_tree_add_exclude(image, "/home/user/data/private"); + * iso_tree_add_dir_rec(image, root, "/home/user/data"); + * + * the directory /home/user/data/private won't be added to image. + * + * @return + * 1 on success, < 0 on error + */ +int iso_tree_add_exclude(IsoImage *image, const char *path) +{ + if (image == NULL || path == NULL) { + return ISO_NULL_POINTER; + } + image->excludes = realloc(image->excludes, ++image->nexcludes * + sizeof(void*)); + if (image->excludes == NULL) { + return ISO_OUT_OF_MEM; + } + image->excludes[image->nexcludes - 1] = strdup(path); + if (image->excludes[image->nexcludes - 1] == NULL) { + return ISO_OUT_OF_MEM; + } + return ISO_SUCCESS; +} + +/** + * Remove a previously added exclude. + * + * @see iso_tree_add_exclude + * @return + * 1 on success, 0 exclude do not exists, < 0 on error + */ +int iso_tree_remove_exclude(IsoImage *image, const char *path) +{ + size_t i, j; + + if (image == NULL || path == NULL) { + return ISO_NULL_POINTER; + } + + for (i = 0; i < image->nexcludes; ++i) { + if (strcmp(image->excludes[i], path) == 0) { + /* exclude found */ + free(image->excludes[i]); + --image->nexcludes; + for (j = i; j < image->nexcludes; ++j) { + image->excludes[j] = image->excludes[j+1]; + } + image->excludes = realloc(image->excludes, image->nexcludes * + sizeof(void*)); + return ISO_SUCCESS; + } + } + return 0; +} + +static +int iso_tree_add_node_builder(IsoImage *image, IsoDir *parent, + IsoFileSource *src, IsoNodeBuilder *builder, + IsoNode **node) +{ + int result; + IsoNode *new; + IsoNode **pos; + char *name; + + if (parent == NULL || src == NULL || builder == NULL) { + return ISO_NULL_POINTER; + } + if (node) { + *node = NULL; + } + + name = iso_file_source_get_name(src); + + /* find place where to insert */ + result = iso_dir_exists(parent, name, &pos); + free(name); + if (result) { + /* a node with same name already exists */ + return ISO_NODE_NAME_NOT_UNIQUE; + } + + result = builder->create_node(builder, image, src, &new); + if (result < 0) { + return result; + } + + if (node) { + *node = new; + } + + /* finally, add node to parent */ + return iso_dir_insert(parent, (IsoNode*)new, pos, ISO_REPLACE_NEVER); +} + +int iso_tree_add_node(IsoImage *image, IsoDir *parent, const char *path, + IsoNode **node) +{ + int result; + IsoFilesystem *fs; + IsoFileSource *file; + + if (image == NULL || parent == NULL || path == NULL) { + return ISO_NULL_POINTER; + } + + fs = image->fs; + result = fs->get_by_path(fs, path, &file); + if (result < 0) { + return result; + } + result = iso_tree_add_node_builder(image, parent, file, image->builder, + node); + /* free the file */ + iso_file_source_unref(file); + return result; +} + +static +int check_excludes(IsoImage *image, const char *path) +{ + int i; + + for (i = 0; i < image->nexcludes; ++i) { + char *exclude = image->excludes[i]; + if (exclude[0] == '/') { + /* absolute exclude, must completely match path */ + if (!fnmatch(exclude, path, FNM_PERIOD|FNM_PATHNAME)) { + return 1; + } + } else { + /* relative exclude, it is enought if a part of the path matches */ + char *pos = (char*)path; + while (pos != NULL) { + pos++; + if (!fnmatch(exclude, pos, FNM_PERIOD|FNM_PATHNAME)) { + return 1; + } + pos = strchr(pos, '/'); + } + } + } + return 0; +} + +static +int check_hidden(IsoImage *image, const char *name) +{ + return (image->ignore_hidden && name[0] == '.'); +} + +static +int check_special(IsoImage *image, mode_t mode) +{ + if (image->ignore_special != 0) { + switch(mode & S_IFMT) { + case S_IFBLK: + return image->ignore_special & 0x08 ? 1 : 0; + case S_IFCHR: + return image->ignore_special & 0x04 ? 1 : 0; + case S_IFSOCK: + return image->ignore_special & 0x02 ? 1 : 0; + case S_IFIFO: + return image->ignore_special & 0x01 ? 1 : 0; + default: + return 0; + } + } + return 0; +} + +/** + * Recursively add a given directory to the image tree. + * + * @return + * 1 continue, < 0 error (ISO_CANCELED stop) + */ +int iso_add_dir_src_rec(IsoImage *image, IsoDir *parent, IsoFileSource *dir) +{ + int ret; + IsoNodeBuilder *builder; + IsoFileSource *file; + IsoNode **pos; + struct stat info; + char *name, *path; + IsoNode *new; + enum iso_replace_mode replace; + + ret = iso_file_source_open(dir); + if (ret < 0) { + char *path = iso_file_source_get_path(dir); + /* instead of the probable error, we throw a sorry event */ + ret = iso_msg_submit(image->id, ISO_FILE_CANT_ADD, ret, + "Can't open dir %s", path); + free(path); + return ret; + } + + builder = image->builder; + + /* iterate over all directory children */ + while (1) { + int skip = 0; + + ret = iso_file_source_readdir(dir, &file); + if (ret <= 0) { + if (ret < 0) { + /* error reading dir */ + ret = iso_msg_submit(image->id, ret, ret, "Error reading dir"); + } + break; + } + + path = iso_file_source_get_path(file); + name = strrchr(path, '/') + 1; + + if (image->follow_symlinks) { + ret = iso_file_source_stat(file, &info); + } else { + ret = iso_file_source_lstat(file, &info); + } + if (ret < 0) { + goto dir_rec_continue; + } + + if (check_excludes(image, path)) { + iso_msg_debug(image->id, "Skipping excluded file %s", path); + skip = 1; + } else if (check_hidden(image, name)) { + iso_msg_debug(image->id, "Skipping hidden file %s", path); + skip = 1; + } else if (check_special(image, info.st_mode)) { + iso_msg_debug(image->id, "Skipping special file %s", path); + skip = 1; + } + + if (skip) { + goto dir_rec_continue; + } + + replace = image->replace; + + /* find place where to insert */ + ret = iso_dir_exists(parent, name, &pos); + /* TODO + * if (ret && replace == ISO_REPLACE_ASK) { + * replace = /.... + * } + */ + + /* chek if we must insert or not */ + /* TODO check for other replace behavior */ + if (ret && (replace == ISO_REPLACE_NEVER)) { + /* skip file */ + goto dir_rec_continue; + } + + /* if we are here we must insert. Give user a chance for cancel */ + if (image->report) { + int r = image->report(image, file); + if (r <= 0) { + ret = (r < 0 ? ISO_CANCELED : ISO_SUCCESS); + goto dir_rec_continue; + } + } + ret = builder->create_node(builder, image, file, &new); + if (ret < 0) { + ret = iso_msg_submit(image->id, ISO_FILE_CANT_ADD, ret, + "Error when adding file %s", path); + goto dir_rec_continue; + } + + /* ok, node has correctly created, we need to add it */ + ret = iso_dir_insert(parent, new, pos, replace); + if (ret < 0) { + iso_node_unref(new); + if (ret != ISO_NODE_NAME_NOT_UNIQUE) { + /* error */ + goto dir_rec_continue; + } else { + /* file ignored because a file with same node already exists */ + iso_msg_debug(image->id, "Skipping file %s. A node with same " + "file already exists", path); + ret = 0; + } + } else { + iso_msg_debug(image->id, "Added file %s", path); + } + + /* finally, if the node is a directory we need to recurse */ + if (new->type == LIBISO_DIR && S_ISDIR(info.st_mode)) { + ret = iso_add_dir_src_rec(image, (IsoDir*)new, file); + } + +dir_rec_continue:; + free(path); + iso_file_source_unref(file); + + /* check for error severity to decide what to do */ + if (ret < 0) { + ret = iso_msg_submit(image->id, ret, 0, NULL); + if (ret < 0) { + break; + } + } + } /* while */ + + iso_file_source_close(dir); + return ret < 0 ? ret : ISO_SUCCESS; +} + +int iso_tree_add_dir_rec(IsoImage *image, IsoDir *parent, const char *dir) +{ + int result; + struct stat info; + IsoFilesystem *fs; + IsoFileSource *file; + + if (image == NULL || parent == NULL || dir == NULL) { + return ISO_NULL_POINTER; + } + + fs = image->fs; + result = fs->get_by_path(fs, dir, &file); + if (result < 0) { + return result; + } + + /* we also allow dir path to be a symlink to a dir */ + result = iso_file_source_stat(file, &info); + if (result < 0) { + iso_file_source_unref(file); + return result; + } + + if (!S_ISDIR(info.st_mode)) { + iso_file_source_unref(file); + return ISO_FILE_IS_NOT_DIR; + } + result = iso_add_dir_src_rec(image, parent, file); + iso_file_source_unref(file); + return result; +} + +int iso_tree_path_to_node(IsoImage *image, const char *path, IsoNode **node) +{ + int result; + IsoNode *n; + IsoDir *dir; + char *ptr, *brk_info, *component; + + if (image == NULL || path == NULL) { + return ISO_NULL_POINTER; + } + + /* get the first child at the root of the image that is "/" */ + dir = image->root; + n = (IsoNode *)dir; + if (!strcmp(path, "/")) { + if (node) { + *node = n; + } + return ISO_SUCCESS; + } + + ptr = strdup(path); + result = 0; + + /* get the first component of the path */ + component = strtok_r(ptr, "/", &brk_info); + while (component) { + if (n->type != LIBISO_DIR) { + n = NULL; + break; + } + dir = (IsoDir *)n; + + result = iso_dir_get_node(dir, component, &n); + if (result != 1) { + n = NULL; + break; + } + + component = strtok_r(NULL, "/", &brk_info); + } + + free(ptr); + if (node) { + *node = n; + } + return result; +} diff --git a/libisofs/tree.h b/libisofs/tree.h new file mode 100644 index 0000000..9c5347c --- /dev/null +++ b/libisofs/tree.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_IMAGE_TREE_H_ +#define LIBISO_IMAGE_TREE_H_ + +#include "image.h" + +/** + * Recursively add a given directory to the image tree. + * + * @return + * 1 continue, 0 stop, < 0 error + */ +int iso_add_dir_src_rec(IsoImage *image, IsoDir *parent, IsoFileSource *dir); + +#endif /*LIBISO_IMAGE_TREE_H_*/ diff --git a/libisofs/util.c b/libisofs/util.c new file mode 100644 index 0000000..6317a8b --- /dev/null +++ b/libisofs/util.c @@ -0,0 +1,1264 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * Copyright (c) 2007 Mario Danic + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "util.h" +#include "libisofs.h" +#include "../version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* if we don't have eaccess, we check file access by openning it */ +#ifndef HAVE_EACCESS +#include +#include +#include +#endif + +int int_pow(int base, int power) +{ + int result = 1; + while (--power >= 0) { + result *= base; + } + return result; +} + +int strconv(const char *str, const char *icharset, const char *ocharset, + char **output) +{ + size_t inbytes; + size_t outbytes; + size_t n; + iconv_t conv; + char *out; + char *src; + char *ret; + + inbytes = strlen(str); + outbytes = (inbytes + 1) * MB_LEN_MAX; + out = alloca(outbytes); + if (out == NULL) { + return ISO_OUT_OF_MEM; + } + + conv = iconv_open(ocharset, icharset); + if (conv == (iconv_t)(-1)) { + return ISO_CHARSET_CONV_ERROR; + } + src = (char *)str; + ret = (char *)out; + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + if (n == -1) { + /* error */ + iconv_close(conv); + return ISO_CHARSET_CONV_ERROR; + } + *ret = '\0'; + iconv_close(conv); + + *output = malloc(ret - out + 1); + if (*output == NULL) { + return ISO_OUT_OF_MEM; + } + memcpy(*output, out, ret - out + 1); + return ISO_SUCCESS; +} + +int strnconv(const char *str, const char *icharset, const char *ocharset, + size_t len, char **output) +{ + size_t inbytes; + size_t outbytes; + size_t n; + iconv_t conv; + char *out; + char *src; + char *ret; + + inbytes = len; + outbytes = (inbytes + 1) * MB_LEN_MAX; + out = alloca(outbytes); + if (out == NULL) { + return ISO_OUT_OF_MEM; + } + + conv = iconv_open(ocharset, icharset); + if (conv == (iconv_t)(-1)) { + return ISO_CHARSET_CONV_ERROR; + } + src = (char *)str; + ret = (char *)out; + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + if (n == -1) { + /* error */ + iconv_close(conv); + return ISO_CHARSET_CONV_ERROR; + } + *ret = '\0'; + iconv_close(conv); + + *output = malloc(ret - out + 1); + if (*output == NULL) { + return ISO_OUT_OF_MEM; + } + memcpy(*output, out, ret - out + 1); + return ISO_SUCCESS; +} + +/** + * Convert a str in a specified codeset to WCHAR_T. + * The result must be free() when no more needed + * + * @return + * 1 success, < 0 error + */ +static +int str2wchar(const char *icharset, const char *input, wchar_t **output) +{ + iconv_t conv; + size_t inbytes; + size_t outbytes; + char *ret; + char *src; + wchar_t *wstr; + size_t n; + + if (icharset == NULL || input == NULL || output == NULL) { + return ISO_NULL_POINTER; + } + + conv = iconv_open("WCHAR_T", icharset); + if (conv == (iconv_t)-1) { + return ISO_CHARSET_CONV_ERROR; + } + + inbytes = strlen(input); + outbytes = (inbytes + 1) * sizeof(wchar_t); + + /* we are sure that numchars <= inbytes */ + wstr = malloc(outbytes); + if (wstr == NULL) { + return ISO_OUT_OF_MEM; + } + ret = (char *)wstr; + src = (char *)input; + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + while (n == -1) { + + if (errno == E2BIG) { + /* error, should never occur */ + iconv_close(conv); + free(wstr); + return ISO_CHARSET_CONV_ERROR; + } else { + wchar_t *wret; + + /* + * Invalid input string charset. + * This can happen if input is in fact encoded in a charset + * different than icharset. + * We can't do anything better than replace by "_" and continue. + */ + inbytes--; + src++; + + wret = (wchar_t*) ret; + *wret++ = (wchar_t) '_'; + ret = (char *) wret; + outbytes -= sizeof(wchar_t); + + if (!inbytes) + break; + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + } + } + iconv_close(conv); + + *( (wchar_t *)ret )='\0'; + *output = wstr; + return ISO_SUCCESS; +} + +int str2ascii(const char *icharset, const char *input, char **output) +{ + int result; + wchar_t *wsrc_; + char *ret; + char *ret_; + char *src; + iconv_t conv; + size_t numchars; + size_t outbytes; + size_t inbytes; + size_t n; + + if (icharset == NULL || input == NULL || output == NULL) { + return ISO_NULL_POINTER; + } + + /* convert the string to a wide character string. Note: outbytes + * is in fact the number of characters in the string and doesn't + * include the last NULL character. + */ + result = str2wchar(icharset, input, &wsrc_); + if (result < 0) { + return result; + } + src = (char *)wsrc_; + numchars = wcslen(wsrc_); + + inbytes = numchars * sizeof(wchar_t); + + ret_ = malloc(numchars + 1); + if (ret_ == NULL) { + return ISO_OUT_OF_MEM; + } + outbytes = numchars; + ret = ret_; + + /* initialize iconv */ + conv = iconv_open("ASCII", "WCHAR_T"); + if (conv == (iconv_t)-1) { + free(wsrc_); + free(ret_); + return ISO_CHARSET_CONV_ERROR; + } + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + while (n == -1) { + /* The destination buffer is too small. Stops here. */ + if (errno == E2BIG) + break; + + /* An incomplete multi bytes sequence was found. We + * can't do anything here. That's quite unlikely. */ + if (errno == EINVAL) + break; + + /* The last possible error is an invalid multi bytes + * sequence. Just replace the character with a "_". + * Probably the character doesn't exist in ascii like + * "é, è, à, ç, ..." in French. */ + *ret++ = '_'; + outbytes--; + + if (!outbytes) + break; + + /* There was an error with one character but some other remain + * to be converted. That's probably a multibyte character. + * See above comment. */ + src += sizeof(wchar_t); + inbytes -= sizeof(wchar_t); + + if (!inbytes) + break; + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + } + + iconv_close(conv); + + *ret='\0'; + free(wsrc_); + + *output = ret_; + return ISO_SUCCESS; +} + +static +void set_ucsbe(uint16_t *ucs, char c) +{ + char *v = (char*)ucs; + v[0] = (char)0; + v[1] = c; +} + +/** + * @return + * -1, 0, 1 if *ucs <, == or > than c + */ +static +int cmp_ucsbe(const uint16_t *ucs, char c) +{ + char *v = (char*)ucs; + if (v[0] != 0) { + return 1; + } else if (v[1] == c) { + return 0; + } else { + return (uint8_t)c > (uint8_t)v[1] ? -1 : 1; + } +} + +int str2ucs(const char *icharset, const char *input, uint16_t **output) +{ + int result; + wchar_t *wsrc_; + char *src; + char *ret; + char *ret_; + iconv_t conv; + size_t numchars; + size_t outbytes; + size_t inbytes; + size_t n; + + if (icharset == NULL || input == NULL || output == NULL) { + return ISO_NULL_POINTER; + } + + /* convert the string to a wide character string. Note: outbytes + * is in fact the number of characters in the string and doesn't + * include the last NULL character. + */ + result = str2wchar(icharset, input, &wsrc_); + if (result < 0) { + return result; + } + src = (char *)wsrc_; + numchars = wcslen(wsrc_); + + inbytes = numchars * sizeof(wchar_t); + + ret_ = malloc((numchars+1) * sizeof(uint16_t)); + if (ret_ == NULL) { + return ISO_OUT_OF_MEM; + } + outbytes = numchars * sizeof(uint16_t); + ret = ret_; + + /* initialize iconv */ + conv = iconv_open("UCS-2BE", "WCHAR_T"); + if (conv == (iconv_t)-1) { + free(wsrc_); + free(ret_); + return ISO_CHARSET_CONV_ERROR; + } + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + while (n == -1) { + /* The destination buffer is too small. Stops here. */ + if (errno == E2BIG) + break; + + /* An incomplete multi bytes sequence was found. We + * can't do anything here. That's quite unlikely. */ + if (errno == EINVAL) + break; + + /* The last possible error is an invalid multi bytes + * sequence. Just replace the character with a "_". + * Probably the character doesn't exist in UCS */ + set_ucsbe((uint16_t*) ret, '_'); + ret += sizeof(uint16_t); + outbytes -= sizeof(uint16_t); + + if (!outbytes) + break; + + /* There was an error with one character but some other remain + * to be converted. That's probably a multibyte character. + * See above comment. */ + src += sizeof(wchar_t); + inbytes -= sizeof(wchar_t); + + if (!inbytes) + break; + + n = iconv(conv, &src, &inbytes, &ret, &outbytes); + } + + iconv_close(conv); + + /* close the ucs string */ + set_ucsbe((uint16_t*) ret, '\0'); + free(wsrc_); + + *output = (uint16_t*)ret_; + return ISO_SUCCESS; +} + +static int valid_d_char(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == '_'); +} + +static int valid_a_char(char c) +{ + return (c >= ' ' && c <= '"') || (c >= '%' && c <= '?') || + (c >= 'A' && c <= 'Z') || (c == '_'); +} + +static int valid_j_char(uint16_t c) +{ + return cmp_ucsbe(&c, ' ') != -1 && cmp_ucsbe(&c, '*') && cmp_ucsbe(&c, '/') + && cmp_ucsbe(&c, ':') && cmp_ucsbe(&c, ';') && cmp_ucsbe(&c, '?') + && cmp_ucsbe(&c, '\\'); +} + +static +char *iso_dirid(const char *src, int size) +{ + size_t len, i; + char name[32]; + + len = strlen(src); + if (len > size) { + len = size; + } + for (i = 0; i < len; i++) { + char c= toupper(src[i]); + name[i] = valid_d_char(c) ? c : '_'; + } + + name[len] = '\0'; + return strdup(name); +} + +char *iso_1_dirid(const char *src) +{ + return iso_dirid(src, 8); +} + +char *iso_2_dirid(const char *src) +{ + return iso_dirid(src, 31); +} + +char *iso_1_fileid(const char *src) +{ + char *dot; /* Position of the last dot in the filename, will be used + * to calculate lname and lext. */ + int lname, lext, pos, i; + char dest[13]; /* 13 = 8 (name) + 1 (.) + 3 (ext) + 1 (\0) */ + + if (src == NULL) { + return NULL; + } + dot = strrchr(src, '.'); + + lext = dot ? strlen(dot + 1) : 0; + lname = strlen(src) - lext - (dot ? 1 : 0); + + /* If we can't build a filename, return NULL. */ + if (lname == 0 && lext == 0) { + return NULL; + } + + pos = 0; + + /* Convert up to 8 characters of the filename. */ + for (i = 0; i < lname && i < 8; i++) { + char c= toupper(src[i]); + + dest[pos++] = valid_d_char(c) ? c : '_'; + } + + /* This dot is mandatory, even if there is no extension. */ + dest[pos++] = '.'; + + /* Convert up to 3 characters of the extension, if any. */ + for (i = 0; i < lext && i < 3; i++) { + char c= toupper(src[lname + 1 + i]); + + dest[pos++] = valid_d_char(c) ? c : '_'; + } + + dest[pos] = '\0'; + return strdup(dest); +} + +char *iso_2_fileid(const char *src) +{ + char *dot; + int lname, lext, lnname, lnext, pos, i; + char dest[32]; /* 32 = 30 (name + ext) + 1 (.) + 1 (\0) */ + + if (src == NULL) { + return NULL; + } + + dot = strrchr(src, '.'); + + /* + * Since the maximum length can be divided freely over the name and + * extension, we need to calculate their new lengths (lnname and + * lnext). If the original filename is too long, we start by trimming + * the extension, but keep a minimum extension length of 3. + */ + if (dot == NULL || *(dot + 1) == '\0') { + lname = strlen(src); + lnname = (lname > 30) ? 30 : lname; + lext = lnext = 0; + } else { + lext = strlen(dot + 1); + lname = strlen(src) - lext - 1; + lnext = (strlen(src) > 31 && lext > 3) ? (lname < 27 ? 30 - lname : 3) + : lext; + lnname = (strlen(src) > 31) ? 30 - lnext : lname; + } + + if (lnname == 0 && lnext == 0) { + return NULL; + } + + pos = 0; + + /* Convert up to lnname characters of the filename. */ + for (i = 0; i < lnname; i++) { + char c= toupper(src[i]); + + dest[pos++] = valid_d_char(c) ? c : '_'; + } + dest[pos++] = '.'; + + /* Convert up to lnext characters of the extension, if any. */ + for (i = 0; i < lnext; i++) { + char c= toupper(src[lname + 1 + i]); + + dest[pos++] = valid_d_char(c) ? c : '_'; + } + dest[pos] = '\0'; + return strdup(dest); +} + +/** + * Create a dir name suitable for an ISO image with relaxed constraints. + * + * @param size + * Max len for the name + * @param relaxed + * 0 only allow d-characters, 1 allow also lowe case chars, + * 2 allow all characters + */ +char *iso_r_dirid(const char *src, int size, int relaxed) +{ + size_t len, i; + char *dest; + + len = strlen(src); + if (len > size) { + len = size; + } + dest = malloc(len + 1); + for (i = 0; i < len; i++) { + char c= src[i]; + if (relaxed == 2) { + /* all chars are allowed */ + dest[i] = c; + } else if (valid_d_char(c)) { + /* it is a valid char */ + dest[i] = c; + } else { + c= toupper(src[i]); + if (valid_d_char(c)) { + if (relaxed) { + /* lower chars are allowed */ + dest[i] = src[i]; + } else { + dest[i] = c; + } + } else { + dest[i] = '_'; + } + } + } + + dest[len] = '\0'; + return dest; +} + +/** + * Create a file name suitable for an ISO image with relaxed constraints. + * + * @param len + * Max len for the name, without taken the "." into account. + * @param relaxed + * 0 only allow d-characters, 1 allow also lowe case chars, + * 2 allow all characters + * @param forcedot + * Whether to ensure that "." is added + */ +char *iso_r_fileid(const char *src, size_t len, int relaxed, int forcedot) +{ + char *dot; + int lname, lext, lnname, lnext, pos, i; + char *dest = alloca(len + 1 + 1); + + if (src == NULL) { + return NULL; + } + + dot = strrchr(src, '.'); + + /* + * Since the maximum length can be divided freely over the name and + * extension, we need to calculate their new lengths (lnname and + * lnext). If the original filename is too long, we start by trimming + * the extension, but keep a minimum extension length of 3. + */ + if (dot == NULL || *(dot + 1) == '\0') { + lname = strlen(src); + lnname = (lname > len) ? len : lname; + lext = lnext = 0; + } else { + lext = strlen(dot + 1); + lname = strlen(src) - lext - 1; + lnext = (strlen(src) > len + 1 && lext > 3) ? + (lname < len - 3 ? len - lname : 3) + : lext; + lnname = (strlen(src) > len + 1) ? len - lnext : lname; + } + + if (lnname == 0 && lnext == 0) { + return NULL; + } + + pos = 0; + + /* Convert up to lnname characters of the filename. */ + for (i = 0; i < lnname; i++) { + char c= src[i]; + if (relaxed == 2) { + /* all chars are allowed */ + dest[pos++] = c; + } else if (valid_d_char(c)) { + /* it is a valid char */ + dest[pos++] = c; + } else { + c= toupper(src[i]); + if (valid_d_char(c)) { + if (relaxed) { + /* lower chars are allowed */ + dest[pos++] = src[i]; + } else { + dest[pos++] = c; + } + } else { + dest[pos++] = '_'; + } + } + } + if (lnext > 0 || forcedot) { + dest[pos++] = '.'; + } + + /* Convert up to lnext characters of the extension, if any. */ + for (i = lname + 1; i < lname + 1 + lnext; i++) { + char c= src[i]; + if (relaxed == 2) { + /* all chars are allowed */ + dest[pos++] = c; + } else if (valid_d_char(c)) { + /* it is a valid char */ + dest[pos++] = c; + } else { + c= toupper(src[i]); + if (valid_d_char(c)) { + if (relaxed) { + /* lower chars are allowed */ + dest[pos++] = src[i]; + } else { + dest[pos++] = c; + } + } else { + dest[pos++] = '_'; + } + } + } + dest[pos] = '\0'; + return strdup(dest); +} + +uint16_t *iso_j_file_id(const uint16_t *src) +{ + uint16_t *dot; + size_t lname, lext, lnname, lnext, pos, i; + uint16_t dest[66]; /* 66 = 64 (name + ext) + 1 (.) + 1 (\0) */ + + if (src == NULL) { + return NULL; + } + + dot = ucsrchr(src, '.'); + + /* + * Since the maximum length can be divided freely over the name and + * extension, we need to calculate their new lengths (lnname and + * lnext). If the original filename is too long, we start by trimming + * the extension, but keep a minimum extension length of 3. + */ + if (dot == NULL || cmp_ucsbe(dot + 1, '\0') == 0) { + lname = ucslen(src); + lnname = (lname > 64) ? 64 : lname; + lext = lnext = 0; + } else { + lext = ucslen(dot + 1); + lname = ucslen(src) - lext - 1; + lnext = (ucslen(src) > 65 && lext > 3) ? (lname < 61 ? 64 - lname : 3) + : lext; + lnname = (ucslen(src) > 65) ? 64 - lnext : lname; + } + + if (lnname == 0 && lnext == 0) { + return NULL; + } + + pos = 0; + + /* Convert up to lnname characters of the filename. */ + for (i = 0; i < lnname; i++) { + uint16_t c = src[i]; + if (valid_j_char(c)) { + dest[pos++] = c; + } else { + set_ucsbe(dest + pos, '_'); + pos++; + } + } + set_ucsbe(dest + pos, '.'); + pos++; + + /* Convert up to lnext characters of the extension, if any. */ + for (i = 0; i < lnext; i++) { + uint16_t c = src[lname + 1 + i]; + if (valid_j_char(c)) { + dest[pos++] = c; + } else { + set_ucsbe(dest + pos, '_'); + pos++; + } + } + set_ucsbe(dest + pos, '\0'); + return ucsdup(dest); +} + +uint16_t *iso_j_dir_id(const uint16_t *src) +{ + size_t len, i; + uint16_t dest[65]; /* 65 = 64 + 1 (\0) */ + + if (src == NULL) { + return NULL; + } + + len = ucslen(src); + if (len > 64) { + len = 64; + } + for (i = 0; i < len; i++) { + uint16_t c = src[i]; + if (valid_j_char(c)) { + dest[i] = c; + } else { + set_ucsbe(dest + i, '_'); + } + } + set_ucsbe(dest + len, '\0'); + return ucsdup(dest); +} + +size_t ucslen(const uint16_t *str) +{ + size_t i; + + for (i = 0; str[i]; i++) + ; + return i; +} + +uint16_t *ucsrchr(const uint16_t *str, char c) +{ + size_t len = ucslen(str); + + while (len-- > 0) { + if (cmp_ucsbe(str + len, c) == 0) { + return (uint16_t*)(str + len); + } + } + return NULL; +} + +uint16_t *ucsdup(const uint16_t *str) +{ + uint16_t *ret; + size_t len = ucslen(str); + + ret = malloc(2 * (len + 1)); + if (ret != NULL) { + memcpy(ret, str, 2 * (len + 1)); + } + return ret; +} + +/** + * Although each character is 2 bytes, we actually compare byte-by-byte + * (thats what the spec says). + */ +int ucscmp(const uint16_t *s1, const uint16_t *s2) +{ + const char *s = (const char*)s1; + const char *t = (const char*)s2; + size_t len1 = ucslen(s1); + size_t len2 = ucslen(s2); + size_t i, len = MIN(len1, len2) * 2; + + for (i = 0; i < len; i++) { + if (s[i] < t[i]) { + return -1; + } else if (s[i] > t[i]) { + return 1; + } + } + + if (len1 < len2) + return -1; + else if (len1 > len2) + return 1; + return 0; +} + +uint16_t *ucscpy(uint16_t *dest, const uint16_t *src) +{ + size_t n = ucslen(src) + 1; + memcpy(dest, src, n*2); + return dest; +} + +uint16_t *ucsncpy(uint16_t *dest, const uint16_t *src, size_t n) +{ + n = MIN(n, ucslen(src) + 1); + memcpy(dest, src, n*2); + return dest; +} + +int str2d_char(const char *icharset, const char *input, char **output) +{ + int ret; + char *ascii; + size_t len, i; + + if (output == NULL) { + return ISO_OUT_OF_MEM; + } + + /** allow NULL input */ + if (input == NULL) { + *output = NULL; + return 0; + } + + /* this checks for NULL parameters */ + ret = str2ascii(icharset, input, &ascii); + if (ret < 0) { + *output = NULL; + return ret; + } + + len = strlen(ascii); + + for (i = 0; i < len; ++i) { + char c= toupper(ascii[i]); + ascii[i] = valid_d_char(c) ? c : '_'; + } + + *output = ascii; + return ISO_SUCCESS; +} + +int str2a_char(const char *icharset, const char *input, char **output) +{ + int ret; + char *ascii; + size_t len, i; + + if (output == NULL) { + return ISO_OUT_OF_MEM; + } + + /** allow NULL input */ + if (input == NULL) { + *output = NULL; + return 0; + } + + /* this checks for NULL parameters */ + ret = str2ascii(icharset, input, &ascii); + if (ret < 0) { + *output = NULL; + return ret; + } + + len = strlen(ascii); + + for (i = 0; i < len; ++i) { + char c= toupper(ascii[i]); + ascii[i] = valid_a_char(c) ? c : '_'; + } + + *output = ascii; + return ISO_SUCCESS; +} + +void iso_lsb(uint8_t *buf, uint32_t num, int bytes) +{ + int i; + + for (i = 0; i < bytes; ++i) + buf[i] = (num >> (8 * i)) & 0xff; +} + +void iso_msb(uint8_t *buf, uint32_t num, int bytes) +{ + int i; + + for (i = 0; i < bytes; ++i) + buf[bytes - 1 - i] = (num >> (8 * i)) & 0xff; +} + +void iso_bb(uint8_t *buf, uint32_t num, int bytes) +{ + iso_lsb(buf, num, bytes); + iso_msb(buf+bytes, num, bytes); +} + +uint32_t iso_read_lsb(const uint8_t *buf, int bytes) +{ + int i; + uint32_t ret = 0; + + for (i=0; i 52 || tzoffset < -48 || always_gmt) { + /* absurd timezone offset, represent time in GMT */ + gmtime_r(&t, &tm); + tzoffset = 0; + } + buf[0] = tm.tm_year; + buf[1] = tm.tm_mon + 1; + buf[2] = tm.tm_mday; + buf[3] = tm.tm_hour; + buf[4] = tm.tm_min; + buf[5] = tm.tm_sec; + buf[6] = tzoffset; +} + +void iso_datetime_17(unsigned char *buf, time_t t, int always_gmt) +{ + static int tzsetup = 0; + static int tzoffset; + struct tm tm; + + if (t == (time_t) - 1) { + /* unspecified time */ + memset(buf, '0', 16); + buf[16] = 0; + return; + } + + if (!tzsetup) { + tzset(); + tzsetup = 1; + } + + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; /* some Linuxes change tm_isdst only if it is -1 */ + localtime_r(&t, &tm); + + localtime_r(&t, &tm); + +#ifdef HAVE_TM_GMTOFF + tzoffset = tm.tm_gmtoff / 60 / 15; +#else + if (tm.tm_isdst < 0) + tm.tm_isdst = 0; + tzoffset = ( - timezone / 60 / 15 ) + 4 * tm.tm_isdst; +#endif + + if (tzoffset > 52 || tzoffset < -48 || always_gmt) { + /* absurd timezone offset, represent time in GMT */ + gmtime_r(&t, &tm); + tzoffset = 0; + } + + sprintf((char*)&buf[0], "%04d", tm.tm_year + 1900); + sprintf((char*)&buf[4], "%02d", tm.tm_mon + 1); + sprintf((char*)&buf[6], "%02d", tm.tm_mday); + sprintf((char*)&buf[8], "%02d", tm.tm_hour); + sprintf((char*)&buf[10], "%02d", tm.tm_min); + sprintf((char*)&buf[12], "%02d", MIN(59, tm.tm_sec)); + memcpy(&buf[14], "00", 2); + buf[16] = tzoffset; + +} + +#ifndef HAVE_TIMEGM +static +time_t timegm(struct tm *tm) +{ + time_t ret; + char *tz; + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +#endif + +time_t iso_datetime_read_7(const uint8_t *buf) +{ + struct tm tm; + + tm.tm_year = buf[0]; + tm.tm_mon = buf[1] - 1; + tm.tm_mday = buf[2]; + tm.tm_hour = buf[3]; + tm.tm_min = buf[4]; + tm.tm_sec = buf[5]; + return timegm(&tm) - ((int8_t)buf[6]) * 60 * 15; +} + +time_t iso_datetime_read_17(const uint8_t *buf) +{ + struct tm tm; + + sscanf((char*)&buf[0], "%4d", &tm.tm_year); + sscanf((char*)&buf[4], "%2d", &tm.tm_mon); + sscanf((char*)&buf[6], "%2d", &tm.tm_mday); + sscanf((char*)&buf[8], "%2d", &tm.tm_hour); + sscanf((char*)&buf[10], "%2d", &tm.tm_min); + sscanf((char*)&buf[12], "%2d", &tm.tm_sec); + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + return timegm(&tm) - ((int8_t)buf[6]) * 60 * 15; +} + +/** + * Check whether the caller process has read access to the given local file. + * + * @return + * 1 on success (i.e, the process has read access), < 0 on error + * (including ISO_FILE_ACCESS_DENIED on access denied to the specified file + * or any directory on the path). + */ +int iso_eaccess(const char *path) +{ + int access; + + /* use non standard eaccess when available, open() otherwise */ +#ifdef HAVE_EACCESS + access = !eaccess(path, R_OK); +#else + int fd = open(path, O_RDONLY); + if (fd != -1) { + close(fd); + access = 1; + } else { + access = 0; + } +#endif + + if (!access) { + int err; + + /* error, choose an appropriate return code */ + switch (errno) { + case EACCES: + err = ISO_FILE_ACCESS_DENIED; + break; + case ENOTDIR: + case ENAMETOOLONG: + case ELOOP: + err = ISO_FILE_BAD_PATH; + break; + case ENOENT: + err = ISO_FILE_DOESNT_EXIST; + break; + case EFAULT: + case ENOMEM: + err = ISO_OUT_OF_MEM; + break; + default: + err = ISO_FILE_ERROR; + break; + } + return err; + } + return ISO_SUCCESS; +} + +char *strcopy(const char *buf, size_t len) +{ + char *str; + + str = malloc((len + 1) * sizeof(char)); + if (str == NULL) { + return NULL; + } + strncpy(str, buf, len); + str[len] = '\0'; + + /* remove trailing spaces */ + for (len = len-1; str[len] == ' ' && len > 0; --len) + str[len] = '\0'; + + return str; +} + +/** + * Copy up to \p max characters from \p src to \p dest. If \p src has less than + * \p max characters, we pad dest with " " characters. + */ +void strncpy_pad(char *dest, const char *src, size_t max) +{ + size_t len, i; + + if (src != NULL) { + len = MIN(strlen(src), max); + } else { + len = 0; + } + + for (i = 0; i < len; ++i) + dest[i] = src[i]; + for (i = len; i < max; ++i) + dest[i] = ' '; +} + +char *ucs2str(const char *buf, size_t len) +{ + size_t outbytes, inbytes; + char *str, *src, *out; + iconv_t conv; + size_t n; + + inbytes = len; + + outbytes = (inbytes+1) * MB_LEN_MAX; + + /* ensure enought space */ + out = alloca(outbytes); + + /* convert to local charset */ + setlocale(LC_CTYPE, ""); + conv = iconv_open(nl_langinfo(CODESET), "UCS-2BE"); + if (conv == (iconv_t)(-1)) { + return NULL; + } + src = (char *)buf; + str = (char *)out; + + n = iconv(conv, &src, &inbytes, &str, &outbytes); + if (n == -1) { + /* error */ + iconv_close(conv); + return NULL; + } + iconv_close(conv); + *str = '\0'; + + /* remove trailing spaces */ + for (len = strlen(out) - 1; out[len] == ' ' && len > 0; --len) + out[len] = '\0'; + return strdup(out); +} + +void iso_lib_version(int *major, int *minor, int *micro) +{ + *major = LIBISOFS_MAJOR_VERSION; + *minor = LIBISOFS_MINOR_VERSION; + *micro = LIBISOFS_MICRO_VERSION; +} + +int iso_lib_is_compatible(int major, int minor, int micro) +{ + int cmajor, cminor, cmicro; + + /* for now, the rule is that library is compitable if requested + * version is lower */ + iso_lib_version(&cmajor, &cminor, &cmicro); + + return cmajor > major + || (cmajor == major + && (cminor > minor + || (cminor == minor && cmicro >= micro))); +} diff --git a/libisofs/util.h b/libisofs/util.h new file mode 100644 index 0000000..5a9616c --- /dev/null +++ b/libisofs/util.h @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#ifndef LIBISO_UTIL_H_ +#define LIBISO_UTIL_H_ + +#include +#include + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#define DIV_UP(n,div) ((n + div - 1) / div) +#define ROUND_UP(n,mul) (DIV_UP(n, mul) * mul) + +int int_pow(int base, int power); + +/** + * Convert the charset encoding of a given string. + * + * @param input + * Input string + * @param icharset + * Input charset. Must be supported by iconv + * @param ocharset + * Output charset. Must be supported by iconv + * @param output + * Location where the pointer to the ouput string will be stored + * @return + * 1 on success, < 0 on error + */ +int strconv(const char *input, const char *icharset, const char *ocharset, + char **output); + +int strnconv(const char *str, const char *icharset, const char *ocharset, + size_t len, char **output); + +/** + * Convert a given string from any input charset to ASCII + * + * @param icharset + * Input charset. Must be supported by iconv + * @param input + * Input string + * @param output + * Location where the pointer to the ouput string will be stored + * @return + * 1 on success, < 0 on error + */ +int str2ascii(const char *icharset, const char *input, char **output); + +/** + * Convert a given string from any input charset to UCS-2BE charset, + * used for Joliet file identifiers. + * + * @param icharset + * Input charset. Must be supported by iconv + * @param input + * Input string + * @param output + * Location where the pointer to the ouput string will be stored + * @return + * 1 on success, < 0 on error + */ +int str2ucs(const char *icharset, const char *input, uint16_t **output); + +/** + * Create a level 1 directory identifier. + * + * @param src + * The identifier, in ASCII encoding. + */ +char *iso_1_dirid(const char *src); + +/** + * Create a level 2 directory identifier. + * + * @param src + * The identifier, in ASCII encoding. + */ +char *iso_2_dirid(const char *src); + +/** + * Create a dir name suitable for an ISO image with relaxed constraints. + * + * @param src + * The identifier, in ASCII encoding. + * @param size + * Max len for the name + * @param relaxed + * 0 only allow d-characters, 1 allow also lowe case chars, + * 2 allow all characters + */ +char *iso_r_dirid(const char *src, int size, int relaxed); + +/** + * Create a level 1 file identifier that consists of a name, in 8.3 + * format. + * Note that version number is not added to the file name + * + * @param src + * The identifier, in ASCII encoding. + */ +char *iso_1_fileid(const char *src); + +/** + * Create a level 2 file identifier. + * Note that version number is not added to the file name + * + * @param src + * The identifier, in ASCII encoding. + */ +char *iso_2_fileid(const char *src); + +/** + * Create a file name suitable for an ISO image with relaxed constraints. + * + * @param src + * The identifier, in ASCII encoding. + * @param len + * Max len for the name, without taken the "." into account. + * @param relaxed + * 0 only allow d-characters, 1 allow also lowe case chars, + * 2 allow all characters + * @param forcedot + * Whether to ensure that "." is added + */ +char *iso_r_fileid(const char *src, size_t len, int relaxed, int forcedot); + +/** + * Create a Joliet file identifier that consists of name and extension. The + * combined name and extension length will not exceed 128 bytes, and the + * name and extension will be separated (.). All characters consist of + * 2 bytes and the resulting string is NULL-terminated by a 2-byte NULL. + * + * Note that version number and (;1) is not appended. + * + * @return + * NULL if the original name and extension both are of length 0. + */ +uint16_t *iso_j_file_id(const uint16_t *src); + +/** + * Create a Joliet directory identifier that consists of name and optionally + * extension. The combined name and extension length will not exceed 128 bytes, + * and the name and extension will be separated (.). All characters consist of + * 2 bytes and the resulting string is NULL-terminated by a 2-byte NULL. + * + * @return + * NULL if the original name and extension both are of length 0. + */ +uint16_t *iso_j_dir_id(const uint16_t *src); + +/** + * Like strlen, but for Joliet strings. + */ +size_t ucslen(const uint16_t *str); + +/** + * Like strrchr, but for Joliet strings. + */ +uint16_t *ucsrchr(const uint16_t *str, char c); + +/** + * Like strdup, but for Joliet strings. + */ +uint16_t *ucsdup(const uint16_t *str); + +/** + * Like strcmp, but for Joliet strings. + */ +int ucscmp(const uint16_t *s1, const uint16_t *s2); + +/** + * Like strcpy, but for Joliet strings. + */ +uint16_t *ucscpy(uint16_t *dest, const uint16_t *src); + +/** + * Like strncpy, but for Joliet strings. + * @param n + * Maximum number of characters to copy (2 bytes per char). + */ +uint16_t *ucsncpy(uint16_t *dest, const uint16_t *src, size_t n); + +/** + * Convert a given input string to d-chars. + * @return + * 1 on succes, < 0 error, 0 if input was null (output is set to null) + */ +int str2d_char(const char *icharset, const char *input, char **output); +int str2a_char(const char *icharset, const char *input, char **output); + +void iso_lsb(uint8_t *buf, uint32_t num, int bytes); +void iso_msb(uint8_t *buf, uint32_t num, int bytes); +void iso_bb(uint8_t *buf, uint32_t num, int bytes); + +uint32_t iso_read_lsb(const uint8_t *buf, int bytes); +uint32_t iso_read_msb(const uint8_t *buf, int bytes); + +/** + * if error != NULL it will be set to 1 if LSB and MSB integers don't match. + */ +uint32_t iso_read_bb(const uint8_t *buf, int bytes, int *error); + +/** + * Records the date/time into a 7 byte buffer (ECMA-119, 9.1.5) + * + * @param buf + * Buffer where the date will be written + * @param t + * The time to be written + * @param always_gmt + * Always write the date in GMT and not in local time. + */ +void iso_datetime_7(uint8_t *buf, time_t t, int always_gmt); + +/** Records the date/time into a 17 byte buffer (ECMA-119, 8.4.26.1) */ +void iso_datetime_17(uint8_t *buf, time_t t, int always_gmt); + +time_t iso_datetime_read_7(const uint8_t *buf); +time_t iso_datetime_read_17(const uint8_t *buf); + +/** + * Check whether the caller process has read access to the given local file. + * + * @return + * 1 on success (i.e, the process has read access), < 0 on error + * (including ISO_FILE_ACCESS_DENIED on access denied to the specified file + * or any directory on the path). + */ +int iso_eaccess(const char *path); + +/** + * Copy up to \p len chars from \p buf and return this newly allocated + * string. The new string is null-terminated. + */ +char *strcopy(const char *buf, size_t len); + +/** + * Copy up to \p max characters from \p src to \p dest. If \p src has less than + * \p max characters, we pad dest with " " characters. + */ +void strncpy_pad(char *dest, const char *src, size_t max); + +/** + * Convert a Joliet string with a length of \p len bytes to a new string + * in local charset. + */ +char *ucs2str(const char *buf, size_t len); + +typedef struct iso_rbtree IsoRBTree; +typedef struct iso_htable IsoHTable; + +typedef unsigned int (*hash_funtion_t)(const void *key); +typedef int (*compare_function_t)(const void *a, const void *b); +typedef void (*hfree_data_t)(void *key, void *data); + +/** + * Create a new binary tree. libisofs binary trees allow you to add any data + * passing it as a pointer. You must provide a function suitable for compare + * two elements. + * + * @param compare + * A function to compare two keys. It takes a pointer to both keys + * and return 0, -1 or 1 if the first key is equal, less or greater + * than the second one. + * @param tree + * Location where the tree structure will be stored. + */ +int iso_rbtree_new(int (*compare)(const void*, const void*), IsoRBTree **tree); + +/** + * Destroy a given tree. + * + * Note that only the structure itself is deleted. To delete the elements, you + * should provide a valid free_data function. It will be called for each + * element of the tree, so you can use it to free any related data. + */ +void iso_rbtree_destroy(IsoRBTree *tree, void (*free_data)(void *)); + +/** + * Inserts a given element in a Red-Black tree. + * + * @param tree + * the tree where to insert + * @param data + * element to be inserted on the tree. It can't be NULL + * @param item + * if not NULL, it will point to a location where the tree element ptr + * will be stored. If data was inserted, *item == data. If data was + * already on the tree, *item points to the previously inserted object + * that is equal to data. + * @return + * 1 success, 0 element already inserted, < 0 error + */ +int iso_rbtree_insert(IsoRBTree *tree, void *data, void **item); + +/** + * Get the number of elements in a given tree. + */ +size_t iso_rbtree_get_size(IsoRBTree *tree); + +/** + * Get an array view of the elements of the tree. + * + * @param include_item + * Function to select which elements to include in the array. It that takes + * a pointer to an element and returns 1 if the element should be included, + * 0 if not. If you want to add all elements to the array, you can pass a + * NULL pointer. + * @param size + * If not null, will be filled with the number of elements in the array, + * without counting the final NULL item. + * @return + * A sorted array with the contents of the tree, or NULL if there is not + * enought memory to allocate the array. You should free(3) the array when + * no more needed. Note that the array is NULL-terminated, and thus it + * has size + 1 length. + */ +void **iso_rbtree_to_array(IsoRBTree *tree, int (*include_item)(void *), + size_t *size); + +/** + * Create a new hash table. + * + * @param size + * Number of slots in table. + * @param hash + * Function used to generate + */ +int iso_htable_create(size_t size, hash_funtion_t hash, + compare_function_t compare, IsoHTable **table); + +/** + * Put an element in a Hash Table. The element will be identified by + * the given key, that you should use to retrieve the element again. + * + * This function allow duplicates, i.e., two items with the same key. In those + * cases, the value returned by iso_htable_get() is undefined. If you don't + * want to allow duplicates, use iso_htable_put() instead; + * + * Both the key and data pointers will be stored internally, so you should + * free the objects they point to. Use iso_htable_remove() to delete an + * element from the table. + */ +int iso_htable_add(IsoHTable *table, void *key, void *data); + +/** + * Like iso_htable_add(), but this doesn't allow dulpicates. + * + * @return + * 1 success, 0 if an item with the same key already exists, < 0 error + */ +int iso_htable_put(IsoHTable *table, void *key, void *data); + +/** + * Retrieve an element from the given table. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param data + * Will be filled with the element found. Remains untouched if no + * element with the given key is found. + * @return + * 1 if found, 0 if not, < 0 on error + */ +int iso_htable_get(IsoHTable *table, void *key, void **data); + +/** + * Remove an item with the given key from the table. In tables that allow + * duplicates, it is undefined the element that will be deleted. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove(IsoHTable *table, void *key, hfree_data_t free_data); + +/** + * Like remove, but instead of checking for key equality using the compare + * function, it just compare the key pointers. If the table allows duplicates, + * and you provide different keys (i.e. different pointers) to elements + * with same key (i.e. same content), this function ensure the exact element + * is removed. + * + * It has the problem that you must provide the same key pointer, and not just + * a key whose contents are equal. Moreover, if you use the same key (same + * pointer) to identify several objects, what of those are removed is + * undefined. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove_ptr(IsoHTable *table, void *key, hfree_data_t free_data); + +/** + * Destroy the given hash table. + * + * Note that you're responsible to actually destroy the elements by providing + * a valid free_data function. You can pass NULL if you only want to delete + * the hash structure. + */ +void iso_htable_destroy(IsoHTable *table, hfree_data_t free_data); + +/** + * Hash function suitable for keys that are char strings. + */ +unsigned int iso_str_hash(const void *key); + +#endif /*LIBISO_UTIL_H_*/ diff --git a/libisofs/util_htable.c b/libisofs/util_htable.c new file mode 100644 index 0000000..684ce6e --- /dev/null +++ b/libisofs/util_htable.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "util.h" +#include "libisofs.h" + +#include +#include + +/* + * Hash table implementation + */ + +struct iso_hnode +{ + void *key; + void *data; + + /** next node for chaining */ + struct iso_hnode *next; +}; + +struct iso_htable +{ + struct iso_hnode **table; + + size_t size; /**< number of items in table */ + size_t cap; /**< number of slots in table */ + + hash_funtion_t hash; + compare_function_t compare; +}; + +static +struct iso_hnode *iso_hnode_new(void *key, void *data) +{ + struct iso_hnode *node = malloc(sizeof(struct iso_hnode)); + if (node == NULL) + return NULL; + + node->data = data; + node->key = key; + node->next = NULL; + return node; +} + +/** + * Put an element in a Hash Table. The element will be identified by + * the given key, that you should use to retrieve the element again. + * + * This function allow duplicates, i.e., two items with the same key. In those + * cases, the value returned by iso_htable_get() is undefined. If you don't + * want to allow duplicates, use iso_htable_put() instead; + * + * Both the key and data pointers will be stored internally, so you should + * free the objects they point to. Use iso_htable_remove() to delete an + * element from the table. + */ +int iso_htable_add(IsoHTable *table, void *key, void *data) +{ + struct iso_hnode *node; + struct iso_hnode *new; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + new = iso_hnode_new(key, data); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + + table->size++; + new->next = node; + table->table[hash] = new; + return ISO_SUCCESS; +} + +/** + * Like iso_htable_add(), but this doesn't allow dulpicates. + * + * @return + * 1 success, 0 if an item with the same key already exists, < 0 error + */ +int iso_htable_put(IsoHTable *table, void *key, void *data) +{ + struct iso_hnode *node; + struct iso_hnode *new; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + + while (node) { + if (!table->compare(key, node->key)) { + return 0; + } + node = node->next; + } + + new = iso_hnode_new(key, data); + if (new == NULL) { + return ISO_OUT_OF_MEM; + } + + table->size++; + new->next = table->table[hash]; + table->table[hash] = new; + return ISO_SUCCESS; +} + +/** + * Retrieve an element from the given table. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param data + * Will be filled with the element found. Remains untouched if no + * element with the given key is found. + * @return + * 1 if found, 0 if not, < 0 on error + */ +int iso_htable_get(IsoHTable *table, void *key, void **data) +{ + struct iso_hnode *node; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + while (node) { + if (!table->compare(key, node->key)) { + if (data) { + *data = node->data; + } + return 1; + } + node = node->next; + } + return 0; +} + +/** + * Remove an item with the given key from the table. In tables that allow + * duplicates, it is undefined the element that will be deleted. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove(IsoHTable *table, void *key, hfree_data_t free_data) +{ + struct iso_hnode *node, *prev; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + prev = NULL; + while (node) { + if (!table->compare(key, node->key)) { + if (free_data) + free_data(node->key, node->data); + if (prev) { + prev->next = node->next; + } else { + table->table[hash] = node->next; + } + free(node); + table->size--; + return 1; + } + prev = node; + node = node->next; + } + return 0; +} + +/** + * Like remove, but instead of checking for key equality using the compare + * function, it just compare the key pointers. If the table allows duplicates, + * and you provide different keys (i.e. different pointers) to elements + * with same key (i.e. same content), this function ensure the exact element + * is removed. + * + * It has the problem that you must provide the same key pointer, and not just + * a key whose contents are equal. Moreover, if you use the same key (same + * pointer) to identify several objects, what of those are removed is + * undefined. + * + * @param table + * Hash table + * @param key + * Key of the element that will be removed + * @param free_data + * Function that will be called passing as parameters both the key and + * the element that will be deleted. The user can use it to free the + * element. You can pass NULL if you don't want to delete the item itself. + * @return + * 1 success, 0 no element exists with the given key, < 0 error + */ +int iso_htable_remove_ptr(IsoHTable *table, void *key, hfree_data_t free_data) +{ + struct iso_hnode *node, *prev; + unsigned int hash; + + if (table == NULL || key == NULL) { + return ISO_NULL_POINTER; + } + + hash = table->hash(key) % table->cap; + node = table->table[hash]; + prev = NULL; + while (node) { + if (key == node->key) { + if (free_data) + free_data(node->key, node->data); + if (prev) { + prev->next = node->next; + } else { + table->table[hash] = node->next; + } + free(node); + table->size--; + return 1; + } + prev = node; + node = node->next; + } + return 0; +} + +/** + * Hash function suitable for keys that are char strings. + */ +unsigned int iso_str_hash(const void *key) +{ + int i, len; + const char *p = key; + unsigned int h = 2166136261u; + + len = strlen(p); + for (i = 0; i < len; i++) + h = (h * 16777619 ) ^ p[i]; + + return h; +} + +/** + * Destroy the given hash table. + * + * Note that you're responsible to actually destroy the elements by providing + * a valid free_data function. You can pass NULL if you only want to delete + * the hash structure. + */ +void iso_htable_destroy(IsoHTable *table, hfree_data_t free_data) +{ + size_t i; + struct iso_hnode *node, *tmp; + + if (table == NULL) { + return; + } + + for (i = 0; i < table->cap; ++i) { + node = table->table[i]; + while (node) { + tmp = node->next; + if (free_data) + free_data(node->key, node->data); + free(node); + node = tmp; + } + } + free(table->table); + free(table); +} + +/** + * Create a new hash table. + * + * @param size + * Number of slots in table. + * @param hash + * Function used to generate + */ +int iso_htable_create(size_t size, hash_funtion_t hash, + compare_function_t compare, IsoHTable **table) +{ + IsoHTable *t; + + if (table == NULL) { + return ISO_OUT_OF_MEM; + } + + t = malloc(sizeof(IsoHTable)); + if (t == NULL) { + return ISO_OUT_OF_MEM; + } + t->table = calloc(size, sizeof(void*)); + if (t->table == NULL) { + free(t); + return ISO_OUT_OF_MEM; + } + t->cap = size; + t->size = 0; + t->hash = hash; + t->compare = compare; + + *table = t; + return ISO_SUCCESS; +} diff --git a/libisofs/util_rbtree.c b/libisofs/util_rbtree.c new file mode 100644 index 0000000..7a30fc2 --- /dev/null +++ b/libisofs/util_rbtree.c @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ + +#include "util.h" +#include "libisofs.h" + +#include + +/* + * This implementation of Red-Black tree is based on the public domain + * implementation of Julienne Walker. + */ + +struct iso_rbnode +{ + void *data; + struct iso_rbnode *ch[2]; + unsigned int red :1; +}; + +struct iso_rbtree +{ + struct iso_rbnode *root; + size_t size; + int (*compare)(const void *a, const void *b); +}; + +/** + * Create a new binary tree. libisofs binary trees allow you to add any data + * passing it as a pointer. You must provide a function suitable for compare + * two elements. + * + * @param compare + * A function to compare two elements. It takes a pointer to both elements + * and return 0, -1 or 1 if the first element is equal, less or greater + * than the second one. + * @param tree + * Location where the tree structure will be stored. + */ +int iso_rbtree_new(int (*compare)(const void*, const void*), IsoRBTree **tree) +{ + if (compare == NULL || tree == NULL) { + return ISO_NULL_POINTER; + } + *tree = calloc(1, sizeof(IsoRBTree)); + if (*tree == NULL) { + return ISO_OUT_OF_MEM; + } + (*tree)->compare = compare; + return ISO_SUCCESS; +} + +static +void rbtree_destroy_aux(struct iso_rbnode *root, void (*free_data)(void *)) +{ + if (root == NULL) { + return; + } + if (free_data != NULL) { + free_data(root->data); + } + rbtree_destroy_aux(root->ch[0], free_data); + rbtree_destroy_aux(root->ch[1], free_data); + free(root); +} + +/** + * Destroy a given tree. + * + * Note that only the structure itself is deleted. To delete the elements, you + * should provide a valid free_data function. It will be called for each + * element of the tree, so you can use it to free any related data. + */ +void iso_rbtree_destroy(IsoRBTree *tree, void (*free_data)(void *)) +{ + if (tree == NULL) { + return; + } + rbtree_destroy_aux(tree->root, free_data); + free(tree); +} + +static inline +int is_red(struct iso_rbnode *root) +{ + return root != NULL && root->red; +} + +static +struct iso_rbnode *iso_rbtree_single(struct iso_rbnode *root, int dir) +{ + struct iso_rbnode *save = root->ch[!dir]; + + root->ch[!dir] = save->ch[dir]; + save->ch[dir] = root; + + root->red = 1; + save->red = 0; + return save; +} + +static +struct iso_rbnode *iso_rbtree_double(struct iso_rbnode *root, int dir) +{ + root->ch[!dir] = iso_rbtree_single(root->ch[!dir], !dir); + return iso_rbtree_single(root, dir); +} + +static +struct iso_rbnode *iso_rbnode_new(void *data) +{ + struct iso_rbnode *rn = malloc(sizeof(struct iso_rbnode)); + + if (rn != NULL) { + rn->data = data; + rn->red = 1; + rn->ch[0] = NULL; + rn->ch[1] = NULL; + } + + return rn; +} + +/** + * Inserts a given element in a Red-Black tree. + * + * @param tree + * the tree where to insert + * @param data + * element to be inserted on the tree. It can't be NULL + * @param item + * if not NULL, it will point to a location where the tree element ptr + * will be stored. If data was inserted, *item == data. If data was + * already on the tree, *item points to the previously inserted object + * that is equal to data. + * @return + * 1 success, 0 element already inserted, < 0 error + */ +int iso_rbtree_insert(IsoRBTree *tree, void *data, void **item) +{ + struct iso_rbnode *new; + int added = 0; /* has a new node been added? */ + + if (tree == NULL || data == NULL) { + return ISO_NULL_POINTER; + } + + if (tree->root == NULL) { + /* Empty tree case */ + tree->root = iso_rbnode_new(data); + if (tree->root == NULL) { + return ISO_OUT_OF_MEM; + } + new = data; + added = 1; + } else { + struct iso_rbnode head = { 0 }; /* False tree root */ + + struct iso_rbnode *g, *t; /* Grandparent & parent */ + struct iso_rbnode *p, *q; /* Iterator & parent */ + int dir = 0, last = 0; + int comp; + + /* Set up helpers */ + t = &head; + g = p = NULL; + q = t->ch[1] = tree->root; + + /* Search down the tree */ + while (1) { + if (q == NULL) { + /* Insert new node at the bottom */ + p->ch[dir] = q = iso_rbnode_new(data); + if (q == NULL) { + return ISO_OUT_OF_MEM; + } + added = 1; + } else if (is_red(q->ch[0]) && is_red(q->ch[1])) { + /* Color flip */ + q->red = 1; + q->ch[0]->red = 0; + q->ch[1]->red = 0; + } + + /* Fix red violation */ + if (is_red(q) && is_red(p)) { + int dir2 = (t->ch[1] == g); + + if (q == p->ch[last]) { + t->ch[dir2] = iso_rbtree_single(g, !last); + } else { + t->ch[dir2] = iso_rbtree_double(g, !last); + } + } + + comp = tree->compare(q->data, data); + + /* Stop if found */ + if (comp == 0) { + new = q->data; + break; + } + + last = dir; + dir = (comp < 0); + + /* Update helpers */ + if (g != NULL) + t = g; + g = p, p = q; + q = q->ch[dir]; + } + + /* Update root */ + tree->root = head.ch[1]; + } + + /* Make root black */ + tree->root->red = 0; + + if (item != NULL) { + *item = new; + } + if (added) { + /* a new element has been added */ + tree->size++; + return 1; + } else { + return 0; + } +} + +/** + * Get the number of elements in a given tree. + */ +size_t iso_rbtree_get_size(IsoRBTree *tree) +{ + return tree->size; +} + +static +size_t rbtree_to_array_aux(struct iso_rbnode *root, void **array, size_t pos, + int (*include_item)(void *)) +{ + if (root == NULL) { + return pos; + } + pos = rbtree_to_array_aux(root->ch[0], array, pos, include_item); + if (include_item == NULL || include_item(root->data)) { + array[pos++] = root->data; + } + pos = rbtree_to_array_aux(root->ch[1], array, pos, include_item); + return pos; +} + +/** + * Get an array view of the elements of the tree. + * + * @param include_item + * Function to select which elements to include in the array. It that takes + * a pointer to an element and returns 1 if the element should be included, + * 0 if not. If you want to add all elements to the array, you can pass a + * NULL pointer. + * @return + * A sorted array with the contents of the tree, or NULL if there is not + * enought memory to allocate the array. You should free(3) the array when + * no more needed. Note that the array is NULL-terminated, and thus it + * has size + 1 length. + */ +void ** iso_rbtree_to_array(IsoRBTree *tree, int (*include_item)(void *), + size_t *size) +{ + size_t pos; + void **array; + + array = malloc((tree->size + 1) * sizeof(void*)); + if (array == NULL) { + return NULL; + } + + /* fill array */ + pos = rbtree_to_array_aux(tree->root, array, 0, include_item); + array[pos] = NULL; + + array = realloc(array, (pos + 1) * sizeof(void*)); + if (size) { + *size = pos; + } + return array; +} + diff --git a/libisofs/writer.h b/libisofs/writer.h new file mode 100644 index 0000000..8d357f8 --- /dev/null +++ b/libisofs/writer.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007 Vreixo Formoso + * + * This file is part of the libisofs project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See COPYING file for details. + */ +#ifndef LIBISO_IMAGE_WRITER_H_ +#define LIBISO_IMAGE_WRITER_H_ + +#include "ecma119.h" + +struct Iso_Image_Writer +{ + /** + * + */ + int (*compute_data_blocks)(IsoImageWriter *writer); + + int (*write_vol_desc)(IsoImageWriter *writer); + + int (*write_data)(IsoImageWriter *writer); + + int (*free_data)(IsoImageWriter *writer); + + void *data; + Ecma119Image *target; +}; + +/** + * This is the function all Writers shoudl call to write data to image. + * Currently, it is just a wrapper for write(2) Unix system call. + * + * It is implemented in ecma119.c + * + * @return + * 1 on sucess, < 0 error + */ +int iso_write(Ecma119Image *target, void *buf, size_t count); + +int ecma119_writer_create(Ecma119Image *target); + +#endif /*LIBISO_IMAGE_WRITER_H_*/ diff --git a/test/mocked_fsrc.c b/test/mocked_fsrc.c new file mode 100644 index 0000000..13c6b45 --- /dev/null +++ b/test/mocked_fsrc.c @@ -0,0 +1,378 @@ +/* + * + * + */ + +#include "fsource.h" +#include "mocked_fsrc.h" +#include "libisofs.h" + +#include +#include +#include +#include +#include +#include +#include + +static +struct mock_file *path_to_node(IsoFilesystem *fs, const char *path); + +static +char *get_path_aux(struct mock_file *file) +{ + if (file->parent == NULL) { + return strdup(""); + } else { + char *path = get_path_aux(file->parent); + int pathlen = strlen(path); + path = realloc(path, pathlen + strlen(file->name) + 2); + path[pathlen] = '/'; + path[pathlen + 1] = '\0'; + return strcat(path, file->name); + } +} + +static +char* mfs_get_path(IsoFileSource *src) +{ + struct mock_file *data; + data = src->data; + return get_path_aux(data); +} + +static +char* mfs_get_name(IsoFileSource *src) +{ + struct mock_file *data; + data = src->data; + return strdup(data->name); +} + +static +int mfs_lstat(IsoFileSource *src, struct stat *info) +{ + struct mock_file *data; + + if (src == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + data = src->data; + + *info = data->atts; + return ISO_SUCCESS; +} + +static +int mfs_stat(IsoFileSource *src, struct stat *info) +{ + struct mock_file *node; + if (src == NULL || info == NULL) { + return ISO_NULL_POINTER; + } + node = src->data; + + while ( S_ISLNK(node->atts.st_mode) ) { + /* the destination is stated */ + node = path_to_node(node->fs, (char *)node->content); + if (node == NULL) { + return ISO_FILE_ERROR; + } + } + + *info = node->atts; + return ISO_SUCCESS; +} + +static +int mfs_access(IsoFileSource *src) +{ + // TODO not implemented + return ISO_ERROR; +} + +static +int mfs_open(IsoFileSource *src) +{ + // TODO not implemented + return ISO_ERROR; +} + +static +int mfs_close(IsoFileSource *src) +{ + // TODO not implemented + return ISO_ERROR; +} + +static +int mfs_read(IsoFileSource *src, void *buf, size_t count) +{ + // TODO not implemented + return ISO_ERROR; +} + +static +int mfs_readdir(IsoFileSource *src, IsoFileSource **child) +{ + // TODO not implemented + return ISO_ERROR; +} + +static +int mfs_readlink(IsoFileSource *src, char *buf, size_t bufsiz) +{ + struct mock_file *data; + + if (src == NULL || buf == NULL) { + return ISO_NULL_POINTER; + } + + if (bufsiz <= 0) { + return ISO_WRONG_ARG_VALUE; + } + data = src->data; + + if (!S_ISLNK(data->atts.st_mode)) { + return ISO_FILE_IS_NOT_SYMLINK; + } + strncpy(buf, data->content, bufsiz); + buf[bufsiz-1] = '\0'; + return ISO_SUCCESS; +} + +static +IsoFilesystem* mfs_get_filesystem(IsoFileSource *src) +{ + struct mock_file *data; + data = src->data; + return data->fs; +} + +static +void mfs_free(IsoFileSource *src) +{ + /* nothing to do */ +} + +IsoFileSourceIface mfs_class = { + 0, + mfs_get_path, + mfs_get_name, + mfs_lstat, + mfs_stat, + mfs_access, + mfs_open, + mfs_close, + mfs_read, + mfs_readdir, + mfs_readlink, + mfs_get_filesystem, + mfs_free +}; + +/** + * + * @return + * 1 success, < 0 error + */ +static +int mocked_file_source_new(struct mock_file *data, IsoFileSource **src) +{ + IsoFileSource *mocked_src; + + if (src == NULL || data == NULL) { + return ISO_NULL_POINTER; + } + + /* allocate memory */ + mocked_src = malloc(sizeof(IsoFileSource)); + if (mocked_src == NULL) { + free(data); + return ISO_OUT_OF_MEM; + } + + /* fill struct */ + mocked_src->refcount = 1; + mocked_src->data = data; + mocked_src->class = &mfs_class; + + /* take a ref to filesystem */ + //iso_filesystem_ref(fs); + + /* return */ + *src = mocked_src; + return ISO_SUCCESS; +} + +static +struct mock_file *path_to_node(IsoFilesystem *fs, const char *path) +{ + struct mock_file *node; + struct mock_file *dir; + char *ptr, *brk_info, *component; + + /* get the first child at the root of the volume + * that is "/" */ + dir = fs->data; + node = dir; + if (!strcmp(path, "/")) + return node; + + ptr = strdup(path); + + /* get the first component of the path */ + component = strtok_r(ptr, "/", &brk_info); + while (component) { + size_t i; + + if ( !S_ISDIR(node->atts.st_mode) ) { + node=NULL; + break; + } + dir = node; + + node=NULL; + if (!dir->content) { + break; + } + + i = 0; + while (((struct mock_file**)dir->content)[i]) { + if (!strcmp(component, ((struct mock_file**)dir->content)[i]->name)) { + node = ((struct mock_file**)dir->content)[i]; + break; + } + ++i; + } + + /* see if a node could be found */ + if (node==NULL) { + break; + } + component = strtok_r(NULL, "/", &brk_info); + } + free(ptr); + return node; +} + +static +void add_node(struct mock_file *parent, struct mock_file *node) +{ + int i; + + i = 0; + if (parent->content) { + while (((struct mock_file**)parent->content)[i]) { + ++i; + } + } + parent->content = realloc(parent->content, (i+2) * sizeof(void*)); + ((struct mock_file**)parent->content)[i] = node; + ((struct mock_file**)parent->content)[i+1] = NULL; +} + +struct mock_file *test_mocked_fs_get_root(IsoFilesystem *fs) +{ + return fs->data; +} + +int test_mocked_fs_add_dir(const char *name, struct mock_file *p, + struct stat atts, struct mock_file **node) +{ + struct mock_file *dir = calloc(1, sizeof(struct mock_file)); + dir->fs = p->fs; + dir->atts = atts; + dir->name = strdup(name); + dir->parent = p; + add_node(p, dir); + + *node = dir; + return ISO_SUCCESS; +} + +int test_mocked_fs_add_symlink(const char *name, struct mock_file *p, + struct stat atts, const char *dest, struct mock_file **node) +{ + struct mock_file *link = calloc(1, sizeof(struct mock_file)); + link->fs = p->fs; + link->atts = atts; + link->name = strdup(name); + link->parent = p; + add_node(p, link); + link->content = strdup(dest); + *node = link; + return ISO_SUCCESS; +} + +static +int mocked_get_root(IsoFilesystem *fs, IsoFileSource **root) +{ + if (fs == NULL || root == NULL) { + return ISO_NULL_POINTER; + } + return mocked_file_source_new(fs->data, root); +} + +static +int mocked_get_by_path(IsoFilesystem *fs, const char *path, IsoFileSource **file) +{ + struct mock_file *f; + if (fs == NULL || path == NULL || file == NULL) { + return ISO_NULL_POINTER; + } + f = path_to_node(fs, path); + return mocked_file_source_new(f, file); +} + +static +void free_mocked_file(struct mock_file *file) +{ + if (S_ISDIR(file->atts.st_mode)) { + if (file->content) { + int i = 0; + while (((struct mock_file**)file->content)[i]) { + free_mocked_file(((struct mock_file**)file->content)[i]); + ++i; + } + } + } + free(file->content); + free(file->name); + free(file); +} + +static +void mocked_fs_free(IsoFilesystem *fs) +{ + free_mocked_file((struct mock_file *)fs->data); +} + +int test_mocked_filesystem_new(IsoFilesystem **fs) +{ + struct mock_file *root; + IsoFilesystem *filesystem; + + if (fs == NULL) { + return ISO_NULL_POINTER; + } + + root = calloc(1, sizeof(struct mock_file)); + root->atts.st_atime = time(NULL); + root->atts.st_ctime = time(NULL); + root->atts.st_mtime = time(NULL); + root->atts.st_uid = 0; + root->atts.st_gid = 0; + root->atts.st_mode = S_IFDIR | 0777; + + filesystem = malloc(sizeof(IsoFilesystem)); + filesystem->refcount = 1; + root->fs = filesystem; + filesystem->data = root; + filesystem->get_root = mocked_get_root; + filesystem->get_by_path = mocked_get_by_path; + filesystem->free = mocked_fs_free; + *fs = filesystem; + return ISO_SUCCESS; +} + diff --git a/test/mocked_fsrc.h b/test/mocked_fsrc.h new file mode 100644 index 0000000..8e92c61 --- /dev/null +++ b/test/mocked_fsrc.h @@ -0,0 +1,31 @@ +/* + * Mocked objects to simulate an input filesystem. + */ + +#ifndef MOCKED_FSRC_H_ +#define MOCKED_FSRC_H_ + +struct mock_file { + IsoFilesystem *fs; + struct mock_file *parent; + struct stat atts; + char *name; + + /* for links, link dest. For dirs, children */ + void *content; +}; + +/** + * A mocked fs. + */ +int test_mocked_filesystem_new(IsoFilesystem **fs); + +struct mock_file *test_mocked_fs_get_root(IsoFilesystem *fs); + +int test_mocked_fs_add_dir(const char *name, struct mock_file *parent, + struct stat atts, struct mock_file **dir); + +int test_mocked_fs_add_symlink(const char *name, struct mock_file *p, + struct stat atts, const char *dest, struct mock_file **node); + +#endif /*MOCKED_FSRC_H_*/ diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..dd9d0a4 --- /dev/null +++ b/test/test.c @@ -0,0 +1,26 @@ +#include "test.h" + +static void create_test_suite() +{ + add_node_suite(); + add_image_suite(); + add_tree_suite(); + add_util_suite(); + add_rockridge_suite(); + add_stream_suite(); +} + +int main(int argc, char **argv) +{ + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + create_test_suite(); + + /* Run all tests using the console interface */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_cleanup_registry(); + return CU_get_error(); +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..6e5e76a --- /dev/null +++ b/test/test.h @@ -0,0 +1,14 @@ +#ifndef TEST_H_ +#define TEST_H_ + +#include +#include "libisofs.h" + +void add_node_suite(); +void add_image_suite(); +void add_tree_suite(); +void add_util_suite(); +void add_rockridge_suite(); +void add_stream_suite(); + +#endif /*TEST_H_*/ diff --git a/test/test_image.c b/test/test_image.c new file mode 100644 index 0000000..fd0fef6 --- /dev/null +++ b/test/test_image.c @@ -0,0 +1,354 @@ +/* + * Unit test for image.h + */ + + +#include "libisofs.h" +#include "test.h" +#include "image.h" + +#include +#include +#include +#include +#include +#include + + +static void test_iso_image_new() +{ + int ret; + IsoImage *image; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NOT_NULL(image); + CU_ASSERT_EQUAL(image->refcount, 1); + CU_ASSERT_PTR_NOT_NULL(image->root); + + CU_ASSERT_STRING_EQUAL(image->volume_id, "volume_id"); + CU_ASSERT_STRING_EQUAL(image->volset_id, "volume_id"); + + CU_ASSERT_PTR_NULL(image->publisher_id); + CU_ASSERT_PTR_NULL(image->data_preparer_id); + CU_ASSERT_PTR_NULL(image->system_id); + CU_ASSERT_PTR_NULL(image->application_id); + CU_ASSERT_PTR_NULL(image->copyright_file_id); + CU_ASSERT_PTR_NULL(image->abstract_file_id); + CU_ASSERT_PTR_NULL(image->biblio_file_id); + + //CU_ASSERT_PTR_NULL(image->bootcat); + + iso_image_unref(image); +} + +static void test_iso_image_set_volume_id() +{ + int ret; + IsoImage *image; + char *volid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_STRING_EQUAL(image->volume_id, "volume_id"); + + volid = "new volume id"; + iso_image_set_volume_id(image, volid); + CU_ASSERT_STRING_EQUAL(image->volume_id, "new volume id"); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL(image->volume_id, volid); + iso_image_unref(image); +} + +static void test_iso_image_get_volume_id() +{ + int ret; + IsoImage *image; + char *volid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_STRING_EQUAL(iso_image_get_volume_id(image), "volume_id"); + + volid = "new volume id"; + iso_image_set_volume_id(image, volid); + CU_ASSERT_STRING_EQUAL( iso_image_get_volume_id(image), "new volume id" ); + + iso_image_unref(image); +} + +static void test_iso_image_set_publisher_id() +{ + int ret; + IsoImage *image; + char *pubid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->publisher_id); + + pubid = "new publisher id"; + iso_image_set_publisher_id(image, pubid); + CU_ASSERT_STRING_EQUAL( image->publisher_id, "new publisher id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->publisher_id, pubid ); + iso_image_unref(image); +} + +static void test_iso_image_get_publisher_id() +{ + int ret; + IsoImage *image; + char *pubid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->publisher_id); + + pubid = "new publisher id"; + iso_image_set_publisher_id(image, pubid); + CU_ASSERT_STRING_EQUAL(iso_image_get_publisher_id(image), "new publisher id"); + + iso_image_unref(image); +} + +static void test_iso_image_set_data_preparer_id() +{ + int ret; + IsoImage *image; + char *dpid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->data_preparer_id); + + dpid = "new data preparer id"; + iso_image_set_data_preparer_id(image, dpid); + CU_ASSERT_STRING_EQUAL(image->data_preparer_id, "new data preparer id"); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL(image->data_preparer_id, dpid); + iso_image_unref(image); +} + +static void test_iso_image_get_data_preparer_id() +{ + int ret; + IsoImage *image; + char *dpid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->data_preparer_id); + + dpid = "new data preparer id"; + iso_image_set_data_preparer_id(image, dpid); + CU_ASSERT_STRING_EQUAL( iso_image_get_data_preparer_id(image), "new data preparer id" ); + + iso_image_unref(image); +} + +static void test_iso_image_set_system_id() +{ + int ret; + IsoImage *image; + char *sysid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->system_id); + + sysid = "new system id"; + iso_image_set_system_id(image, sysid); + CU_ASSERT_STRING_EQUAL( image->system_id, "new system id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->system_id, sysid ); + iso_image_unref(image); +} + +static void test_iso_image_get_system_id() +{ + int ret; + IsoImage *image; + char *sysid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(iso_image_get_system_id(image)); + + sysid = "new system id"; + iso_image_set_system_id(image, sysid); + CU_ASSERT_STRING_EQUAL( iso_image_get_system_id(image), "new system id" ); + + iso_image_unref(image); +} + +static void test_iso_image_set_application_id() +{ + int ret; + IsoImage *image; + char *appid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->application_id); + + appid = "new application id"; + iso_image_set_application_id(image, appid); + CU_ASSERT_STRING_EQUAL( image->application_id, "new application id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->application_id, appid ); + iso_image_unref(image); +} + +static void test_iso_image_get_application_id() +{ + int ret; + IsoImage *image; + char *appid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(iso_image_get_application_id(image)); + + appid = "new application id"; + iso_image_set_application_id(image, appid); + CU_ASSERT_STRING_EQUAL( iso_image_get_application_id(image), "new application id" ); + + iso_image_unref(image); +} + +static void test_iso_image_set_copyright_file_id() +{ + int ret; + IsoImage *image; + char *copid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->copyright_file_id); + + copid = "new copyright id"; + iso_image_set_copyright_file_id(image, copid); + CU_ASSERT_STRING_EQUAL( image->copyright_file_id, "new copyright id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->copyright_file_id, copid ); + iso_image_unref(image); +} + +static void test_iso_image_get_copyright_file_id() +{ + int ret; + IsoImage *image; + char *copid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(iso_image_get_copyright_file_id(image)); + + copid = "new copyright id"; + iso_image_set_copyright_file_id(image, copid); + CU_ASSERT_STRING_EQUAL( iso_image_get_copyright_file_id(image), "new copyright id" ); + + iso_image_unref(image); +} + +static void test_iso_image_set_abstract_file_id() +{ + int ret; + IsoImage *image; + char *absid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->abstract_file_id); + + absid = "new abstract id"; + iso_image_set_abstract_file_id(image, absid); + CU_ASSERT_STRING_EQUAL( image->abstract_file_id, "new abstract id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->abstract_file_id, absid ); + iso_image_unref(image); +} + +static void test_iso_image_get_abstract_file_id() +{ + int ret; + IsoImage *image; + char *absid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(iso_image_get_abstract_file_id(image)); + + absid = "new abstract id"; + iso_image_set_abstract_file_id(image, absid); + CU_ASSERT_STRING_EQUAL(iso_image_get_abstract_file_id(image), "new abstract id"); + + iso_image_unref(image); +} + +static void test_iso_image_set_biblio_file_id() +{ + int ret; + IsoImage *image; + char *bibid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(image->biblio_file_id); + + bibid = "new biblio id"; + iso_image_set_biblio_file_id(image, bibid); + CU_ASSERT_STRING_EQUAL( image->biblio_file_id, "new biblio id" ); + + /* check string was strdup'ed */ + CU_ASSERT_PTR_NOT_EQUAL( image->biblio_file_id, bibid ); + iso_image_unref(image); +} + +static void test_iso_image_get_biblio_file_id() +{ + int ret; + IsoImage *image; + char *bibid; + + ret = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_PTR_NULL(iso_image_get_biblio_file_id(image)); + + bibid = "new biblio id"; + iso_image_set_biblio_file_id(image, bibid); + CU_ASSERT_STRING_EQUAL(iso_image_get_biblio_file_id(image), "new biblio id"); + + iso_image_unref(image); +} + +void add_image_suite() +{ + CU_pSuite pSuite = CU_add_suite("imageSuite", NULL, NULL); + + CU_add_test(pSuite, "iso_image_new()", test_iso_image_new); + CU_add_test(pSuite, "iso_image_set_volume_id()", test_iso_image_set_volume_id); + CU_add_test(pSuite, "iso_image_get_volume_id()", test_iso_image_get_volume_id); + CU_add_test(pSuite, "iso_image_set_publisher_id()", test_iso_image_set_publisher_id); + CU_add_test(pSuite, "iso_image_get_publisher_id()", test_iso_image_get_publisher_id); + CU_add_test(pSuite, "iso_image_set_data_preparer_id()", test_iso_image_set_data_preparer_id); + CU_add_test(pSuite, "iso_image_get_data_preparer_id()", test_iso_image_get_data_preparer_id); + CU_add_test(pSuite, "iso_image_set_system_id()", test_iso_image_set_system_id); + CU_add_test(pSuite, "iso_image_get_system_id()", test_iso_image_get_system_id); + CU_add_test(pSuite, "iso_image_set_application_id()", test_iso_image_set_application_id); + CU_add_test(pSuite, "iso_image_get_application_id()", test_iso_image_get_application_id); + CU_add_test(pSuite, "iso_image_set_copyright_file_id()", test_iso_image_set_copyright_file_id); + CU_add_test(pSuite, "iso_image_get_copyright_file_id()", test_iso_image_get_copyright_file_id); + CU_add_test(pSuite, "iso_image_set_abstract_file_id()", test_iso_image_set_abstract_file_id); + CU_add_test(pSuite, "iso_image_get_abstract_file_id()", test_iso_image_get_abstract_file_id); + CU_add_test(pSuite, "iso_image_set_biblio_file_id()", test_iso_image_set_biblio_file_id); + CU_add_test(pSuite, "iso_image_get_biblio_file_id()", test_iso_image_get_biblio_file_id); +} diff --git a/test/test_node.c b/test/test_node.c new file mode 100644 index 0000000..6c2084a --- /dev/null +++ b/test/test_node.c @@ -0,0 +1,690 @@ +/* + * Unit test for node.h + */ + +#include "libisofs.h" +#include "node.h" +#include "test.h" + +#include + +static +void test_iso_node_new_root() +{ + int ret; + IsoDir *dir; + + ret = iso_node_new_root(&dir); + CU_ASSERT_EQUAL(ret, ISO_SUCCESS); + + CU_ASSERT_EQUAL(dir->node.refcount, 1); + CU_ASSERT_EQUAL(dir->node.type, LIBISO_DIR); + CU_ASSERT_EQUAL(dir->node.mode, S_IFDIR | 0555); + CU_ASSERT_EQUAL(dir->node.uid, 0); + CU_ASSERT_EQUAL(dir->node.gid, 0); + CU_ASSERT_PTR_NULL(dir->node.name); + CU_ASSERT_EQUAL(dir->node.hidden, 0); + CU_ASSERT_PTR_EQUAL(dir->node.parent, dir); + CU_ASSERT_PTR_NULL(dir->node.next); + CU_ASSERT_EQUAL(dir->nchildren, 0); + CU_ASSERT_PTR_NULL(dir->children); + + iso_node_unref((IsoNode*)dir); +} + +static +void test_iso_node_new_dir() +{ + int ret; + IsoDir *dir; + char *name; + + name = strdup("name1"); + ret = iso_node_new_dir(name, &dir); + CU_ASSERT_EQUAL(ret, ISO_SUCCESS); + CU_ASSERT_EQUAL(dir->node.refcount, 1); + CU_ASSERT_EQUAL(dir->node.type, LIBISO_DIR); + CU_ASSERT_EQUAL(dir->node.mode, S_IFDIR); + CU_ASSERT_EQUAL(dir->node.uid, 0); + CU_ASSERT_EQUAL(dir->node.gid, 0); + CU_ASSERT_EQUAL(dir->node.atime, 0); + CU_ASSERT_EQUAL(dir->node.mtime, 0); + CU_ASSERT_EQUAL(dir->node.ctime, 0); + CU_ASSERT_STRING_EQUAL(dir->node.name, "name1"); + CU_ASSERT_EQUAL(dir->node.hidden, 0); + CU_ASSERT_PTR_NULL(dir->node.parent); + CU_ASSERT_PTR_NULL(dir->node.next); + CU_ASSERT_EQUAL(dir->nchildren, 0); + CU_ASSERT_PTR_NULL(dir->children); + + iso_node_unref((IsoNode*)dir); + + /* try with invalid names */ + ret = iso_node_new_dir("H/DHS/s", &dir); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); + ret = iso_node_new_dir(".", &dir); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); + ret = iso_node_new_dir("..", &dir); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); + ret = iso_node_new_dir(NULL, &dir); + CU_ASSERT_EQUAL(ret, ISO_NULL_POINTER); +} + +static +void test_iso_node_new_symlink() +{ + int ret; + IsoSymlink *link; + char *name, *dest; + + name = strdup("name1"); + dest = strdup("/home"); + ret = iso_node_new_symlink(name, dest, &link); + CU_ASSERT_EQUAL(ret, ISO_SUCCESS); + CU_ASSERT_EQUAL(link->node.refcount, 1); + CU_ASSERT_EQUAL(link->node.type, LIBISO_SYMLINK); + CU_ASSERT_EQUAL(link->node.mode, S_IFLNK); + CU_ASSERT_EQUAL(link->node.uid, 0); + CU_ASSERT_EQUAL(link->node.gid, 0); + CU_ASSERT_EQUAL(link->node.atime, 0); + CU_ASSERT_EQUAL(link->node.mtime, 0); + CU_ASSERT_EQUAL(link->node.ctime, 0); + CU_ASSERT_STRING_EQUAL(link->node.name, "name1"); + CU_ASSERT_EQUAL(link->node.hidden, 0); + CU_ASSERT_PTR_NULL(link->node.parent); + CU_ASSERT_PTR_NULL(link->node.next); + CU_ASSERT_STRING_EQUAL(link->dest, "/home"); + + iso_node_unref((IsoNode*)link); + + /* try with invalid names */ + ret = iso_node_new_symlink("H/DHS/s", "/home", &link); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); + ret = iso_node_new_symlink(".", "/home", &link); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); + ret = iso_node_new_symlink("..", "/home", &link); + CU_ASSERT_EQUAL(ret, ISO_WRONG_ARG_VALUE); +} + +static +void test_iso_node_set_permissions() +{ + IsoNode *node; + node = malloc(sizeof(IsoNode)); + + node->mode = S_IFDIR | 0777; + + /* set permissions propertly */ + iso_node_set_permissions(node, 0555); + CU_ASSERT_EQUAL(node->mode, S_IFDIR | 0555); + iso_node_set_permissions(node, 0640); + CU_ASSERT_EQUAL(node->mode, S_IFDIR | 0640); + + /* try to change file type via this call */ + iso_node_set_permissions(node, S_IFBLK | 0440); + CU_ASSERT_EQUAL(node->mode, S_IFDIR | 0440); + + free(node); +} + +static +void test_iso_node_get_permissions() +{ + IsoNode *node; + mode_t mode; + + node = malloc(sizeof(IsoNode)); + node->mode = S_IFDIR | 0777; + + mode = iso_node_get_permissions(node); + CU_ASSERT_EQUAL(mode, 0777); + + iso_node_set_permissions(node, 0640); + mode = iso_node_get_permissions(node); + CU_ASSERT_EQUAL(mode, 0640); + + iso_node_set_permissions(node, S_IFBLK | 0440); + mode = iso_node_get_permissions(node); + CU_ASSERT_EQUAL(mode, 0440); + + free(node); +} + +static +void test_iso_node_get_mode() +{ + IsoNode *node; + mode_t mode; + + node = malloc(sizeof(IsoNode)); + node->mode = S_IFDIR | 0777; + + mode = iso_node_get_mode(node); + CU_ASSERT_EQUAL(mode, S_IFDIR | 0777); + + iso_node_set_permissions(node, 0640); + mode = iso_node_get_mode(node); + CU_ASSERT_EQUAL(mode, S_IFDIR | 0640); + + iso_node_set_permissions(node, S_IFBLK | 0440); + mode = iso_node_get_mode(node); + CU_ASSERT_EQUAL(mode, S_IFDIR | 0440); + + free(node); +} + +static +void test_iso_node_set_uid() +{ + IsoNode *node; + node = malloc(sizeof(IsoNode)); + + node->uid = 0; + + iso_node_set_uid(node, 23); + CU_ASSERT_EQUAL(node->uid, 23); + iso_node_set_uid(node, 0); + CU_ASSERT_EQUAL(node->uid, 0); + + free(node); +} + +static +void test_iso_node_get_uid() +{ + IsoNode *node; + uid_t uid; + + node = malloc(sizeof(IsoNode)); + node->uid = 0; + + uid = iso_node_get_uid(node); + CU_ASSERT_EQUAL(uid, 0); + + iso_node_set_uid(node, 25); + uid = iso_node_get_uid(node); + CU_ASSERT_EQUAL(uid, 25); + + free(node); +} + +static +void test_iso_node_set_gid() +{ + IsoNode *node; + node = malloc(sizeof(IsoNode)); + + node->gid = 0; + + iso_node_set_gid(node, 23); + CU_ASSERT_EQUAL(node->gid, 23); + iso_node_set_gid(node, 0); + CU_ASSERT_EQUAL(node->gid, 0); + + free(node); +} + +static +void test_iso_node_get_gid() +{ + IsoNode *node; + gid_t gid; + + node = malloc(sizeof(IsoNode)); + node->gid = 0; + + gid = iso_node_get_gid(node); + CU_ASSERT_EQUAL(gid, 0); + + iso_node_set_gid(node, 25); + gid = iso_node_get_gid(node); + CU_ASSERT_EQUAL(gid, 25); + + free(node); +} + +static +void test_iso_dir_add_node() +{ + int result; + IsoDir *dir; + IsoNode *node1, *node2, *node3, *node4, *node5; + + /* init dir with default values, not all field need to be initialized */ + dir = malloc(sizeof(IsoDir)); + dir->children = NULL; + dir->nchildren = 0; + + /* 1st node to be added */ + node1 = calloc(1, sizeof(IsoNode)); + node1->name = "Node1"; + + /* addition of node to an empty dir */ + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(dir->nchildren, 1); + CU_ASSERT_PTR_EQUAL(dir->children, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, dir); + + /* addition of a node, to be inserted before */ + node2 = calloc(1, sizeof(IsoNode)); + node2->name = "A node to be added first"; + + result = iso_dir_add_node(dir, node2, 0); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(dir->nchildren, 2); + CU_ASSERT_PTR_EQUAL(dir->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node2->parent, dir); + + /* addition of a node, to be inserted last */ + node3 = calloc(1, sizeof(IsoNode)); + node3->name = "This node will be inserted last"; + + result = iso_dir_add_node(dir, node3, 0); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(dir->nchildren, 3); + CU_ASSERT_PTR_EQUAL(dir->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_EQUAL(node1->next, node3); + CU_ASSERT_PTR_NULL(node3->next); + CU_ASSERT_PTR_EQUAL(node3->parent, dir); + + /* force some failures */ + result = iso_dir_add_node(NULL, node3, 0); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_dir_add_node(dir, NULL, 0); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + + result = iso_dir_add_node(dir, (IsoNode*)dir, 0); + CU_ASSERT_EQUAL(result, ISO_WRONG_ARG_VALUE); + + /* a node with same name */ + node4 = calloc(1, sizeof(IsoNode)); + node4->name = "This node will be inserted last"; + result = iso_dir_add_node(dir, node4, 0); + CU_ASSERT_EQUAL(result, ISO_NODE_NAME_NOT_UNIQUE); + CU_ASSERT_EQUAL(dir->nchildren, 3); + CU_ASSERT_PTR_EQUAL(dir->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_EQUAL(node1->next, node3); + CU_ASSERT_PTR_NULL(node3->next); + CU_ASSERT_PTR_NULL(node4->parent); + + /* a node already added to another dir should fail */ + node5 = calloc(1, sizeof(IsoNode)); + node5->name = "other node"; + node5->parent = (IsoDir*)node4; + result = iso_dir_add_node(dir, node5, 0); + CU_ASSERT_EQUAL(result, ISO_NODE_ALREADY_ADDED); + + free(node1); + free(node2); + free(node3); + free(node4); + free(node5); + free(dir); +} + +static +void test_iso_dir_get_node() +{ + int result; + IsoDir *dir; + IsoNode *node1, *node2, *node3; + IsoNode *node; + + /* init dir with default values, not all field need to be initialized */ + dir = malloc(sizeof(IsoDir)); + dir->children = NULL; + dir->nchildren = 0; + + /* try to find a node in an empty dir */ + result = iso_dir_get_node(dir, "a inexistent name", &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + + /* add a node */ + node1 = calloc(1, sizeof(IsoNode)); + node1->name = "Node1"; + result = iso_dir_add_node(dir, node1, 0); + + /* try to find a node not existent */ + result = iso_dir_get_node(dir, "a inexistent name", &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + + /* and an existing one */ + result = iso_dir_get_node(dir, "Node1", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + + /* add another node */ + node2 = calloc(1, sizeof(IsoNode)); + node2->name = "A node to be added first"; + result = iso_dir_add_node(dir, node2, 0); + + /* try to find a node not existent */ + result = iso_dir_get_node(dir, "a inexistent name", &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + + /* and the two existing */ + result = iso_dir_get_node(dir, "Node1", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + result = iso_dir_get_node(dir, "A node to be added first", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node2); + + /* insert another node */ + node3 = calloc(1, sizeof(IsoNode)); + node3->name = "This node will be inserted last"; + result = iso_dir_add_node(dir, node3, 0); + + /* get again */ + result = iso_dir_get_node(dir, "a inexistent name", &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + result = iso_dir_get_node(dir, "This node will be inserted last", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node3); + + /* force some failures */ + result = iso_dir_get_node(NULL, "asas", &node); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_dir_get_node(dir, NULL, &node); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + + /* and try with null node */ + result = iso_dir_get_node(dir, "asas", NULL); + CU_ASSERT_EQUAL(result, 0); + result = iso_dir_get_node(dir, "This node will be inserted last", NULL); + CU_ASSERT_EQUAL(result, 1); + + free(node1); + free(node2); + free(node3); + free(dir); +} + +void test_iso_dir_get_children() +{ + int result; + IsoDirIter *iter; + IsoDir *dir; + IsoNode *node, *node1, *node2, *node3; + + /* init dir with default values, not all field need to be initialized */ + dir = malloc(sizeof(IsoDir)); + dir->children = NULL; + dir->nchildren = 0; + + result = iso_dir_get_children(dir, &iter); + CU_ASSERT_EQUAL(result, 1); + + /* item should have no items */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 0); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + iso_dir_iter_free(iter); + + /* 1st node to be added */ + node1 = calloc(1, sizeof(IsoNode)); + node1->name = "Node1"; + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(dir->nchildren, 1); + + /* test iteration again */ + result = iso_dir_get_children(dir, &iter); + CU_ASSERT_EQUAL(result, 1); + + /* iter should have a single item... */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + + /* ...and no more */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 0); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + iso_dir_iter_free(iter); + + /* add another node */ + node2 = calloc(1, sizeof(IsoNode)); + node2->name = "A node to be added first"; + result = iso_dir_add_node(dir, node2, 0); + CU_ASSERT_EQUAL(result, 2); + + result = iso_dir_get_children(dir, &iter); + CU_ASSERT_EQUAL(result, 1); + + /* iter should have two items... */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node2); + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + + /* ...and no more */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 0); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + iso_dir_iter_free(iter); + + /* addition of a 3rd node, to be inserted last */ + node3 = calloc(1, sizeof(IsoNode)); + node3->name = "This node will be inserted last"; + result = iso_dir_add_node(dir, node3, 0); + CU_ASSERT_EQUAL(result, 3); + + result = iso_dir_get_children(dir, &iter); + CU_ASSERT_EQUAL(result, 1); + + /* iter should have 3 items... */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node2); + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 1); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node3); + + /* ...and no more */ + result = iso_dir_iter_has_next(iter); + CU_ASSERT_EQUAL(result, 0); + result = iso_dir_iter_next(iter, &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + iso_dir_iter_free(iter); + + free(node1); + free(node2); + free(node3); + free(dir); +} + +void test_iso_node_take() +{ + int result; + IsoDir *dir; + IsoNode *node1, *node2, *node3; + + /* init dir with default values, not all field need to be initialized */ + dir = malloc(sizeof(IsoDir)); + dir->children = NULL; + dir->nchildren = 0; + + /* 1st node to be added */ + node1 = calloc(1, sizeof(IsoNode)); + node1->name = "Node1"; + + /* addition of node to an empty dir */ + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(result, 1); + + /* and take it */ + result = iso_node_take(node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(dir->nchildren, 0); + CU_ASSERT_PTR_NULL(dir->children); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_NULL(node1->parent); + + /* insert it again */ + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(result, 1); + + /* addition of a 2nd node, to be inserted before */ + node2 = calloc(1, sizeof(IsoNode)); + node2->name = "A node to be added first"; + result = iso_dir_add_node(dir, node2, 0); + CU_ASSERT_EQUAL(result, 2); + + /* take first child */ + result = iso_node_take(node2); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(dir->nchildren, 1); + CU_ASSERT_PTR_EQUAL(dir->children, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, dir); + CU_ASSERT_PTR_NULL(node2->next); + CU_ASSERT_PTR_NULL(node2->parent); + + /* insert node 2 again */ + result = iso_dir_add_node(dir, node2, 0); + CU_ASSERT_EQUAL(result, 2); + + /* now take last child */ + result = iso_node_take(node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(dir->nchildren, 1); + CU_ASSERT_PTR_EQUAL(dir->children, node2); + CU_ASSERT_PTR_NULL(node2->next); + CU_ASSERT_PTR_EQUAL(node2->parent, dir); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_NULL(node1->parent); + + /* insert again node1... */ + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(result, 2); + + /* ...and a 3rd child, to be inserted last */ + node3 = calloc(1, sizeof(IsoNode)); + node3->name = "This node will be inserted last"; + result = iso_dir_add_node(dir, node3, 0); + CU_ASSERT_EQUAL(result, 3); + + /* and take the node in the middle */ + result = iso_node_take(node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(dir->nchildren, 2); + CU_ASSERT_PTR_EQUAL(dir->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node3); + CU_ASSERT_PTR_EQUAL(node2->parent, dir); + CU_ASSERT_PTR_NULL(node3->next); + CU_ASSERT_PTR_EQUAL(node3->parent, dir); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_NULL(node1->parent); + + free(node1); + free(node2); + free(node3); + free(dir); +} + +static +void test_iso_node_set_name() +{ + int result; + IsoDir *dir; + IsoNode *node1, *node2; + + /* init dir with default values, not all field need to be initialized */ + dir = malloc(sizeof(IsoDir)); + dir->children = NULL; + dir->nchildren = 0; + + /* cretae a node */ + node1 = calloc(1, sizeof(IsoNode)); + node1->name = strdup("Node1"); + + /* check name change */ + result = iso_node_set_name(node1, "New name"); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_STRING_EQUAL(node1->name, "New name"); + + /* add node dir */ + result = iso_dir_add_node(dir, node1, 0); + CU_ASSERT_EQUAL(result, 1); + + /* check name change */ + result = iso_node_set_name(node1, "Another name"); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_STRING_EQUAL(node1->name, "Another name"); + + /* addition of a 2nd node */ + node2 = calloc(1, sizeof(IsoNode)); + node2->name = strdup("A node to be added first"); + result = iso_dir_add_node(dir, node2, 0); + CU_ASSERT_EQUAL(result, 2); + + result = iso_node_set_name(node2, "New name"); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_STRING_EQUAL(node2->name, "New name"); + + /* and now try to give an existing name */ + result = iso_node_set_name(node2, "Another name"); + CU_ASSERT_EQUAL(result, ISO_NODE_NAME_NOT_UNIQUE); + CU_ASSERT_STRING_EQUAL(node2->name, "New name"); + + free(node1->name); + free(node2->name); + free(node1); + free(node2); + free(dir); +} + +void add_node_suite() +{ + CU_pSuite pSuite = CU_add_suite("Node Test Suite", NULL, NULL); + + CU_add_test(pSuite, "iso_node_new_root()", test_iso_node_new_root); + CU_add_test(pSuite, "iso_node_new_dir()", test_iso_node_new_dir); + CU_add_test(pSuite, "iso_node_new_symlink()", test_iso_node_new_symlink); + CU_add_test(pSuite, "iso_node_set_permissions()", test_iso_node_set_permissions); + CU_add_test(pSuite, "iso_node_get_permissions()", test_iso_node_get_permissions); + CU_add_test(pSuite, "iso_node_get_mode()", test_iso_node_get_mode); + CU_add_test(pSuite, "iso_node_set_uid()", test_iso_node_set_uid); + CU_add_test(pSuite, "iso_node_get_uid()", test_iso_node_get_uid); + CU_add_test(pSuite, "iso_node_set_gid()", test_iso_node_set_gid); + CU_add_test(pSuite, "iso_node_get_gid()", test_iso_node_get_gid); + CU_add_test(pSuite, "iso_dir_add_node()", test_iso_dir_add_node); + CU_add_test(pSuite, "iso_dir_get_node()", test_iso_dir_get_node); + CU_add_test(pSuite, "iso_dir_get_children()", test_iso_dir_get_children); + CU_add_test(pSuite, "iso_node_take()", test_iso_node_take); + CU_add_test(pSuite, "iso_node_set_name()", test_iso_node_set_name); +} diff --git a/test/test_rockridge.c b/test/test_rockridge.c new file mode 100644 index 0000000..0a3d411 --- /dev/null +++ b/test/test_rockridge.c @@ -0,0 +1,1395 @@ +/* + * Unit test for util.h + * + * This test utiliy functions + */ +#include "test.h" +#include "ecma119_tree.h" +#include "rockridge.h" +#include "node.h" + +#include + +static void test_rrip_calc_len_file() +{ + IsoFile *file; + Ecma119Node *node; + Ecma119Image t; + size_t sua_len = 0, ce_len = 0; + + memset(&t, 0, sizeof(Ecma119Image)); + t.input_charset = "UTF-8"; + t.output_charset = "UTF-8"; + + file = malloc(sizeof(IsoFile)); + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + file->msblock = 0; + file->sort_weight = 0; + file->stream = NULL; /* it is not needed here */ + file->node.type = LIBISO_FILE; + + node = malloc(sizeof(Ecma119Node)); + CU_ASSERT_PTR_NOT_NULL_FATAL(node); + node->node = (IsoNode*)file; + node->parent = (Ecma119Node*)0x55555555; /* just to make it not NULL */ + node->info.file = NULL; /* it is not needed here */ + node->type = ECMA119_FILE; + + /* Case 1. Name fit in System Use field */ + file->node.name = "a small name.txt"; + node->iso_name = "A_SMALL_.TXT"; + + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 0); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 16) + (5 + 3*7) + 1); + + /* Case 2. Name fits exactly */ + file->node.name = "a big name, with 133 characters, that it is the max " + "that fits in System Use field of the directory record " + "PADPADPADADPADPADPADPAD.txt"; + node->iso_name = "A_BIG_NA.TXT"; + + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 0); + /* note that 254 is the max length of a directory record, as it needs to + * be an even number */ + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* case 3. A name just 1 character too big to fit in SUA */ + file->node.name = "a big name, with 133 characters, that it is the max " + "that fits in System Use field of the directory record " + "PADPADPADADPADPADPADPAD1.txt"; + node->iso_name = "A_BIG_NA.TXT"; + + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + /* 28 (the chars moved to include the CE entry) + 5 (header of NM in CE) + + * 1 (the char that originally didn't fit) */ + CU_ASSERT_EQUAL(ce_len, 28 + 5 + 1); + /* note that 254 is the max length of a directory record, as it needs to + * be an even number */ + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* case 4. A 255 characters name */ + file->node.name = "a big name, with 255 characters, that it is the max " + "that a POSIX filename can have. PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"; + node->iso_name = "A_BIG_NA.TXT"; + + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + /* 150 + 5 (header + characters that don't fit in sua) */ + CU_ASSERT_EQUAL(ce_len, 150 + 5); + /* note that 254 is the max length of a directory record, as it needs to + * be an even number */ + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + free(node); + free(file); +} + +static void test_rrip_calc_len_symlink() +{ + IsoSymlink *link; + Ecma119Node *node; + Ecma119Image t; + size_t sua_len = 0, ce_len = 0; + + memset(&t, 0, sizeof(Ecma119Image)); + t.input_charset = "UTF-8"; + t.output_charset = "UTF-8"; + + link = malloc(sizeof(IsoSymlink)); + CU_ASSERT_PTR_NOT_NULL_FATAL(link); + link->node.type = LIBISO_SYMLINK; + + node = malloc(sizeof(Ecma119Node)); + CU_ASSERT_PTR_NOT_NULL_FATAL(node); + node->node = (IsoNode*)link; + node->parent = (Ecma119Node*)0x55555555; /* just to make it not NULL */ + node->type = ECMA119_SYMLINK; + + /* Case 1. Name and dest fit in System Use field */ + link->node.name = "a small name.txt"; + link->dest = "/three/components"; + node->iso_name = "A_SMALL_.TXT"; + + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 0); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 16) + (5 + 3*7) + 1 + + (5 + 2 + (2+5) + (2+10)) ); + + /* case 2. name + dest fits exactly */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 0); + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* case 3. name fits, dest is one byte larger to fit */ + /* 3.a extra byte in dest */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./big/destination/with/10/componentsk"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 60); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* 3.b extra byte in name */ + link->node.name = "this name will have 75 characters as it is the max " + "that fits in the SUx.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 59); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 75) + (5 + 3*7) + 28); + + /* case 4. name seems to fit, but SL no, and when CE is added NM + * doesn't fit too */ + /* 4.a it just fits */ + link->node.name = "this name will have 105 characters as it is just the " + "max that fits in the SU once we add the CE entry.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 59); + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* 4.b it just fits, the the component ends in '/' */ + link->node.name = "this name will have 105 characters as it is just the " + "max that fits in the SU once we add the CE entry.txt"; + link->dest = "./and/../a/./big/destination/with/10/components/"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 59); + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* 4.c extra char in name, that forces it to be divided */ + link->node.name = "this name will have 105 characters as it is just the " + "max that fits in the SU once we add the CE entryc.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 59 + 6); + CU_ASSERT_EQUAL(sua_len, 254 - 46); + + /* 5 max destination length to fit in a single SL entry (250) */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./very/big/destination/with/10/components/that/" + "conforms/the/max/that/fits/in/a/single/SL/as/it/takes/" + "just/two/hundred/and/fifty/bytes/bytes/bytes/bytes/bytes" + "/bytes/bytes/bytes/bytes/bytes/bytes/../bytes"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 255); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* 6 min destination length to need two SL entries (251) */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./very/big/destination/with/10/components/that/" + "conforms/the/max/that/fits/in/a/single/SL/as/it/takes/" + "just/two/hundred/and/fifty/bytes/bytes/bytes/bytes/bytes" + "/bytes/bytes/bytes/bytes/bytes/bytes/../bytess"; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 261); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* 7 destination with big component that need to be splited + * in two SL entries */ + /* 7.a just fits in one */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "very big component with 248 characters, that is the max that" + " fits in a single SL entry. Take care that SL header takes 5 " + "bytes, and component header another 2, one for size, another" + " for flags. This last characters are just padding to get 248 " + "bytes."; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 255); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* 7.b doesn't fits by one character */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "very big component with 249 characters, that is the min that" + " doesn't fit in a single SL entry. Take care that SL header " + "takes 5 bytes, and component header another 2, one for size," + " another for flags. This last characters are just padding to" + " get 249."; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 255 + (5+2+1)); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* 7.c several components before, such as it has just the right len + * to fit in the SL entry plus another one */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "the/first/components/take just 245 characters/and thus the " + "first SL entry will have/255 - 5 - 245 - 2 (component " + "header) = 3/ just the space for another component with a " + "single character/This makes that last component fit in exactly 2 " + "SLs/very big component with 249 characters, that is the min " + "that doesn't fit in a single SL entry. Take care that SL " + "header takes 5 bytes, and component header another 2, one " + "for size, another for flags. This last characters are just " + "padding to get 249."; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 255 + 255); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* + * 7.d several components before, and then a big component that doesn't + * fit in the 1st SL entry and another one. That case needs a 3rd SL entry, + * but instead of divide the component in 2 entries, we put it in 2, + * without completelly fill the first one. + */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "the/first/components/take just 245 characters/and thus the " + "first SL entry will have/255 - 5 - 245 - 2 (component " + "header) = 3/ just the space for another component with a " + "single character/This makes that last component fit in exactly 2 " + "SLs/very big component with 250 characters, that is the min " + "that does not fit in a single SL entry. Take care that SL " + "header takes 5 bytes, and component header another 2, one " + "for size, another for flags. This last characters are just " + "padding to get 249."; + node->iso_name = "THIS_NAM.TXT"; + sua_len = rrip_calc_len(&t, node, 0, 255 - 46, &ce_len); + CU_ASSERT_EQUAL(ce_len, 252 + 255 + 9); + CU_ASSERT_EQUAL(sua_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + free(link); + free(node); +} + +static +void susp_info_free(struct susp_info *susp) +{ + size_t i; + + for (i = 0; i < susp->n_susp_fields; ++i) { + free(susp->susp_fields[i]); + } + free(susp->susp_fields); + + for (i = 0; i < susp->n_ce_susp_fields; ++i) { + free(susp->ce_susp_fields[i]); + } + free(susp->ce_susp_fields); +} + +static +void test_rrip_get_susp_fields_file() +{ + IsoFile *file; + Ecma119Node *node; + int ret; + struct susp_info susp; + Ecma119Image t; + uint8_t *entry; + + memset(&t, 0, sizeof(Ecma119Image)); + t.input_charset = "UTF-8"; + t.output_charset = "UTF-8"; + + file = malloc(sizeof(IsoFile)); + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + file->msblock = 0; + file->sort_weight = 0; + file->stream = NULL; /* it is not needed here */ + file->node.type = LIBISO_FILE; + file->node.mode = S_IFREG | 0555; + file->node.uid = 235; + file->node.gid = 654; + file->node.mtime = 675757578; + file->node.atime = 546462546; + file->node.ctime = 323245342; + + node = malloc(sizeof(Ecma119Node)); + CU_ASSERT_PTR_NOT_NULL_FATAL(node); + node->node = (IsoNode*)file; + node->parent = (Ecma119Node*)0x55555555; /* just to make it not NULL */ + node->info.file = NULL; /* it is not needed here */ + node->type = ECMA119_FILE; + node->nlink = 1; + node->ino = 0x03447892; + + /* Case 1. Name fit in System Use field */ + file->node.name = "a small name.txt"; + node->iso_name = "A_SMALL_.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 0); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 0); + CU_ASSERT_EQUAL(susp.n_susp_fields, 3); /* PX + TF + NM */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 16) + (5 + 3*7) + 1); + + /* PX is the first entry */ + entry = susp.susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'P'); + CU_ASSERT_EQUAL(entry[1], 'X'); + CU_ASSERT_EQUAL(entry[2], 44); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), S_IFREG | 0555); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), S_IFREG | 0555); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 1); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 235); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 235); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 28, 4), 654); + CU_ASSERT_EQUAL(iso_read_msb(entry + 32, 4), 654); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 36, 4), 0x03447892); + CU_ASSERT_EQUAL(iso_read_msb(entry + 40, 4), 0x03447892); + + /* TF is the second entry */ + entry = susp.susp_fields[1]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'T'); + CU_ASSERT_EQUAL(entry[1], 'F'); + CU_ASSERT_EQUAL(entry[2], 5 + 3*7); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0x0E); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 5), 675757578); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 12), 546462546); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 19), 323245342); + + /* NM is the last entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 16); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "a small name.txt", 16); + + susp_info_free(&susp); + + /* Case 2. Name fits exactly */ + file->node.name = "a big name, with 133 characters, that it is the max " + "that fits in System Use field of the directory record " + "PADPADPADADPADPADPADPAD.txt"; + node->iso_name = "A_BIG_NA.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 0); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 0); + CU_ASSERT_EQUAL(susp.suf_len, 254 - 46); + + CU_ASSERT_EQUAL(susp.n_susp_fields, 3); /* PX + TF + NM */ + + /* NM is the last entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 133); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "a big name, with 133 characters, that " + "it is the max that fits in System Use field of the " + "directory record PADPADPADADPADPADPADPAD.txt", 133); + + susp_info_free(&susp); + + /* case 3. A name just 1 character too big to fit in SUA */ + file->node.name = "a big name, with 133 characters, that it is the max " + "that fits in System Use field of the directory record " + "PADPADPADADPADPADPADPAD1.txt"; + node->iso_name = "A_BIG_NA.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 28 + 5 + 1); + CU_ASSERT_EQUAL(susp.suf_len, 254 - 46); + + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* NM */ + + /* test NM entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 105); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 1); /* CONTINUE */ + CU_ASSERT_NSTRING_EQUAL(entry + 5, "a big name, with 133 characters, that " + "it is the max that fits in System Use field of the " + "directory record", 105); + + /* and CE entry */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 34); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 34); + + /* and check Continuation area */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 29); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, " PADPADPADADPADPADPADPAD1.txt", 29); + + susp_info_free(&susp); + + /* case 4. A 255 characters name */ + file->node.name = "a big name, with 255 characters, that it is the max " + "that a POSIX filename can have. PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"; + node->iso_name = "A_BIG_NA.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + susp.ce_block = 12; + susp.ce_len = 456; + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 150 + 5 + 456); + CU_ASSERT_EQUAL(susp.suf_len, 254 - 46); + + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* NM */ + + /* test NM entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 105); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 1); /* CONTINUE */ + CU_ASSERT_NSTRING_EQUAL(entry + 5, "a big name, with 255 characters, that " + "it is the max that a POSIX filename can have. PPP" + "PPPPPPPPPPPPPPPPPP", 105); + + /* and CE entry */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + + /* block, offset, size */ + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 12); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 12); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 456); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 456); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 155); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 155); + + /* and check Continuation area */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 150); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + "PPPPPPPPPPPPPP", 150); + + susp_info_free(&susp); + + free(node); + free(file); +} + +static void test_rrip_get_susp_fields_symlink() +{ + IsoSymlink *link; + Ecma119Node *node; + Ecma119Image t; + int ret; + struct susp_info susp; + uint8_t *entry; + + memset(&t, 0, sizeof(Ecma119Image)); + t.input_charset = "UTF-8"; + t.output_charset = "UTF-8"; + + link = malloc(sizeof(IsoSymlink)); + CU_ASSERT_PTR_NOT_NULL_FATAL(link); + link->node.type = LIBISO_SYMLINK; + link->node.mode = S_IFREG | 0555; + link->node.uid = 235; + link->node.gid = 654; + link->node.mtime = 675757578; + link->node.atime = 546462546; + link->node.ctime = 323245342; + + node = malloc(sizeof(Ecma119Node)); + CU_ASSERT_PTR_NOT_NULL_FATAL(node); + node->node = (IsoNode*)link; + node->parent = (Ecma119Node*)0x55555555; /* just to make it not NULL */ + node->type = ECMA119_SYMLINK; + node->nlink = 1; + node->ino = 0x03447892; + + /* Case 1. Name and dest fit in System Use field */ + link->node.name = "a small name.txt"; + link->dest = "/three/components"; + node->iso_name = "A_SMALL_.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 0); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 0); + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + SL */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 16) + (5 + 3*7) + 1 + + (5 + 2 + (2 + 5) + (2 + 10))); + + /* PX is the first entry */ + entry = susp.susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'P'); + CU_ASSERT_EQUAL(entry[1], 'X'); + CU_ASSERT_EQUAL(entry[2], 44); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), S_IFREG | 0555); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), S_IFREG | 0555); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 1); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 235); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 235); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 28, 4), 654); + CU_ASSERT_EQUAL(iso_read_msb(entry + 32, 4), 654); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 36, 4), 0x03447892); + CU_ASSERT_EQUAL(iso_read_msb(entry + 40, 4), 0x03447892); + + /* TF is the second entry */ + entry = susp.susp_fields[1]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'T'); + CU_ASSERT_EQUAL(entry[1], 'F'); + CU_ASSERT_EQUAL(entry[2], 5 + 3*7); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0x0E); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 5), 675757578); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 12), 546462546); + CU_ASSERT_EQUAL(iso_datetime_read_7(entry + 19), 323245342); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 16); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "a small name.txt", 16); + + /* SL is the last entry */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 5 + 2 + (2 + 5) + (2 + 10)); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x8); /* root */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 5); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "three", 5); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 16, "components", 10); + + susp_info_free(&susp); + + /* case 2. name + dest fits exactly */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 0); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 0); + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + SL */ + CU_ASSERT_EQUAL(susp.suf_len, 254 - 46); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 74); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 74 characters as " + "it is the max that fits in the SU.txt", 74); + + /* SL is the last entry */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 5 + 2 + 5 + 2 + 3 + 2 + 5 + 13 + 6 + 4 + 12); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "components", 10); + + susp_info_free(&susp); + + /* case 3. name fits, dest is one byte larger to fit */ + /* 3.a extra byte in dest */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./big/destination/with/10/componentsk"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 60); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 74) + (5 + 3*7) + 1 + 28); + + /* PX is the first entry */ + entry = susp.susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'P'); + CU_ASSERT_EQUAL(entry[1], 'X'); + CU_ASSERT_EQUAL(entry[2], 44); + + /* TF is the second entry */ + entry = susp.susp_fields[1]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'T'); + CU_ASSERT_EQUAL(entry[1], 'F'); + CU_ASSERT_EQUAL(entry[2], 5 + 3*7); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 74); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 74 characters as " + "it is the max that fits in the SU.txt", 74); + + /* and CE entry is last */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 60); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 60); + + + /* finally, SL is the single entry in CE */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 60); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "componentsk", 11); + + susp_info_free(&susp); + + /* 3.b extra byte in name */ + link->node.name = "this name will have 75 characters as it is the max " + "that fits in the SUx.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 59); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 75) + (5 + 3*7) + 28); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 75); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 75 characters as it " + "is the max that fits in the SUx.txt", 75); + + /* and CE entry is last */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 59); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 59); + + + /* finally, SL is the single entry in CE */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 59); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "components", 10); + + susp_info_free(&susp); + + /* case 4. name seems to fit, but SL no, and when CE is added NM + * doesn't fit too */ + /* 4.a it just fits */ + link->node.name = "this name will have 105 characters as it is just the " + "max that fits in the SU once we add the CE entry.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 59); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 3*7) + (5 + 105) + 28); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 105); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 105 characters as " + "it is just the max that fits in the SU once we " + "add the CE entry.txt", 105); + + /* and CE entry is last */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 59); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 59); + + /* finally, SL is the single entry in CE */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 59); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "components", 10); + + susp_info_free(&susp); + + /* 4.b it just fits, the the component ends in '/' */ + link->node.name = "this name will have 105 characters as it is just the " + "max that fits in the SU once we add the CE entry.txt"; + link->dest = "./and/../a/./big/destination/with/10/components/"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 59); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 3*7) + (5 + 105) + 28); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 105); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 105 characters as " + "it is just the max that fits in the SU once we " + "add the CE entry.txt", 105); + + /* and CE entry is last */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 59); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 59); + + /* finally, SL is the single entry in CE */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 59); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "components", 10); + + susp_info_free(&susp); + + /* 4.c extra char in name, that forces it to be divided */ + link->node.name = "this name will have 106 characters as it is just the " + "max that fits in the SU once we add the CE entryc.txt"; + link->dest = "./and/../a/./big/destination/with/10/components"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 6 + 59); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 2); /* NM + SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 3*7) + (5 + 105) + 28); + + /* NM is the 3rd entry */ + entry = susp.susp_fields[2]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 105); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0x1); /* continue */ + CU_ASSERT_NSTRING_EQUAL(entry + 5, "this name will have 106 characters as " + "it is just the max that fits in the SU once we " + "add the CE entryc.tx", 105); + + /* and CE entry is last */ + entry = susp.susp_fields[3]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'C'); + CU_ASSERT_EQUAL(entry[1], 'E'); + CU_ASSERT_EQUAL(entry[2], 28); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 4, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 8, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 12, 4), 0); + CU_ASSERT_EQUAL(iso_read_msb(entry + 16, 4), 0); + CU_ASSERT_EQUAL(iso_read_lsb(entry + 20, 4), 59 + 6); + CU_ASSERT_EQUAL(iso_read_msb(entry + 24, 4), 59 + 6); + + /* NM is the 1st entry in CE */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'N'); + CU_ASSERT_EQUAL(entry[1], 'M'); + CU_ASSERT_EQUAL(entry[2], 5 + 1); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + CU_ASSERT_EQUAL(entry[5], 't'); + + /* finally, SL is the single entry in CE */ + entry = susp.ce_susp_fields[1]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 59); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "big", 3); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[24], 0); + CU_ASSERT_EQUAL(entry[25], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 26, "destination", 11); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[37], 0); + CU_ASSERT_EQUAL(entry[38], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 39, "with", 4); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "10", 2); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[47], 0); + CU_ASSERT_EQUAL(entry[48], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 49, "components", 10); + + susp_info_free(&susp); + + /* 5 max destination length to fit in a single SL entry (250) */ + link->node.name = "this name will have 74 characters as it is the max " + "that fits in the SU.txt"; + link->dest = "./and/../a/./very/big/destination/with/10/components/that/" + "conforms/the/max/that/fits/in/a single SL/entry as it takes " + "just two hundred and/fifty bytes bytes bytes bytes/bytes" + " bytes bytes bytes bytes bytes bytes bytes bytes/../bytes"; + node->iso_name = "THIS_NAM.TXT"; + + memset(&susp, 0, sizeof(struct susp_info)); + ret = rrip_get_susp_fields(&t, node, 0, 255 - 46, &susp); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(susp.ce_len, 255); + CU_ASSERT_EQUAL(susp.n_ce_susp_fields, 1); /* SL */ + CU_ASSERT_EQUAL(susp.n_susp_fields, 4); /* PX + TF + NM + CE */ + CU_ASSERT_EQUAL(susp.suf_len, 44 + (5 + 3*7) + (5 + 74) + 1 + 28); + + /* just check the SL entry */ + entry = susp.ce_susp_fields[0]; + CU_ASSERT_PTR_NOT_NULL(entry); + CU_ASSERT_EQUAL(entry[0], 'S'); + CU_ASSERT_EQUAL(entry[1], 'L'); + CU_ASSERT_EQUAL(entry[2], 255); + CU_ASSERT_EQUAL(entry[3], 1); + CU_ASSERT_EQUAL(entry[4], 0); + + /* first component */ + CU_ASSERT_EQUAL(entry[5], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[6], 0); + + /* 2nd component */ + CU_ASSERT_EQUAL(entry[7], 0); + CU_ASSERT_EQUAL(entry[8], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 9, "and", 3); + + /* 3rd component */ + CU_ASSERT_EQUAL(entry[12], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[13], 0); + + /* 4th component */ + CU_ASSERT_EQUAL(entry[14], 0); + CU_ASSERT_EQUAL(entry[15], 1); + CU_ASSERT_EQUAL(entry[16], 'a'); + + /* 5th component */ + CU_ASSERT_EQUAL(entry[17], 0x2); /* current */ + CU_ASSERT_EQUAL(entry[18], 0); + + /* 6th component */ + CU_ASSERT_EQUAL(entry[19], 0); + CU_ASSERT_EQUAL(entry[20], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 21, "very", 4); + + /* 7th component */ + CU_ASSERT_EQUAL(entry[25], 0); + CU_ASSERT_EQUAL(entry[26], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 27, "big", 3); + + /* 8th component */ + CU_ASSERT_EQUAL(entry[30], 0); + CU_ASSERT_EQUAL(entry[31], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 32, "destination", 11); + + /* 9th component */ + CU_ASSERT_EQUAL(entry[43], 0); + CU_ASSERT_EQUAL(entry[44], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 45, "with", 4); + + /* 10th component */ + CU_ASSERT_EQUAL(entry[49], 0); + CU_ASSERT_EQUAL(entry[50], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 51, "10", 2); + + /* 11th component */ + CU_ASSERT_EQUAL(entry[53], 0); + CU_ASSERT_EQUAL(entry[54], 10); + CU_ASSERT_NSTRING_EQUAL(entry + 55, "components", 10); + + /* 12th component */ + CU_ASSERT_EQUAL(entry[65], 0); + CU_ASSERT_EQUAL(entry[66], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 67, "that", 4); + + /* 13th component */ + CU_ASSERT_EQUAL(entry[71], 0); + CU_ASSERT_EQUAL(entry[72], 8); + CU_ASSERT_NSTRING_EQUAL(entry + 73, "conforms", 8); + + /* 14th component */ + CU_ASSERT_EQUAL(entry[81], 0); + CU_ASSERT_EQUAL(entry[82], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 83, "the", 3); + + /* 15th component */ + CU_ASSERT_EQUAL(entry[86], 0); + CU_ASSERT_EQUAL(entry[87], 3); + CU_ASSERT_NSTRING_EQUAL(entry + 88, "max", 3); + + /* 16th component */ + CU_ASSERT_EQUAL(entry[91], 0); + CU_ASSERT_EQUAL(entry[92], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 93, "that", 4); + + /* 17th component */ + CU_ASSERT_EQUAL(entry[97], 0); + CU_ASSERT_EQUAL(entry[98], 4); + CU_ASSERT_NSTRING_EQUAL(entry + 99, "fits", 4); + + /* 18th component */ + CU_ASSERT_EQUAL(entry[103], 0); + CU_ASSERT_EQUAL(entry[104], 2); + CU_ASSERT_NSTRING_EQUAL(entry + 105, "in", 2); + + /* 19th component */ + CU_ASSERT_EQUAL(entry[107], 0); + CU_ASSERT_EQUAL(entry[108], 11); + CU_ASSERT_NSTRING_EQUAL(entry + 109, "a single SL", 11); + + /* 20th component */ + CU_ASSERT_EQUAL(entry[120], 0); + CU_ASSERT_EQUAL(entry[121], 38); + CU_ASSERT_NSTRING_EQUAL(entry + 122, "entry as it takes " + "just two hundred and", 38); + + /* 21th component */ + CU_ASSERT_EQUAL(entry[160], 0); + CU_ASSERT_EQUAL(entry[161], 29); + CU_ASSERT_NSTRING_EQUAL(entry + 162, "fifty bytes bytes bytes bytes", 29); + + /* 22th component */ + CU_ASSERT_EQUAL(entry[191], 0); + CU_ASSERT_EQUAL(entry[192], 53); + CU_ASSERT_NSTRING_EQUAL(entry + 193, "bytes bytes bytes bytes bytes bytes" + " bytes bytes bytes", 53); + + /* 23th component */ + CU_ASSERT_EQUAL(entry[246], 0x4); /* parent */ + CU_ASSERT_EQUAL(entry[247], 0); + + /* 24th component */ + CU_ASSERT_EQUAL(entry[248], 0); + CU_ASSERT_EQUAL(entry[249], 5); + CU_ASSERT_NSTRING_EQUAL(entry + 250, "bytes", 5); + + susp_info_free(&susp); + + free(node); + free(link); +} + +void add_rockridge_suite() +{ + CU_pSuite pSuite = CU_add_suite("RockRidge Suite", NULL, NULL); + + CU_add_test(pSuite, "rrip_calc_len(file)", test_rrip_calc_len_file); + CU_add_test(pSuite, "rrip_calc_len(symlink)", test_rrip_calc_len_symlink); + CU_add_test(pSuite, "rrip_get_susp_fields(file)", test_rrip_get_susp_fields_file); + CU_add_test(pSuite, "rrip_get_susp_fields(symlink)", test_rrip_get_susp_fields_symlink); +} diff --git a/test/test_stream.c b/test/test_stream.c new file mode 100644 index 0000000..35e1466 --- /dev/null +++ b/test/test_stream.c @@ -0,0 +1,155 @@ +/* + * Unit test for util.h + * + * This test utiliy functions + */ +#include "test.h" +#include "stream.h" + +#include + +static +void test_mem_new() +{ + int ret; + IsoStream *stream; + unsigned char *buf; + + buf = malloc(3000); + ret = iso_memory_stream_new(buf, 3000, &stream); + CU_ASSERT_EQUAL(ret, 1); + iso_stream_unref(stream); + + ret = iso_memory_stream_new(NULL, 3000, &stream); + CU_ASSERT_EQUAL(ret, ISO_NULL_POINTER); + + ret = iso_memory_stream_new(buf, 3000, NULL); + CU_ASSERT_EQUAL(ret, ISO_NULL_POINTER); +} + +static +void test_mem_open() +{ + int ret; + IsoStream *stream; + unsigned char *buf; + + buf = malloc(3000); + ret = iso_memory_stream_new(buf, 3000, &stream); + CU_ASSERT_EQUAL(ret, 1); + + ret = iso_stream_open(stream); + CU_ASSERT_EQUAL(ret, 1); + + /* try to open an already opened stream */ + ret = iso_stream_open(stream); + CU_ASSERT_EQUAL(ret, ISO_FILE_ALREADY_OPENNED); + + ret = iso_stream_close(stream); + CU_ASSERT_EQUAL(ret, 1); + + ret = iso_stream_close(stream); + CU_ASSERT_EQUAL(ret, ISO_FILE_NOT_OPENNED); + + iso_stream_unref(stream); +} + +static +void test_mem_read() +{ + int ret; + IsoStream *stream; + unsigned char *buf; + unsigned char rbuf[3000]; + + buf = malloc(3000); + memset(buf, 2, 200); + memset(buf + 200, 3, 300); + memset(buf + 500, 5, 500); + memset(buf + 1000, 10, 1000); + memset(buf + 2000, 56, 48); + memset(buf + 2048, 137, 22); + memset(buf + 2070, 13, 130); + memset(buf + 2200, 88, 800); + + ret = iso_memory_stream_new(buf, 3000, &stream); + CU_ASSERT_EQUAL(ret, 1); + + /* test 1: read full buf */ + ret = iso_stream_open(stream); + CU_ASSERT_EQUAL(ret, 1); + + ret = iso_stream_read(stream, rbuf, 3000); + CU_ASSERT_EQUAL(ret, 3000); + CU_ASSERT_NSTRING_EQUAL(rbuf, buf, 3000); + + /* read again is EOF */ + ret = iso_stream_read(stream, rbuf, 20); + CU_ASSERT_EQUAL(ret, 0); + + ret = iso_stream_close(stream); + CU_ASSERT_EQUAL(ret, 1); + + /* test 2: read more than available bytes */ + ret = iso_stream_open(stream); + CU_ASSERT_EQUAL(ret, 1); + + ret = iso_stream_read(stream, rbuf, 3050); + CU_ASSERT_EQUAL(ret, 3000); + CU_ASSERT_NSTRING_EQUAL(rbuf, buf, 3000); + + /* read again is EOF */ + ret = iso_stream_read(stream, rbuf, 20); + CU_ASSERT_EQUAL(ret, 0); + + ret = iso_stream_close(stream); + CU_ASSERT_EQUAL(ret, 1); + + /* test 3: read in block size */ + ret = iso_stream_open(stream); + CU_ASSERT_EQUAL(ret, 1); + + ret = iso_stream_read(stream, rbuf, 2048); + CU_ASSERT_EQUAL(ret, 2048); + CU_ASSERT_NSTRING_EQUAL(rbuf, buf, 2048); + + ret = iso_stream_read(stream, rbuf, 2048); + CU_ASSERT_EQUAL(ret, 3000 - 2048); + CU_ASSERT_NSTRING_EQUAL(rbuf, buf + 2048, 3000 - 2048); + + ret = iso_stream_read(stream, rbuf, 20); + CU_ASSERT_EQUAL(ret, 0); + + ret = iso_stream_close(stream); + CU_ASSERT_EQUAL(ret, 1); + + iso_stream_unref(stream); +} + +static +void test_mem_size() +{ + int ret; + off_t size; + IsoStream *stream; + unsigned char *buf; + + buf = malloc(3000); + ret = iso_memory_stream_new(buf, 3000, &stream); + CU_ASSERT_EQUAL(ret, 1); + + size = iso_stream_get_size(stream); + CU_ASSERT_EQUAL(size, 3000); + + iso_stream_unref(stream); +} + +void add_stream_suite() +{ + CU_pSuite pSuite = CU_add_suite("IsoStreamSuite", NULL, NULL); + + CU_add_test(pSuite, "iso_memory_stream_new()", test_mem_new); + CU_add_test(pSuite, "MemoryStream->open()", test_mem_open); + CU_add_test(pSuite, "MemoryStream->read()", test_mem_read); + CU_add_test(pSuite, "MemoryStream->get_size()", test_mem_size); +} diff --git a/test/test_tree.c b/test/test_tree.c new file mode 100644 index 0000000..8379439 --- /dev/null +++ b/test/test_tree.c @@ -0,0 +1,566 @@ +/* + * Unit test for node.h + */ + +#include "libisofs.h" +#include "node.h" +#include "image.h" + +#include "test.h" +#include "mocked_fsrc.h" + +#include + +static +void test_iso_tree_add_new_dir() +{ + int result; + IsoDir *root; + IsoDir *node1, *node2, *node3, *node4; + IsoImage *image; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + result = iso_tree_add_new_dir(root, "Dir1", &node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(root->nchildren, 1); + CU_ASSERT_PTR_EQUAL(root->children, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node1->node.parent, root); + CU_ASSERT_EQUAL(node1->node.type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node1->node.name, "Dir1"); + + /* creation of a second dir, to be inserted before */ + result = iso_tree_add_new_dir(root, "A node to be added first", &node2); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(root->nchildren, 2); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node2->node.parent, root); + CU_ASSERT_EQUAL(node2->node.type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node2->node.name, "A node to be added first"); + + /* creation of a 3rd node, to be inserted last */ + result = iso_tree_add_new_dir(root, "This node will be inserted last", &node3); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_EQUAL(node3->node.parent, root); + CU_ASSERT_EQUAL(node3->node.type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node3->node.name, "This node will be inserted last"); + + /* force some failures */ + result = iso_tree_add_new_dir(NULL, "dsadas", &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_tree_add_new_dir(root, NULL, &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + + /* try to insert a new dir with same name */ + result = iso_tree_add_new_dir(root, "This node will be inserted last", &node4); + CU_ASSERT_EQUAL(result, ISO_NODE_NAME_NOT_UNIQUE); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_NULL(node4); + + /* but pointer to new dir can be null */ + result = iso_tree_add_new_dir(root, "Another node", NULL); + CU_ASSERT_EQUAL(result, 4); + CU_ASSERT_EQUAL(root->nchildren, 4); + CU_ASSERT_PTR_EQUAL(node2->node.next->next, node1); + CU_ASSERT_STRING_EQUAL(node2->node.next->name, "Another node"); + + iso_image_unref(image); +} + +static +void test_iso_tree_add_new_symlink() +{ + int result; + IsoDir *root; + IsoSymlink *node1, *node2, *node3, *node4; + IsoImage *image; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + result = iso_tree_add_new_symlink(root, "Link1", "/path/to/dest", &node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(root->nchildren, 1); + CU_ASSERT_PTR_EQUAL(root->children, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node1->node.parent, root); + CU_ASSERT_EQUAL(node1->node.type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node1->node.name, "Link1"); + CU_ASSERT_STRING_EQUAL(node1->dest, "/path/to/dest"); + + /* creation of a second link, to be inserted before */ + result = iso_tree_add_new_symlink(root, "A node to be added first", "/home/me", &node2); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(root->nchildren, 2); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node2->node.parent, root); + CU_ASSERT_EQUAL(node2->node.type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node2->node.name, "A node to be added first"); + CU_ASSERT_STRING_EQUAL(node2->dest, "/home/me"); + + /* creation of a 3rd node, to be inserted last */ + result = iso_tree_add_new_symlink(root, "This node will be inserted last", + "/path/to/dest", &node3); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_EQUAL(node3->node.parent, root); + CU_ASSERT_EQUAL(node3->node.type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node3->node.name, "This node will be inserted last"); + CU_ASSERT_STRING_EQUAL(node3->dest, "/path/to/dest"); + + /* force some failures */ + result = iso_tree_add_new_symlink(NULL, "dsadas", "/path/to/dest", &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_tree_add_new_symlink(root, NULL, "/path/to/dest", &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_tree_add_new_symlink(root, "dsadas", NULL, &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + + /* try to insert a new link with same name */ + result = iso_tree_add_new_symlink(root, "This node will be inserted last", "/", &node4); + CU_ASSERT_EQUAL(result, ISO_NODE_NAME_NOT_UNIQUE); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_NULL(node4); + + /* but pointer to new link can be null */ + result = iso_tree_add_new_symlink(root, "Another node", ".", NULL); + CU_ASSERT_EQUAL(result, 4); + CU_ASSERT_EQUAL(root->nchildren, 4); + CU_ASSERT_PTR_EQUAL(node2->node.next->next, node1); + CU_ASSERT_EQUAL(node2->node.next->type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(((IsoSymlink*)(node2->node.next))->dest, "."); + CU_ASSERT_STRING_EQUAL(node2->node.next->name, "Another node"); + + iso_image_unref(image); +} + +static +void test_iso_tree_add_new_special() +{ + int result; + IsoDir *root; + IsoSpecial *node1, *node2, *node3, *node4; + IsoImage *image; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + result = iso_tree_add_new_special(root, "Special1", S_IFSOCK | 0644, 0, &node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(root->nchildren, 1); + CU_ASSERT_PTR_EQUAL(root->children, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node1->node.parent, root); + CU_ASSERT_EQUAL(node1->node.type, LIBISO_SPECIAL); + CU_ASSERT_STRING_EQUAL(node1->node.name, "Special1"); + CU_ASSERT_EQUAL(node1->dev, 0); + CU_ASSERT_EQUAL(node1->node.mode, S_IFSOCK | 0644); + + /* creation of a block dev, to be inserted before */ + result = iso_tree_add_new_special(root, "A node to be added first", S_IFBLK | 0640, 34, &node2); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(root->nchildren, 2); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_NULL(node1->node.next); + CU_ASSERT_PTR_EQUAL(node2->node.parent, root); + CU_ASSERT_EQUAL(node2->node.type, LIBISO_SPECIAL); + CU_ASSERT_STRING_EQUAL(node2->node.name, "A node to be added first"); + CU_ASSERT_EQUAL(node2->dev, 34); + CU_ASSERT_EQUAL(node2->node.mode, S_IFBLK | 0640); + + /* creation of a 3rd node, to be inserted last */ + result = iso_tree_add_new_special(root, "This node will be inserted last", + S_IFCHR | 0440, 345, &node3); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_EQUAL(node3->node.parent, root); + CU_ASSERT_EQUAL(node3->node.type, LIBISO_SPECIAL); + CU_ASSERT_STRING_EQUAL(node3->node.name, "This node will be inserted last"); + CU_ASSERT_EQUAL(node3->dev, 345); + CU_ASSERT_EQUAL(node3->node.mode, S_IFCHR | 0440); + + /* force some failures */ + result = iso_tree_add_new_special(NULL, "dsadas", S_IFBLK | 0440, 345, &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_tree_add_new_special(root, NULL, S_IFBLK | 0440, 345, &node4); + CU_ASSERT_EQUAL(result, ISO_NULL_POINTER); + result = iso_tree_add_new_special(root, "dsadas", S_IFDIR | 0666, 0, &node4); + CU_ASSERT_EQUAL(result, ISO_WRONG_ARG_VALUE); + result = iso_tree_add_new_special(root, "dsadas", S_IFREG | 0666, 0, &node4); + CU_ASSERT_EQUAL(result, ISO_WRONG_ARG_VALUE); + result = iso_tree_add_new_special(root, "dsadas", S_IFLNK | 0666, 0, &node4); + CU_ASSERT_EQUAL(result, ISO_WRONG_ARG_VALUE); + + /* try to insert a new special file with same name */ + result = iso_tree_add_new_special(root, "This node will be inserted last", S_IFIFO | 0666, 0, &node4); + CU_ASSERT_EQUAL(result, ISO_NODE_NAME_NOT_UNIQUE); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->node.next, node1); + CU_ASSERT_PTR_EQUAL(node1->node.next, node3); + CU_ASSERT_PTR_NULL(node3->node.next); + CU_ASSERT_PTR_NULL(node4); + + /* but pointer to new special can be null */ + result = iso_tree_add_new_special(root, "Another node", S_IFIFO | 0666, 0, NULL); + CU_ASSERT_EQUAL(result, 4); + CU_ASSERT_EQUAL(root->nchildren, 4); + CU_ASSERT_PTR_EQUAL(node2->node.next->next, node1); + CU_ASSERT_EQUAL(node2->node.next->type, LIBISO_SPECIAL); + CU_ASSERT_EQUAL(((IsoSpecial*)(node2->node.next))->dev, 0); + CU_ASSERT_EQUAL(node2->node.next->mode, S_IFIFO | 0666); + CU_ASSERT_STRING_EQUAL(node2->node.next->name, "Another node"); + + iso_image_unref(image); +} + +static +void test_iso_tree_add_node_dir() +{ + int result; + IsoDir *root; + IsoNode *node1, *node2, *node3, *node4; + IsoImage *image; + IsoFilesystem *fs; + struct stat info; + struct mock_file *mroot, *dir1, *dir2; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + /* replace image filesystem with out mockep one */ + iso_filesystem_unref(image->fs); + result = test_mocked_filesystem_new(&fs); + CU_ASSERT_EQUAL(result, 1); + image->fs = fs; + mroot = test_mocked_fs_get_root(fs); + + /* add some files to the filesystem */ + info.st_mode = S_IFDIR | 0550; + info.st_uid = 20; + info.st_gid = 21; + info.st_atime = 234523; + info.st_ctime = 23432432; + info.st_mtime = 1111123; + result = test_mocked_fs_add_dir("dir", mroot, info, &dir1); + CU_ASSERT_EQUAL(result, 1); + + info.st_mode = S_IFDIR | 0555; + info.st_uid = 30; + info.st_gid = 31; + info.st_atime = 3234523; + info.st_ctime = 3234432; + info.st_mtime = 3111123; + result = test_mocked_fs_add_dir("a child node", dir1, info, &dir2); + CU_ASSERT_EQUAL(result, 1); + + info.st_mode = S_IFDIR | 0750; + info.st_uid = 40; + info.st_gid = 41; + info.st_atime = 4234523; + info.st_ctime = 4234432; + info.st_mtime = 4111123; + result = test_mocked_fs_add_dir("another one", dir1, info, &dir2); + CU_ASSERT_EQUAL(result, 1); + + info.st_mode = S_IFDIR | 0755; + info.st_uid = 50; + info.st_gid = 51; + info.st_atime = 5234523; + info.st_ctime = 5234432; + info.st_mtime = 5111123; + result = test_mocked_fs_add_dir("zzzz", mroot, info, &dir2); + CU_ASSERT_EQUAL(result, 1); + + /* and now insert those files to the image */ + result = iso_tree_add_node(image, root, "/dir", &node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(root->nchildren, 1); + CU_ASSERT_PTR_EQUAL(root->children, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_EQUAL(node1->type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node1->name, "dir"); + CU_ASSERT_EQUAL(node1->mode, S_IFDIR | 0550); + CU_ASSERT_EQUAL(node1->uid, 20); + CU_ASSERT_EQUAL(node1->gid, 21); + CU_ASSERT_EQUAL(node1->atime, 234523); + CU_ASSERT_EQUAL(node1->ctime, 23432432); + CU_ASSERT_EQUAL(node1->mtime, 1111123); + CU_ASSERT_PTR_NULL(((IsoDir*)node1)->children); + CU_ASSERT_EQUAL(((IsoDir*)node1)->nchildren, 0); + + result = iso_tree_add_node(image, root, "/dir/a child node", &node2); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(root->nchildren, 2); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_PTR_EQUAL(node2->parent, root); + CU_ASSERT_EQUAL(node2->type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node2->name, "a child node"); + CU_ASSERT_EQUAL(node2->mode, S_IFDIR | 0555); + CU_ASSERT_EQUAL(node2->uid, 30); + CU_ASSERT_EQUAL(node2->gid, 31); + CU_ASSERT_EQUAL(node2->atime, 3234523); + CU_ASSERT_EQUAL(node2->ctime, 3234432); + CU_ASSERT_EQUAL(node2->mtime, 3111123); + CU_ASSERT_PTR_NULL(((IsoDir*)node2)->children); + CU_ASSERT_EQUAL(((IsoDir*)node2)->nchildren, 0); + + result = iso_tree_add_node(image, root, "/dir/another one", &node3); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node3); + CU_ASSERT_PTR_EQUAL(node3->next, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_PTR_EQUAL(node2->parent, root); + CU_ASSERT_PTR_EQUAL(node3->parent, root); + CU_ASSERT_EQUAL(node3->type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node3->name, "another one"); + CU_ASSERT_EQUAL(node3->mode, S_IFDIR | 0750); + CU_ASSERT_EQUAL(node3->uid, 40); + CU_ASSERT_EQUAL(node3->gid, 41); + CU_ASSERT_EQUAL(node3->atime, 4234523); + CU_ASSERT_EQUAL(node3->ctime, 4234432); + CU_ASSERT_EQUAL(node3->mtime, 4111123); + CU_ASSERT_PTR_NULL(((IsoDir*)node3)->children); + CU_ASSERT_EQUAL(((IsoDir*)node3)->nchildren, 0); + + result = iso_tree_add_node(image, root, "/zzzz", &node4); + CU_ASSERT_EQUAL(result, 4); + CU_ASSERT_EQUAL(root->nchildren, 4); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node3); + CU_ASSERT_PTR_EQUAL(node3->next, node1); + CU_ASSERT_PTR_EQUAL(node1->next, node4); + CU_ASSERT_PTR_NULL(node4->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_PTR_EQUAL(node2->parent, root); + CU_ASSERT_PTR_EQUAL(node3->parent, root); + CU_ASSERT_PTR_EQUAL(node4->parent, root); + CU_ASSERT_EQUAL(node4->type, LIBISO_DIR); + CU_ASSERT_STRING_EQUAL(node4->name, "zzzz"); + CU_ASSERT_EQUAL(node4->mode, S_IFDIR | 0755); + CU_ASSERT_EQUAL(node4->uid, 50); + CU_ASSERT_EQUAL(node4->gid, 51); + CU_ASSERT_EQUAL(node4->atime, 5234523); + CU_ASSERT_EQUAL(node4->ctime, 5234432); + CU_ASSERT_EQUAL(node4->mtime, 5111123); + CU_ASSERT_PTR_NULL(((IsoDir*)node4)->children); + CU_ASSERT_EQUAL(((IsoDir*)node4)->nchildren, 0); + + iso_image_unref(image); +} + +static +void test_iso_tree_add_node_link() +{ + int result; + IsoDir *root; + IsoNode *node1, *node2, *node3; + IsoImage *image; + IsoFilesystem *fs; + struct stat info; + struct mock_file *mroot, *link; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + /* replace image filesystem with out mockep one */ + iso_filesystem_unref(image->fs); + result = test_mocked_filesystem_new(&fs); + CU_ASSERT_EQUAL(result, 1); + image->fs = fs; + mroot = test_mocked_fs_get_root(fs); + + /* add some files to the filesystem */ + info.st_mode = S_IFLNK | 0777; + info.st_uid = 12; + info.st_gid = 13; + info.st_atime = 123444; + info.st_ctime = 123555; + info.st_mtime = 123666; + result = test_mocked_fs_add_symlink("link1", mroot, info, "/home/me", &link); + CU_ASSERT_EQUAL(result, 1); + + info.st_mode = S_IFLNK | 0555; + info.st_uid = 22; + info.st_gid = 23; + info.st_atime = 223444; + info.st_ctime = 223555; + info.st_mtime = 223666; + result = test_mocked_fs_add_symlink("another link", mroot, info, "/", &link); + CU_ASSERT_EQUAL(result, 1); + + info.st_mode = S_IFLNK | 0750; + info.st_uid = 32; + info.st_gid = 33; + info.st_atime = 323444; + info.st_ctime = 323555; + info.st_mtime = 323666; + result = test_mocked_fs_add_symlink("this will be the last", mroot, info, "/etc", &link); + CU_ASSERT_EQUAL(result, 1); + + /* and now insert those files to the image */ + result = iso_tree_add_node(image, root, "/link1", &node1); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_EQUAL(root->nchildren, 1); + CU_ASSERT_PTR_EQUAL(root->children, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_EQUAL(node1->type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node1->name, "link1"); + CU_ASSERT_EQUAL(node1->mode, S_IFLNK | 0777); + CU_ASSERT_EQUAL(node1->uid, 12); + CU_ASSERT_EQUAL(node1->gid, 13); + CU_ASSERT_EQUAL(node1->atime, 123444); + CU_ASSERT_EQUAL(node1->ctime, 123555); + CU_ASSERT_EQUAL(node1->mtime, 123666); + CU_ASSERT_STRING_EQUAL(((IsoSymlink*)node1)->dest, "/home/me"); + + result = iso_tree_add_node(image, root, "/another link", &node2); + CU_ASSERT_EQUAL(result, 2); + CU_ASSERT_EQUAL(root->nchildren, 2); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_NULL(node1->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_PTR_EQUAL(node2->parent, root); + CU_ASSERT_EQUAL(node2->type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node2->name, "another link"); + CU_ASSERT_EQUAL(node2->mode, S_IFLNK | 0555); + CU_ASSERT_EQUAL(node2->uid, 22); + CU_ASSERT_EQUAL(node2->gid, 23); + CU_ASSERT_EQUAL(node2->atime, 223444); + CU_ASSERT_EQUAL(node2->ctime, 223555); + CU_ASSERT_EQUAL(node2->mtime, 223666); + CU_ASSERT_STRING_EQUAL(((IsoSymlink*)node2)->dest, "/"); + + result = iso_tree_add_node(image, root, "/this will be the last", &node3); + CU_ASSERT_EQUAL(result, 3); + CU_ASSERT_EQUAL(root->nchildren, 3); + CU_ASSERT_PTR_EQUAL(root->children, node2); + CU_ASSERT_PTR_EQUAL(node2->next, node1); + CU_ASSERT_PTR_EQUAL(node1->next, node3); + CU_ASSERT_PTR_NULL(node3->next); + CU_ASSERT_PTR_EQUAL(node1->parent, root); + CU_ASSERT_PTR_EQUAL(node2->parent, root); + CU_ASSERT_PTR_EQUAL(node3->parent, root); + CU_ASSERT_EQUAL(node3->type, LIBISO_SYMLINK); + CU_ASSERT_STRING_EQUAL(node3->name, "this will be the last"); + CU_ASSERT_EQUAL(node3->mode, S_IFLNK | 0750); + CU_ASSERT_EQUAL(node3->uid, 32); + CU_ASSERT_EQUAL(node3->gid, 33); + CU_ASSERT_EQUAL(node3->atime, 323444); + CU_ASSERT_EQUAL(node3->ctime, 323555); + CU_ASSERT_EQUAL(node3->mtime, 323666); + CU_ASSERT_STRING_EQUAL(((IsoSymlink*)node3)->dest, "/etc"); + + iso_image_unref(image); +} + +static +void test_iso_tree_path_to_node() +{ + int result; + IsoDir *root; + IsoDir *node1, *node2, *node11; + IsoNode *node; + IsoImage *image; + IsoFilesystem *fs; + + result = iso_image_new("volume_id", &image); + CU_ASSERT_EQUAL(result, 1); + root = iso_image_get_root(image); + CU_ASSERT_PTR_NOT_NULL(root); + + /* replace image filesystem with out mockep one */ + iso_filesystem_unref(image->fs); + result = test_mocked_filesystem_new(&fs); + CU_ASSERT_EQUAL(result, 1); + image->fs = fs; + + /* add some files */ + result = iso_tree_add_new_dir(root, "Dir1", &node1); + CU_ASSERT_EQUAL(result, 1); + result = iso_tree_add_new_dir(root, "Dir2", (IsoDir**)&node2); + CU_ASSERT_EQUAL(result, 2); + result = iso_tree_add_new_dir((IsoDir*)node1, "Dir11", (IsoDir**)&node11); + CU_ASSERT_EQUAL(result, 1); + + /* retrive some items */ + result = iso_tree_path_to_node(image, "/", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, root); + result = iso_tree_path_to_node(image, "/Dir1", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node1); + result = iso_tree_path_to_node(image, "/Dir2", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node2); + result = iso_tree_path_to_node(image, "/Dir1/Dir11", &node); + CU_ASSERT_EQUAL(result, 1); + CU_ASSERT_PTR_EQUAL(node, node11); + + /* some failtures */ + result = iso_tree_path_to_node(image, "/Dir2/Dir11", &node); + CU_ASSERT_EQUAL(result, 0); + CU_ASSERT_PTR_NULL(node); + + iso_image_unref(image); +} + +void add_tree_suite() +{ + CU_pSuite pSuite = CU_add_suite("Iso Tree Suite", NULL, NULL); + + CU_add_test(pSuite, "iso_tree_add_new_dir()", test_iso_tree_add_new_dir); + CU_add_test(pSuite, "iso_tree_add_new_symlink()", test_iso_tree_add_new_symlink); + CU_add_test(pSuite, "iso_tree_add_new_special()", test_iso_tree_add_new_special); + CU_add_test(pSuite, "iso_tree_add_node() [1. dir]", test_iso_tree_add_node_dir); + CU_add_test(pSuite, "iso_tree_add_node() [2. symlink]", test_iso_tree_add_node_link); + CU_add_test(pSuite, "iso_tree_path_to_node()", test_iso_tree_path_to_node); + +} diff --git a/test/test_util.c b/test/test_util.c new file mode 100644 index 0000000..0852e92 --- /dev/null +++ b/test/test_util.c @@ -0,0 +1,1072 @@ +/* + * Unit test for util.h + * + * This test utiliy functions + */ +#include "test.h" +#include "util.h" + +#include +#include +#include + +static void test_int_pow() +{ + CU_ASSERT_EQUAL(int_pow(1, 2), 1); + CU_ASSERT_EQUAL(int_pow(2, 2), 4); + CU_ASSERT_EQUAL(int_pow(0, 2), 0); + CU_ASSERT_EQUAL(int_pow(-1, 2), 1); + CU_ASSERT_EQUAL(int_pow(-1, 3), -1); + CU_ASSERT_EQUAL(int_pow(3, 2), 9); + CU_ASSERT_EQUAL(int_pow(3, 10), 59049); +} + +static void test_strconv() +{ + int ret; + char *out; + + /* Prova de cadeia com codificação ISO-8859-15 */ + unsigned char in1[45] = + {0x50, 0x72, 0x6f, 0x76, 0x61, 0x20, 0x64, 0x65, 0x20, 0x63, 0x61, + 0x64, 0x65, 0x69, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x20, 0x63, 0x6f, + 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0xe7, 0xe3, 0x6f, 0x20, 0x49, + 0x53, 0x4f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, 0x31, 0x35, 0x0a, + 0x00}; /* encoded in ISO-8859-15 */ + unsigned char out1[47] = + {0x50, 0x72, 0x6f, 0x76, 0x61, 0x20, 0x64, 0x65, 0x20, 0x63, 0x61, + 0x64, 0x65, 0x69, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x20, 0x63, 0x6f, + 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0xc3, 0xa7, 0xc3, 0xa3, 0x6f, + 0x20, 0x49, 0x53, 0x4f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, 0x31, + 0x35, 0x0a, 0x00}; /* encoded in UTF-8 */ + unsigned char in2[45] = + {0x50, 0x72, 0x6f, 0x76, 0x61, 0x20, 0x64, 0x65, 0x20, 0x63, 0x61, + 0x64, 0x65, 0x69, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x20, 0x63, 0x6f, + 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0xe7, 0xe3, 0x6f, 0x20, 0x49, + 0x53, 0x4f, 0x2d, 0x38, 0x38, 0xff, 0xff, 0x2d, 0x31, 0x35, 0x0a, + 0x00}; /* incorrect encoding */ + + /* ISO-8859-15 to UTF-8 */ + ret = strconv((char*)in1, "ISO-8859-15", "UTF-8", &out); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_STRING_EQUAL(out, (char*)out1); + free(out); + + /* UTF-8 to ISO-8859-15 */ + ret = strconv((char*)out1, "UTF-8", "ISO-8859-15", &out); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_STRING_EQUAL(out, (char*)in1); + free(out); + + /* try with an incorrect input */ + ret = strconv((char*)in2, "UTF-8", "ISO-8859-15", &out); + CU_ASSERT_EQUAL(ret, ISO_CHARSET_CONV_ERROR); +} + +static void test_div_up() +{ + CU_ASSERT_EQUAL( DIV_UP(1, 2), 1 ); + CU_ASSERT_EQUAL( DIV_UP(2, 2), 1 ); + CU_ASSERT_EQUAL( DIV_UP(0, 2), 0 ); + CU_ASSERT_EQUAL( DIV_UP(-1, 2), 0 ); + CU_ASSERT_EQUAL( DIV_UP(3, 2), 2 ); +} + +static void test_round_up() +{ + CU_ASSERT_EQUAL( ROUND_UP(1, 2), 2 ); + CU_ASSERT_EQUAL( ROUND_UP(2, 2), 2 ); + CU_ASSERT_EQUAL( ROUND_UP(0, 2), 0 ); + CU_ASSERT_EQUAL( ROUND_UP(-1, 2), 0 ); + CU_ASSERT_EQUAL( ROUND_UP(3, 2), 4 ); + CU_ASSERT_EQUAL( ROUND_UP(15, 7), 21 ); + CU_ASSERT_EQUAL( ROUND_UP(13, 7), 14 ); + CU_ASSERT_EQUAL( ROUND_UP(14, 7), 14 ); +} + +static void test_iso_lsb_msb() +{ + uint8_t buf[4]; + uint32_t num; + + num = 0x01020304; + iso_lsb(buf, num, 4); + CU_ASSERT_EQUAL( buf[0], 0x04 ); + CU_ASSERT_EQUAL( buf[1], 0x03 ); + CU_ASSERT_EQUAL( buf[2], 0x02 ); + CU_ASSERT_EQUAL( buf[3], 0x01 ); + + iso_msb(buf, num, 4); + CU_ASSERT_EQUAL( buf[0], 0x01 ); + CU_ASSERT_EQUAL( buf[1], 0x02 ); + CU_ASSERT_EQUAL( buf[2], 0x03 ); + CU_ASSERT_EQUAL( buf[3], 0x04 ); + + iso_lsb(buf, num, 2); + CU_ASSERT_EQUAL( buf[0], 0x04 ); + CU_ASSERT_EQUAL( buf[1], 0x03 ); + + iso_msb(buf, num, 2); + CU_ASSERT_EQUAL( buf[0], 0x03 ); + CU_ASSERT_EQUAL( buf[1], 0x04 ); +} + +static void test_iso_read_lsb_msb() +{ + uint8_t buf[4]; + uint32_t num; + + buf[0] = 0x04; + buf[1] = 0x03; + buf[2] = 0x02; + buf[3] = 0x01; + + num = iso_read_lsb(buf, 4); + CU_ASSERT_EQUAL(num, 0x01020304); + + num = iso_read_msb(buf, 4); + CU_ASSERT_EQUAL(num, 0x04030201); + + num = iso_read_lsb(buf, 2); + CU_ASSERT_EQUAL(num, 0x0304); + + num = iso_read_msb(buf, 2); + CU_ASSERT_EQUAL(num, 0x0403); +} + +static void test_iso_bb() +{ + uint8_t buf[8]; + uint32_t num; + + num = 0x01020304; + iso_bb(buf, num, 4); + CU_ASSERT_EQUAL( buf[0], 0x04 ); + CU_ASSERT_EQUAL( buf[1], 0x03 ); + CU_ASSERT_EQUAL( buf[2], 0x02 ); + CU_ASSERT_EQUAL( buf[3], 0x01 ); + CU_ASSERT_EQUAL( buf[4], 0x01 ); + CU_ASSERT_EQUAL( buf[5], 0x02 ); + CU_ASSERT_EQUAL( buf[6], 0x03 ); + CU_ASSERT_EQUAL( buf[7], 0x04 ); + + iso_bb(buf, num, 2); + CU_ASSERT_EQUAL( buf[0], 0x04 ); + CU_ASSERT_EQUAL( buf[1], 0x03 ); + CU_ASSERT_EQUAL( buf[2], 0x03 ); + CU_ASSERT_EQUAL( buf[3], 0x04 ); +} + +static void test_iso_datetime_7() +{ + uint8_t buf[7]; + time_t t1, t2, tr; + char *tz; + struct tm tp; + + tz = getenv("TZ"); + + setenv("TZ", "", 1); + tzset(); + + strptime("01-03-1976 13:27:45", "%d-%m-%Y %T", &tp); + t1 = mktime(&tp); /* t1 in GMT */ + + strptime("01-07-2007 13:27:45", "%d-%m-%Y %T", &tp); + t2 = mktime(&tp); /* t1 in GMT (summer time) */ + + /* ----------------- European Timezones ----------------------*/ + setenv("TZ", "Europe/Madrid", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 14); /* hour (GMT+1) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 4); /* GMT+1 hour for CET */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + iso_datetime_7(buf, t2, 0); + CU_ASSERT_EQUAL(buf[0], 107); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 7); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 15); /* hour (GMT+2, summer time) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 8); /* GMT+2 hour for CEST */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Europe/London", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 13); /* hour (GMT+0) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 0); /* GMT+0 */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + iso_datetime_7(buf, t2, 0); + CU_ASSERT_EQUAL(buf[0], 107); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 7); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 14); /* hour (GMT+1, summer time) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 4); /* GMT+1 */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + /* ----------------- American Timezones ----------------------*/ + setenv("TZ", "America/New_York", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 8); /* hour */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], -5*4); /* GMT-5 for EST */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + /* ----------------- Asia Timezones ----------------------*/ + setenv("TZ", "Asia/Hong_Kong", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 21); /* hour */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 8*4); /* GMT+8 */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + /* read from another timestamp */ + setenv("TZ", "Europe/Madrid", 1); + tzset(); + + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + /* ----------------- Africa Timezones ----------------------*/ + + /* Africa country without Daylight saving time */ + setenv("TZ", "Africa/Luanda", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 14); /* hour (GMT+1) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 4); /* GMT+1 hour */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + iso_datetime_7(buf, t2, 0); + CU_ASSERT_EQUAL(buf[0], 107); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 7); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 14); /* hour (GMT+1, no summer time) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 4); /* GMT+1 hour */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + /* ----------------- Australia Timezones ----------------------*/ + + /* this is GMT+9:30 (note that in South summer is winter in North) */ + setenv("TZ", "Australia/Broken_Hill", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 23); /* hour GMT+9+1 (summer time!!) */ + CU_ASSERT_EQUAL(buf[4], 57); /* minute + 30 */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 42); /* GMT+9:30 hour + 1 (summer time) */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + iso_datetime_7(buf, t2, 0); + CU_ASSERT_EQUAL(buf[0], 107); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 7); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 22); /* hour (GMT+9) */ + CU_ASSERT_EQUAL(buf[4], 57); /* minute +30 */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 38); /* GMT+9:30 */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + /* ----------------- Pacific Timezones ----------------------*/ + + /* this is GMT+13, the max supported */ + setenv("TZ", "Pacific/Tongatapu", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 2); /* day */ + CU_ASSERT_EQUAL(buf[3], 2); /* hour (GMT+13) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], 52); /* GMT+13 hour */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + /* this is GMT-11, I can't found a -12 timezone */ + setenv("TZ", "Pacific/Pago_Pago", 1); + tzset(); + + iso_datetime_7(buf, t1, 0); + CU_ASSERT_EQUAL(buf[0], 76); /* year since 1900 */ + CU_ASSERT_EQUAL(buf[1], 3); /* month */ + CU_ASSERT_EQUAL(buf[2], 1); /* day */ + CU_ASSERT_EQUAL(buf[3], 2); /* hour (GMT-11) */ + CU_ASSERT_EQUAL(buf[4], 27); /* minute */ + CU_ASSERT_EQUAL(buf[5], 45); /* second */ + CU_ASSERT_EQUAL((int8_t)buf[6], -44); /* GMT-11 hour */ + + /* check that reading returns the same time */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + + /* --- and now test from several zones, just for write/read compatibilty */ + setenv("TZ", "Pacific/Kiritimati", 1); + tzset(); + iso_datetime_7(buf, t1, 1); /* this needs GMT */ + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "America/Argentina/La_Rioja", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "America/Argentina/La_Rioja", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "America/Caracas", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Asia/Bangkok", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Asia/Tehran", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Pacific/Pitcairn", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Antarctica/McMurdo", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "EET", 1); /* Eastern European Time */ + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Europe/Moscow", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Asia/Novosibirsk", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Asia/Vladivostok", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Asia/Anadyr", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Atlantic/Canary", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "Indian/Mauritius", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + setenv("TZ", "America/Los_Angeles", 1); + tzset(); + iso_datetime_7(buf, t1, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t1); + iso_datetime_7(buf, t2, 0); + tr = iso_datetime_read_7(buf); + CU_ASSERT_EQUAL(tr, t2); + + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); +} + +static void test_iso_1_dirid() +{ + char *dir; + dir = iso_1_dirid("dir1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_1_dirid("dIR1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_1_dirid("DIR1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_1_dirid("dirwithbigname"); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITHB"); + free(dir); + dir = iso_1_dirid("dirwith8"); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITH8"); + free(dir); + dir = iso_1_dirid("dir.1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR_1"); + free(dir); + dir = iso_1_dirid("4f<0KmM::xcvf"); + CU_ASSERT_STRING_EQUAL(dir, "4F_0KMM_"); + free(dir); +} + +static void test_iso_2_dirid() +{ + char *dir; + dir = iso_2_dirid("dir1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_2_dirid("dIR1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_2_dirid("DIR1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_2_dirid("dirwithbigname"); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITHBIGNAME"); + free(dir); + dir = iso_2_dirid("dirwith8"); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITH8"); + free(dir); + dir = iso_2_dirid("dir.1"); + CU_ASSERT_STRING_EQUAL(dir, "DIR_1"); + free(dir); + dir = iso_2_dirid("4f<0KmM::xcvf"); + CU_ASSERT_STRING_EQUAL(dir, "4F_0KMM__XCVF"); + free(dir); + dir = iso_2_dirid("directory with 31 characters ok"); + CU_ASSERT_STRING_EQUAL(dir, "DIRECTORY_WITH_31_CHARACTERS_OK"); + free(dir); + dir = iso_2_dirid("directory with more than 31 characters"); + CU_ASSERT_STRING_EQUAL(dir, "DIRECTORY_WITH_MORE_THAN_31_CHA"); + free(dir); +} + +static void test_iso_1_fileid() +{ + char *file; + file = iso_1_fileid("file1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_1_fileid("fILe1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_1_fileid("FILE1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_1_fileid(".EXT"); + CU_ASSERT_STRING_EQUAL(file, ".EXT"); + free(file); + file = iso_1_fileid("file.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_1_fileid("fiLE.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_1_fileid("file.EXt"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_1_fileid("FILE.EXT"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_1_fileid("bigfilename"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILEN."); + free(file); + file = iso_1_fileid("bigfilename.ext"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILEN.EXT"); + free(file); + file = iso_1_fileid("bigfilename.e"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILEN.E"); + free(file); + file = iso_1_fileid("file.bigext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.BIG"); + free(file); + file = iso_1_fileid(".bigext"); + CU_ASSERT_STRING_EQUAL(file, ".BIG"); + free(file); + file = iso_1_fileid("bigfilename.bigext"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILEN.BIG"); + free(file); + file = iso_1_fileid("file<:a.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.EXT"); + free(file); + file = iso_1_fileid("file.<:a"); + CU_ASSERT_STRING_EQUAL(file, "FILE.__A"); + free(file); + file = iso_1_fileid("file<:a.--a"); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.__A"); + free(file); + file = iso_1_fileid("file.ex1.ex2"); + CU_ASSERT_STRING_EQUAL(file, "FILE_EX1.EX2"); + free(file); + file = iso_1_fileid("file.ex1.ex2.ex3"); + CU_ASSERT_STRING_EQUAL(file, "FILE_EX1.EX3"); + free(file); + file = iso_1_fileid("fil.ex1.ex2.ex3"); + CU_ASSERT_STRING_EQUAL(file, "FIL_EX1_.EX3"); + free(file); +} + +static void test_iso_2_fileid() +{ + char *file; + file = iso_2_fileid("file1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_2_fileid("fILe1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_2_fileid("FILE1"); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_2_fileid(".EXT"); + CU_ASSERT_STRING_EQUAL(file, ".EXT"); + free(file); + file = iso_2_fileid("file.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_2_fileid("fiLE.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_2_fileid("file.EXt"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_2_fileid("FILE.EXT"); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_2_fileid("bigfilename"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILENAME."); + free(file); + file = iso_2_fileid("bigfilename.ext"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILENAME.EXT"); + free(file); + file = iso_2_fileid("bigfilename.e"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILENAME.E"); + free(file); + file = iso_2_fileid("31 characters filename.extensio"); + CU_ASSERT_STRING_EQUAL(file, "31_CHARACTERS_FILENAME.EXTENSIO"); + free(file); + file = iso_2_fileid("32 characters filename.extension"); + CU_ASSERT_STRING_EQUAL(file, "32_CHARACTERS_FILENAME.EXTENSIO"); + free(file); + file = iso_2_fileid("more than 30 characters filename.extension"); + CU_ASSERT_STRING_EQUAL(file, "MORE_THAN_30_CHARACTERS_FIL.EXT"); + free(file); + file = iso_2_fileid("file.bigext"); + CU_ASSERT_STRING_EQUAL(file, "FILE.BIGEXT"); + free(file); + file = iso_2_fileid(".bigext"); + CU_ASSERT_STRING_EQUAL(file, ".BIGEXT"); + free(file); + file = iso_2_fileid("bigfilename.bigext"); + CU_ASSERT_STRING_EQUAL(file, "BIGFILENAME.BIGEXT"); + free(file); + file = iso_2_fileid("file<:a.ext"); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.EXT"); + free(file); + file = iso_2_fileid("file.<:a"); + CU_ASSERT_STRING_EQUAL(file, "FILE.__A"); + free(file); + file = iso_2_fileid("file<:a.--a"); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.__A"); + free(file); + file = iso_2_fileid("file.ex1.ex2"); + CU_ASSERT_STRING_EQUAL(file, "FILE_EX1.EX2"); + free(file); + file = iso_2_fileid("file.ex1.ex2.ex3"); + CU_ASSERT_STRING_EQUAL(file, "FILE_EX1_EX2.EX3"); + free(file); + file = iso_2_fileid("fil.ex1.ex2.ex3"); + CU_ASSERT_STRING_EQUAL(file, "FIL_EX1_EX2.EX3"); + free(file); + file = iso_2_fileid(".file.bigext"); + CU_ASSERT_STRING_EQUAL(file, "_FILE.BIGEXT"); + free(file); +} + +static void test_iso_r_dirid() +{ + char *dir; + + dir = iso_r_dirid("dir1", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + + dir = iso_r_dirid("dIR1", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + + /* allow lowercase */ + dir = iso_r_dirid("dIR1", 31, 1); + CU_ASSERT_STRING_EQUAL(dir, "dIR1"); + free(dir); + dir = iso_r_dirid("dIR1", 31, 2); + CU_ASSERT_STRING_EQUAL(dir, "dIR1"); + free(dir); + + dir = iso_r_dirid("DIR1", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIR1"); + free(dir); + dir = iso_r_dirid("dirwithbigname", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITHBIGNAME"); + free(dir); + dir = iso_r_dirid("dirwith8", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIRWITH8"); + free(dir); + + /* dot is not allowed */ + dir = iso_r_dirid("dir.1", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIR_1"); + free(dir); + dir = iso_r_dirid("dir.1", 31, 1); + CU_ASSERT_STRING_EQUAL(dir, "dir_1"); + free(dir); + dir = iso_r_dirid("dir.1", 31, 2); + CU_ASSERT_STRING_EQUAL(dir, "dir.1"); + free(dir); + + dir = iso_r_dirid("4f<0KmM::xcvf", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "4F_0KMM__XCVF"); + free(dir); + dir = iso_r_dirid("4f<0KmM::xcvf", 31, 1); + CU_ASSERT_STRING_EQUAL(dir, "4f_0KmM__xcvf"); + free(dir); + dir = iso_r_dirid("4f<0KmM::xcvf", 31, 2); + CU_ASSERT_STRING_EQUAL(dir, "4f<0KmM::xcvf"); + free(dir); + + dir = iso_r_dirid("directory with 31 characters ok", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIRECTORY_WITH_31_CHARACTERS_OK"); + free(dir); + dir = iso_r_dirid("directory with more than 31 characters", 31, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIRECTORY_WITH_MORE_THAN_31_CHA"); + free(dir); + dir = iso_r_dirid("directory with more than 31 characters", 35, 0); + CU_ASSERT_STRING_EQUAL(dir, "DIRECTORY_WITH_MORE_THAN_31_CHARACT"); + free(dir); +} + +static void test_iso_r_fileid() +{ + char *file; + + /* force dot */ + file = iso_r_fileid("file1", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + + /* and not */ + file = iso_r_fileid("file1", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE1"); + free(file); + + /* allow lowercase */ + file = iso_r_fileid("file1", 30, 1, 0); + CU_ASSERT_STRING_EQUAL(file, "file1"); + free(file); + file = iso_r_fileid("file1", 30, 2, 0); + CU_ASSERT_STRING_EQUAL(file, "file1"); + free(file); + + /* force d-char and dot */ + file = iso_r_fileid("fILe1", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + /* force d-char but not dot */ + file = iso_r_fileid("fILe1", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE1"); + free(file); + /* allow lower case but force dot */ + file = iso_r_fileid("fILe1", 30, 1, 1); + CU_ASSERT_STRING_EQUAL(file, "fILe1."); + free(file); + + file = iso_r_fileid("FILE1", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE1."); + free(file); + file = iso_r_fileid(".EXT", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, ".EXT"); + free(file); + file = iso_r_fileid(".EXT", 30, 1, 0); + CU_ASSERT_STRING_EQUAL(file, ".EXT"); + free(file); + + file = iso_r_fileid("file.ext", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + + /* not force dot is the same in this case */ + file = iso_r_fileid("fiLE.ext", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_r_fileid("fiLE.ext", 30, 2, 0); + CU_ASSERT_STRING_EQUAL(file, "fiLE.ext"); + free(file); + + file = iso_r_fileid("file.EXt", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + file = iso_r_fileid("FILE.EXT", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "FILE.EXT"); + free(file); + + file = iso_r_fileid("31 characters filename.extensio", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "31_CHARACTERS_FILENAME.EXTENSIO"); + free(file); + file = iso_r_fileid("32 characters filename.extension", 30, 0, 1); + CU_ASSERT_STRING_EQUAL(file, "32_CHARACTERS_FILENAME.EXTENSIO"); + free(file); + + /* allow lowercase */ + file = iso_r_fileid("31 characters filename.extensio", 30, 1, 1); + CU_ASSERT_STRING_EQUAL(file, "31_characters_filename.extensio"); + free(file); + + /* and all characters */ + file = iso_r_fileid("31 characters filename.extensio", 30, 2, 1); + CU_ASSERT_STRING_EQUAL(file, "31 characters filename.extensio"); + free(file); + + file = iso_r_fileid("more than 30 characters filename.extension", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "MORE_THAN_30_CHARACTERS_FIL.EXT"); + free(file); + + /* incrementing the size... */ + file = iso_r_fileid("more than 30 characters filename.extension", 35, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "MORE_THAN_30_CHARACTERS_FILENAME.EXT"); + free(file); + + file = iso_r_fileid("more than 30 characters filename.extension", 36, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "MORE_THAN_30_CHARACTERS_FILENAME.EXTE"); + free(file); + + file = iso_r_fileid("file.bigext", 30, 1, 0); + CU_ASSERT_STRING_EQUAL(file, "file.bigext"); + free(file); + + file = iso_r_fileid(".bigext", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, ".BIGEXT"); + free(file); + + /* "strange" characters */ + file = iso_r_fileid("file<:a.ext", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.EXT"); + free(file); + file = iso_r_fileid("file<:a.ext", 30, 1, 0); + CU_ASSERT_STRING_EQUAL(file, "file__a.ext"); + free(file); + file = iso_r_fileid("file<:a.ext", 30, 2, 0); + CU_ASSERT_STRING_EQUAL(file, "file<:a.ext"); + free(file); + + /* multiple dots */ + file = iso_r_fileid("fi.le.a.ext", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FI_LE_A.EXT"); + free(file); + file = iso_r_fileid("fi.le.a.ext", 30, 1, 0); + CU_ASSERT_STRING_EQUAL(file, "fi_le_a.ext"); + free(file); + file = iso_r_fileid("fi.le.a.ext", 30, 2, 0); + CU_ASSERT_STRING_EQUAL(file, "fi.le.a.ext"); + free(file); + + file = iso_r_fileid("file.<:a", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE.__A"); + free(file); + file = iso_r_fileid("file<:a.--a", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "FILE__A.__A"); + free(file); + + file = iso_r_fileid(".file.bigext", 30, 0, 0); + CU_ASSERT_STRING_EQUAL(file, "_FILE.BIGEXT"); + free(file); + + file = iso_r_fileid(".file.bigext", 30, 2, 0); + CU_ASSERT_STRING_EQUAL(file, ".file.bigext"); + free(file); +} + +static void test_iso_rbtree_insert() +{ + int res; + IsoRBTree *tree; + char *str1, *str2, *str3, *str4, *str5; + void *str; + + res = iso_rbtree_new((compare_function_t)strcmp, &tree); + CU_ASSERT_EQUAL(res, 1); + + /* ok, insert one str */ + str1 = "first str"; + res = iso_rbtree_insert(tree, str1, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + + str2 = "second str"; + res = iso_rbtree_insert(tree, str2, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* an already inserted string */ + str3 = "second str"; + res = iso_rbtree_insert(tree, str3, &str); + CU_ASSERT_EQUAL(res, 0); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* an already inserted string */ + str3 = "first str"; + res = iso_rbtree_insert(tree, str3, &str); + CU_ASSERT_EQUAL(res, 0); + CU_ASSERT_PTR_EQUAL(str, str1); + + str4 = "a string to be inserted first"; + res = iso_rbtree_insert(tree, str4, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str4); + + str5 = "this to be inserted last"; + res = iso_rbtree_insert(tree, str5, &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str5); + + /* + * TODO write a really good test to check all possible estrange + * behaviors of a red-black tree + */ + + iso_rbtree_destroy(tree, NULL); +} + +void test_iso_htable_put_get() +{ + int res; + IsoHTable *table; + char *str1, *str2, *str3, *str4, *str5; + void *str; + + res = iso_htable_create(4, iso_str_hash, (compare_function_t)strcmp, &table); + CU_ASSERT_EQUAL(res, 1); + + /* try to get str from empty table */ + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 0); + + /* ok, insert one str */ + str1 = "first str"; + res = iso_htable_put(table, str1, str1); + CU_ASSERT_EQUAL(res, 1); + + /* and now get str from table */ + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 0); + + str2 = "second str"; + res = iso_htable_put(table, str2, str2); + CU_ASSERT_EQUAL(res, 1); + + str = NULL; + res = iso_htable_get(table, "first str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str1); + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + /* insert again, with same key but other data */ + res = iso_htable_put(table, str2, str1); + CU_ASSERT_EQUAL(res, 0); + + res = iso_htable_get(table, "second str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str2); + + str3 = "third str"; + res = iso_htable_put(table, str3, str3); + CU_ASSERT_EQUAL(res, 1); + + str4 = "four str"; + res = iso_htable_put(table, str4, str4); + CU_ASSERT_EQUAL(res, 1); + + str5 = "fifth str"; + res = iso_htable_put(table, str5, str5); + CU_ASSERT_EQUAL(res, 1); + + /* some searches */ + res = iso_htable_get(table, "sixth str", &str); + CU_ASSERT_EQUAL(res, 0); + + res = iso_htable_get(table, "fifth str", &str); + CU_ASSERT_EQUAL(res, 1); + CU_ASSERT_PTR_EQUAL(str, str5); + + iso_htable_destroy(table, NULL); +} + +void add_util_suite() +{ + CU_pSuite pSuite = CU_add_suite("UtilSuite", NULL, NULL); + + CU_add_test(pSuite, "strconv()", test_strconv); + CU_add_test(pSuite, "int_pow()", test_int_pow); + CU_add_test(pSuite, "DIV_UP()", test_div_up); + CU_add_test(pSuite, "ROUND_UP()", test_round_up); + CU_add_test(pSuite, "iso_bb()", test_iso_bb); + CU_add_test(pSuite, "iso_lsb/msb()", test_iso_lsb_msb); + CU_add_test(pSuite, "iso_read_lsb/msb()", test_iso_read_lsb_msb); + CU_add_test(pSuite, "iso_datetime_7()", test_iso_datetime_7); + CU_add_test(pSuite, "iso_1_dirid()", test_iso_1_dirid); + CU_add_test(pSuite, "iso_2_dirid()", test_iso_2_dirid); + CU_add_test(pSuite, "iso_1_fileid()", test_iso_1_fileid); + CU_add_test(pSuite, "iso_2_fileid()", test_iso_2_fileid); + CU_add_test(pSuite, "iso_r_dirid()", test_iso_r_dirid); + CU_add_test(pSuite, "iso_r_fileid()", test_iso_r_fileid); + CU_add_test(pSuite, "iso_rbtree_insert()", test_iso_rbtree_insert); + CU_add_test(pSuite, "iso_htable_put/get()", test_iso_htable_put_get); +} diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000..f903ff0 --- /dev/null +++ b/version.h.in @@ -0,0 +1,3 @@ +#define LIBISOFS_MAJOR_VERSION @LIBISOFS_MAJOR_VERSION@ +#define LIBISOFS_MINOR_VERSION @LIBISOFS_MINOR_VERSION@ +#define LIBISOFS_MICRO_VERSION @LIBISOFS_MICRO_VERSION@