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 0000000..a89203e Binary files /dev/null and b/doc/devel/UML/BuilderSec.png differ 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 0000000..6884fe9 Binary files /dev/null and b/doc/devel/UML/builder.violet.png differ diff --git a/doc/devel/UML/burn_source.class.violet b/doc/devel/UML/burn_source.class.violet new file mode 100644 index 0000000..f5b3654 --- /dev/null +++ b/doc/devel/UML/burn_source.class.violet @@ -0,0 +1,634 @@ + + + + + + + + + + «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 0000000..5786ab2 Binary files /dev/null and b/doc/devel/UML/burn_source.png differ 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 0000000..adc195c Binary files /dev/null and b/doc/devel/UML/eltorito.violet.png differ 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 0000000..d445b4f Binary files /dev/null and b/doc/devel/UML/iso_tree.violet.png differ 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 0000000..f5215a5 Binary files /dev/null and b/doc/devel/UML/stream.violet.png differ diff --git a/doc/devel/codestyle.xml b/doc/devel/codestyle.xml new file mode 100644 index 0000000..ef96520 --- /dev/null +++ b/doc/devel/codestyle.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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@