././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0744355 pycdlib-1.11.0/0000775000175000017500000000000000000000000016003 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6221106 pycdlib-1.11.0/COPYING0000664000175000017500000006356400000000000017054 0ustar00clalancetteclalancette00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2.1 of the License. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0754356 pycdlib-1.11.0/PKG-INFO0000664000175000017500000000177500000000000017112 0ustar00clalancetteclalancette00000000000000Metadata-Version: 1.1 Name: pycdlib Version: 1.11.0 Summary: Pure python ISO manipulation library Home-page: http://github.com/clalancette/pycdlib Author: Chris Lalancette Author-email: clalancette@gmail.com License: LGPLv2 Description: PyCdlib is a pure python library to parse, write (master), and create ISO9660 files, suitable for writing to a CD or USB. The original ISO9660 (including ISO9660-1999) specification is supported, as well the El Torito, Joliet, Rock Ridge, and UDF extensions. Please see https://clalancette.github.io/pycdlib/ for much more documentation. Keywords: iso9660 iso ecma119 rockridge joliet eltorito udf Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2) Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6221106 pycdlib-1.11.0/README.md0000664000175000017500000000052400000000000017263 0ustar00clalancetteclalancette00000000000000PyCdlib is a pure python library to parse, write (master), and create ISO9660 files, suitable for writing to a CD or USB. The original ISO9660 (including ISO9660-1999) specification is supported, as well the El Torito, Joliet, Rock Ridge, and UDF extensions. Please see https://clalancette.github.io/pycdlib/ for much more documentation. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0714355 pycdlib-1.11.0/examples/0000775000175000017500000000000000000000000017621 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.631111 pycdlib-1.11.0/examples/create-bootable.py0000664000175000017500000000223500000000000023225 0ustar00clalancetteclalancette00000000000000# This is program to show how to use PyCdlib to create a new ISO that is # bootable. # Import standard python modules. import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO # Import pycdlib itself. import pycdlib # Check that there are enough command-line arguments. if len(sys.argv) != 1: print('Usage: %s' % (sys.argv[0])) sys.exit(1) # Create a new PyCdlib object. iso = pycdlib.PyCdlib() # Create a new ISO, accepting all of the defaults. iso.new() # Add a new file to the ISO, with the contents coming from the file object. # This file will be used as the boot file on the bootable ISO. bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') # Once the bootable file is on the ISO, we need to link it to the boot catalog # by calling add_eltorito. iso.add_eltorito('/BOOT.;1', bootcatfile='/BOOT.CAT;1') # Write out the ISO to the file called 'eltorito.iso'. This will fully master # the ISO, making it bootable. iso.write('eltorito.iso') # Close the ISO object. After this call, the PyCdlib object has forgotten # everything about the previous ISO, and can be re-used. iso.close() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.631111 pycdlib-1.11.0/examples/create-new.py0000664000175000017500000000322700000000000022231 0ustar00clalancetteclalancette00000000000000# This is a simple program to show how to use PyCdlib to create a new # ISO, with one file and one directory on it. # Import standard python modules. import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO # Import pycdlib itself. import pycdlib # Check that there are enough command-line arguments. if len(sys.argv) != 1: print('Usage: %s' % (sys.argv[0])) sys.exit(1) # Create a new PyCdlib object. iso = pycdlib.PyCdlib() # Create a new ISO, accepting all of the defaults. iso.new() # Add a new file to the ISO, with the contents coming from the file object. # Note that the file object must remain open for the lifetime of the PyCdlib # object, as the PyCdlib object uses it for internal operations. Also note that # the filename passed here is the filename the data will get assigned on the # final ISO; it must begin with a forward slash, and according to ISO9660 must # have a '.', and a semicolon followed by a number. PyCdlib will raise a # PyCdlibException if any of the rules for an ISO9660 filename are violated. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add a new directory to the ISO. Like the filename above, ISO9660 directory # names must conform to certain standards, and PyCdlib will raise a # PyCdlibException if those standards are not met. iso.add_directory('/DIR1') # Write out the ISO to the file called 'new.iso'. This will fully master the # ISO, creating a file that can be burned onto a CD. iso.write('new.iso') # Close the ISO object. After this call, the PyCdlib object has forgotten # everything about the previous ISO, and can be re-used. iso.close() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.631111 pycdlib-1.11.0/examples/extract-data.py0000664000175000017500000000173400000000000022561 0ustar00clalancetteclalancette00000000000000# This is a simple program to show how to use PyCdlib to extract data from the # ISO. # Import standard python modules. import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO # Import pycdlib itself. import pycdlib # Check that there are enough command-line arguments. if len(sys.argv) != 1: print('Usage: %s' % (sys.argv[0])) sys.exit(1) # First we'll create a new ISO and write it out (see create-new.py for more # information about these steps). iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() iso.write_fp(out) iso.close() # Now, let's open up the ISO and extract the contents of the FOO.;1 file. iso.open_fp(out) extracted = BytesIO() # Use the get_file_from_iso_fp() API to extract the named filename into the file # descriptor. iso.get_file_from_iso_fp(extracted, iso_path='/FOO.;1') iso.close() print(extracted.getvalue().decode('utf-8')) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.631111 pycdlib-1.11.0/examples/open-existing.py0000664000175000017500000000164400000000000022771 0ustar00clalancetteclalancette00000000000000# This is a simple example program to show how to use PyCdlib to open up an # existing ISO passed on the command-line, and print out all of the file names # at the root of the ISO. # Import standard python modules. import sys # Import pycdlib itself. import pycdlib # Check that there are enough command-line arguments. if len(sys.argv) != 2: print('Usage: %s ' % (sys.argv[0])) sys.exit(1) # Create a new PyCdlib object. iso = pycdlib.PyCdlib() # Open up a file object. This causes PyCdlib to parse all of the metadata on the # ISO, which is used for later manipulation. iso.open(sys.argv[1]) # Now iterate through each of the files on the root of the ISO, printing out # their names. for child in iso.list_children(iso_path='/'): print(child.file_identifier()) # Close the ISO object. After this call, the PyCdlib object has forgotten # everything about the previous ISO, and can be re-used. iso.close() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0714355 pycdlib-1.11.0/man/0000775000175000017500000000000000000000000016556 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1583463470.2068536 pycdlib-1.11.0/man/pycdlib-explorer.10000664000175000017500000001036100000000000022125 0ustar00clalancetteclalancette00000000000000.TH PYCDLIB-EXPLORER 1 "Jan 2018" "pycdlib-explorer" .SH NAME pycdlib-explorer - tool to examine and modify ISOs using pycdlib .SH SYNOPSIS .B pycdlib-explorer .SH DESCRIPTION This is a tool to examine and modify existing ISO files on disk. Using this tool, the files, directories, and metadata on an ISO can be examined, new files can be added, and old files can be deleted. Note that due to the nature of the ISO standard, files cannot be modified in place in a general way. To accomplish this, remove the file and then re-add it with new contents. The commands that modify the ISO only modify the in-memory copy. No changes are made to the original file until the \fBwrite\fR command is issued. Also note that pycdlib-explorer has no command-line options; instead, its behavior is controlled entirely at runtime through commands. The following section describes the available commands in pycdlib-explorer. .SH COMMANDS .TP .B "add_file [rr_name=] [joliet_path=]" Add the contents of to the ISO at the location specified in . If the ISO is a Rock Ridge ISO, must be specified; otherwise, it must not be. If the ISO is not a Joliet ISO, must not be specified. If the ISO is a Joliet ISO, is optional, but highly recommended to supply. .TP .B "cd " Change relative directories to \fBiso_dir\fR for exploration. .TP .B "cwd" Show the current working directory. .TP .B "exit" Exit out of pycdlib-explorer. .TP .B "get " Copy the contents of the relative or absolute ISO path \fBiso_file\fR into \fBout_file\fR. .TP .B "help" Print the available commands. Use "help " for a more detailed description of the commands, including the command-line arguments they require. .TP .B "ls" Show the contents of the current working directory. The format of the output is: TYPE(F=file, D=directory) NAME. .TP .B "mkdir [rr_name=] [joliet_path=]" Make a new directory called . If the ISO is a Rock Ridge ISO, must be specified; otherwise, it must not be. If the ISO is not a Joliet ISO, must not be specified. If the ISO is a Joliet ISO, is optional, but highly recommended to supply. .TP .B "print_mode [iso9660|rr|joliet|udf]" Change which 'mode' of filenames are printed out. There are four main modes: ISO9660 (iso9660, the default), Rock Ridge (rr), Joliet (joliet), and UDF (udf). The original iso9660 mode only allows filenames of 8 characters, plus 3 for the extension. The Rock Ridge extensions allow much longer filenames and much deeper directory structures. The Joliet extensions also allow longer filenames and deeper directory structures, but in an entirely different context (though in most circumstances, the Joliet context will mirror the ISO9660/Rock Ridge context). The UDF Bridge extensions add an entirely parallel UDF context to the ISO as well. Any given ISO will always have ISO9660 mode, but may have any combination of Rock Ridge, Joliet, and UDF (including none of them). Running this command with no arguments prints out the current mode. Passing 'iso9660' as an argument sets it to the original ISO9660 mode. Passing 'rr' as an argument sets it to Rock Ridge mode. Passing 'joliet' as an argument sets it to Joliet mode. Passing 'udf' as an argument sets it to UDF mode. .TP .B "quit" Exit out of pycdlib-explorer. .TP .B "rm_file " Remove the file named \fBiso_path\fR from the ISO. Note that this must be a file; to remove a directory, use \fBrmdir\fR. .TP .B "rmdir " Remove the directory named \fBiso_path\fR from the ISO. Note that this must be a directory; to remove a file, use \fBrm_file\fR. .TP .B "tree" List the contents of the ISO in a tree-like format, similar to the bash \fBtree\fR command. .TP .B "write " Write a valid ISO9660 file out to \fBout_file\fR, taking into account any changes made while running the program. This is also sometimes referred to as "mastering" the ISO. Note that the \fBout_file\fR must NOT be the same file as the input file, or the resulting ISO will not work properly. .SH SEE ALSO pycdlib-extract-files(1), pycdlib-genisoimage(1) .SH AUTHOR Chris Lalancette ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1583463470.2068536 pycdlib-1.11.0/man/pycdlib-extract-files.10000664000175000017500000000330600000000000023040 0ustar00clalancetteclalancette00000000000000.TH PYCDLIB-EXTRACT-FILES 1 "Sep 2018" "pycdlib-extract-files" .SH NAME pycdlib-extract-files - tool to extract files from an ISO using pycdlib .SH SYNOPSIS .B pycdlib-extract-files [OPTIONS] .SH DESCRIPTION This is a tool to extract files from an existing ISO file. Using this tool, the files and directories on an ISO can be extracted to the local filesystem. .SH OPTIONS .TP .BI \-path\-type " [auto,iso9660,rockridge,joliet,udf]" Specifies the path name convention to use while extracting the files. If the ISO to be extracted doesn't contain the path type specified, an error is thrown. If not specified, defaults to auto, which first tries to extract files via the UDF path, then the Rock Ridge path, then the Joliet path, and finally falls back to the ISO9660 path if all else fails. Once one path type succeeds, all files will be extracted as that path type, and then pycdlib\-extract\-files will quit. .TP .BI \-start\-path " " The ISO path to start extracting from, specified in a Unix like path (something like "/foo/bar"). If not specified, extraction starts from the root of the ISO (equivalent to specifying "/"). The starting path must be a directory on the ISO. If \-path\-type is not specified, the path is an ISO9660 style path. If \-path\-type is specified, the start path specified here must be of that particular type. .TP .BI \-extract\-to " " The local directory to extract the data to. The "destpath" must exist and must be a directory, or an error will be thrown. If not specified, pycdlib\-extract\-files will extract the files to the current directory. .SH SEE ALSO pycdlib-explorer(1), pycdlib-genisoimage(1) .SH AUTHOR Chris Lalancette ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4871054 pycdlib-1.11.0/man/pycdlib-genisoimage.10000664000175000017500000017163400000000000022567 0ustar00clalancetteclalancette00000000000000.TH PYCDLIB-GENISOIMAGE 1 "Sep 2017" "pycdlib-genisoimage" .SH NAME pycdlib-genisoimage - tool to master ISOs using pycdlib .SH SYNOPSIS .B pycdlib-genisoimage [options] [-o filename] pathspec [pathspec ...] .SH DESCRIPTION .B pycdlib-genisoimage is a pre-mastering program to generate ISO9660/Joliet/HFS hybrid filesystems. It is meant to be 100% flag-compatible with the original .B genisoimage program so that it can be dropped into existing scripts with no changes. Please see the man page for .B genisoimage for more detailed explanation of the options to this program. There are a few differences to note between this program and the original .B genisoimage. First, not all of the options are implemented in this program. This means that .B pycdlib-genisoimage will silently ignore some flags; for the most common usage of this program, this will not matter. However, if you are trying to do something odd and specific, it may not work. The flags that this applies to are noted in the OPTIONS below. In some cases these flags can be implemented with a bit of work, and in some cases the flags can never be implemented due to the design of pycdlib. If in doubt, please ask on https://github.com/clalancette/pycdlib/issues. Second, .B pycdlib-genisoimage does not output all of the same messages to standard out/standard error that .B genisoimage does. Any program that relies on parsing the output of .B genisoimage will probably not work. Third, .B pycdlib-genisoimage will not always generate ISOs that are 100% the same as the .B genisoimage counterparts. This is for a variety of reasons, ranging from bug fixing to simple differences in implementations. In almost all cases this does not matter, but please keep it in mind when using this program instead of .B genisoimage. .SH OPTIONS .TP .BI \-abstract " file" Specifies the abstract filename. There is space for 37 characters. .TP .BI \-A " application_id" .TP .BI \-appid " application_id" Specifies a text string that will be written into the volume header. This should describe the application that will be on the disc. There is space for 128 characters. .TP .B \-allow\-limited\-size (not supported by pycdlib-genisoimage) When processing files larger than 2GiB which cannot be easily represented in ISO9660, add them with a shrunk visible file size to ISO9660 and with the correct visible file size to the UDF system. The result is an inconsistent filesystem and users need to make sure that they really use UDF rather than ISO9660 driver to read a such disk. Implies enabling .BR \-udf. .TP .B \-allow\-leading\-dots .TP .B \-ldots (not supported by pycdlib-genisoimage) Allow ISO9660 filenames to begin with a period. Usually, a leading dot is replaced with an underscore in order to maintain MS-DOS compatibility. .br This violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .B \-allow\-lowercase (not supported by pycdlib-genisoimage) This options allows lowercase characters to appear in ISO9660 filenames. .br This violates the ISO9660 standard, but it happens to work on some systems. Use with caution. .TP .B \-allow\-multidot (not supported by pycdlib-genisoimage) This options allows more than one dot to appear in ISO9660 filenames. A leading dot is not affected by this option, it may be allowed separately using .BR \-allow\-leading\-dots . .br This violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .BI \-biblio " file" Specifies the bibliographic filename. There is space for 37 characters. .TP .B \-cache\-inodes .TP .B \-no\-cache\-inodes (not supported by pycdlib-genisoimage) Enable or disable caching inode and device numbers to find hard links to files. If .B pycdlib-genisoimage finds a hard link (a file with multiple names), the file will also be hard-linked on the CD, so the file contents only appear once. This helps to save space. .B \-cache\-inodes is default on Unix-like operating systems, but .B \-no\-cache\-inodes is default on some other systems such as Cygwin, because it is not safe to assume that inode numbers are unique on those systems. (Some versions of Cygwin create fake inode numbers using a weak hashing algorithm, which may produce duplicates.) If two files have the same inode number but are not hard links to the same file, .B pycdlib-genisoimage \-cache\-inodes will not behave correctly. .B \-no\-cache\-inodes is safe in all situations, but in that case .B pycdlib-genisoimage cannot detect hard links, so the resulting CD image may be larger than necessary. .TP .BI \-alpha\-boot " alpha_boot_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the boot image to be used when making an Alpha/SRM bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . .TP .BI \-hppa\-bootloader " hppa_bootloader_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the boot image to be used when making an HPPA bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . Other options are required, at the very least a kernel filename and a boot command line. .TP .BI \-hppa\-cmdline " hppa_boot_command_line" (not supported by pycdlib-genisoimage) Specifies the command line to be passed to the HPPA boot loader when making a bootable CD. Separate the parameters with spaces or commas. More options must be passed to .B pycdlib-genisoimage, at the very least a kernel filename and the boot loader filename. .TP .BI \-hppa\-kernel\-32 " hppa_kernel_32" .TP .BI \-hppa\-kernel\-64 " hppa_kernel_64" (not supported by pycdlib-genisoimage) Specifies the path and filename of the 32-bit and/or 64-bit kernel images to be used when making an HPPA bootable CD. The pathnames must be relative to the source path specified to .BR pycdlib-genisoimage . Other options are required, at the very least the boot loader filename and the boot command line. .TP .BI \-hppa\-ramdisk " hppa_ramdisk_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the ramdisk image to be used when making an HPPA bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . This parameter is optional. Other options are required, at the very least a kernel filename and the boot command line. .TP .BI \-mips\-boot " mips_boot_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the boot image to be used when making an SGI/big-endian MIPS bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . This option may be specified several times, to store up to 15 boot images. .TP .BI \-mipsel\-boot " mipsel_boot_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the boot image to be used when making an DEC/little-endian MIPS bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . .TP .BI \-B " img_sun4,img_sun4c,img_sun4m,img_sun4d,img_sun4e" .TP .BI \-sparc\-boot " img_sun4,img_sun4c,img_sun4m,img_sun4d,img_sun4e" (not supported by pycdlib-genisoimage) Specifies a comma-separated list of boot images that are needed to make a bootable CD for SPARC systems. Partition 0 is used for the ISO9660 image, the first image file is mapped to partition 1. The comma-separated list may have up to 7 fields, including empty fields. This option is required to make a bootable CD for Sun SPARC systems. If .B \-B or .B \-sparc\-boot has been specified, the first sector of the resulting image will contain a Sun disk label. This disk label specifies slice 0 for the ISO9660 image and slices 1 to 7 for the boot images that have been specified with this option. Byte offsets 512 to 8191 within each of the additional boot images must contain a primary boot that works for the appropriate SPARC architecture. The rest of each of the images usually contains a UFS filesystem used for the primary kernel boot stage. .IP The implemented boot method is the one found with SunOS 4.x and SunOS 5.x. However, it does not depend on SunOS internals but only on properties of the Open Boot prom, so it should be usable for any OS for SPARC systems. For more information also see the .B NOTES section below. .IP If the special filename .B ... is used, the actual and all following boot partitions are mapped to the previous partition. If .B pycdlib-genisoimage is called with .BI \-G " image " \-B " ..." all boot partitions are mapped to the partition that contains the ISO9660 filesystem image and the generic boot image that is located in the first 16 sectors of the disc is used for all architectures. .TP .BI \-G " generic_boot_image" (not supported by pycdlib-genisoimage) Specifies the path and filename of the generic boot image to be used when making a generic bootable CD. The boot image will be placed on the first 16 sectors of the CD, before the ISO9660 primary volume descriptor. If this option is used together with .BR \-sparc\-boot , the Sun disk label will overlay the first 512 bytes of the generic boot image. .TP .BI \-b " eltorito_boot_image" .TP .BI \-eltorito\-boot " eltorito_boot_image" Specifies the path and filename of the boot image to be used when making an El Torito bootable CD for x86 PCs. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . This option is required to make an El Torito bootable CD. The boot image must be exactly 1200 kB, 1440 kB or 2880 kB, and .B pycdlib-genisoimage will use this size when creating the output ISO9660 filesystem. The PC BIOS will use the image to emulate a floppy disk, so the first 512-byte sector should contain PC boot code. This will work, for example, if the boot image is a LILO-based boot floppy. .IP If the boot image is not an image of a floppy, you need to add either .BR \-hard\-disk\-boot " or " \-no\-emul\-boot . If the system should not boot off the emulated disk, use .BR \-no\-boot . .IP If .B \-sort has not been specified, the boot images are sorted with low priority (+2) to the beginning of the medium. If you don't like this, you need to specify a sort weight of 0 for the boot images. .TP .B \-eltorito\-alt\-boot Start with a new set of El Torito boot parameters. Up to 63 El Torito boot entries may be stored on a single CD. .TP .BI \-hard\-disk\-boot Specifies that the boot image used to create El Torito bootable CDs is a hard disk image. The image must begin with a master boot record that contains a single partition. .TP .BI \-eltorito\-platform " id" (not supported by pycdlib-genisoimage) Set the "El Torito" platform id for a boot record or a section of boot records. The .I id parameter may be either: .RS .TP .B x86 This is the default .I platform id value and specifies entries for the PC platform. If no .B \-eltorito\-platform option appears before the first .B \-eltorito\-boot option, the default boot entry becomes an entry for the x86 PC platform. .TP .B PPC Boot entries for the Power PC platform. .TP .B Mac Boot entries for the Apple Mac platform. .TP .B efi Boot entries for EFI based PCs. .TP .B # A numeric value specifying any platform id. .LP If the option .B \-eltorito\-platform appears before the first .B \-eltorito\-boot option, it sets the .I platform id for the default boot entry. .LP If the option .B \-eltorito\-platform appears after an .B \-eltorito\-boot option and sets the .I platform id to a value different from the previous value, it starts a new set of boot entries. .LP The second boot entry and any new .I platform id creates a new section header and reduces the number of boot entries per CD by one. .RE .TP .BI \-ignore\-error (not supported by pycdlib-genisoimage) Ignore errors. .B pycdlib-genisoimage by default aborts on several errors, such as read errors. With this option in effect, .B pycdlib-genisoimage tries to continue. Use with care. .TP .BI \-no\-emul\-boot Specifies that the boot image used to create El Torito bootable CDs is a "no emulation" image. The system will load and execute this image without performing any disk emulation. .TP .BI \-no\-boot Specifies that the created El Torito CD should be marked as not bootable. The system will provide an emulated drive for the image, but will boot off a standard boot device. .TP .BI \-boot\-load\-seg " segment_address" Specifies the load segment address of the boot image for no-emulation El Torito CDs. .TP .BI \-boot\-load\-size " load_sectors" Specifies the number of "virtual" (512-byte) sectors to load in no-emulation mode. The default is to load the entire boot file. Some BIOSes may have problems if this is not a multiple of 4. .TP .B \-boot\-info\-table Specifies that a 56-byte table with information of the CD-ROM layout will be patched in at offset 8 in the boot file. .TP .BI \-C " last_sess_start,next_sess_start" .TP .BI \-cdrecord\-params " last_sess_start,next_sess_start" (not supported by pycdlib-genisoimage) This option is needed to create a CD Extra or the image of a second session or a higher-level session for a multisession disc. .B \-C takes two numbers separated by a comma. The first is the first sector in the last session of the disc that should be appended to. The second number is the starting sector number of the new session. The correct numbers may be retrieved by calling .B wodim \-msinfo ... If .B \-C is used in conjunction with .BR \-M , .B pycdlib-genisoimage will create a filesystem image that is intended to be a continuation of the previous session. If .B \-C is used without .BR \-M , .B pycdlib-genisoimage will create a filesystem image that is intended to be used for a second session on a CD Extra. This is a multisession CD that holds audio data in the first session and an ISO9660 filesystem in the second session. .TP .BI \-c " boot_catalog" .TP .BI \-eltorito\-catalog " boot_catalog" Specifies the path and filename of the boot catalog, which is required for an El Torito bootable CD. The pathname must be relative to the source path specified to .BR pycdlib-genisoimage . This file will be inserted into the output tree and not created in the source filesystem, so be sure the specified filename does not conflict with an existing file, or it will be excluded. Usually a name like .I boot.catalog is chosen. .IP If .B \-sort has not been specified, the boot catalog sorted with low priority (+1) to the beginning of the medium. If you don't like this, you need to specify a sort weight of 0 for the boot catalog. .TP .B \-check\-oldnames (not supported by pycdlib-genisoimage) Check all filenames imported from the old session for compliance with the ISO9660 file naming rules. Without this option, only names longer than 31 characters are checked, as these files are a serious violation of the ISO9660 standard. .TP .BI \-check\-session " file" (not supported by pycdlib-genisoimage) Check all old sessions for compliance with actual .B pycdlib-genisoimage ISO9660 file naming rules. This is a high-level option that combines .B \-M .I file .BR "\-C 0,0 \-check\-oldnames" . For the parameter .IR file , see the description of .BR \-M . .TP .BI \-checksum_algorithm_iso " alg1,alg2,..." (not supported by pycdlib-genisoimage) Specify the checksum types desired for the output image. .TP .BI \-checksum_algorithm_template " alg1,alg2,..." (not supported by pycdlib-genisoimage) Specify the checksum types desired for the output jigdo template. .TP .BI \-copyright " file" Specifies copyright information, typically a filename on the disc. There is space for 37 characters. .TP .B \-d .TP .B \-omit\-period (not supported by pycdlib-genisoimage) Do not append a period to files that do not have one. .br This violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .B \-D .TP .B \-disable\-deep\-relocation (not supported by pycdlib-genisoimage) Do not use deep directory relocation, and instead just pack them in the way we see them. .br If ISO9660:1999 has not been selected, this violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .B \-data\-change\-warn (not supported by pycdlib-genisoimage) If the size of a file changes while the file is being archived, treat this condition as a warning only that does not cause .B pycdlib-genisoimage to abort. .TP .B \-debug (not supported by pycdlib-genisoimage) Set debug flag. .TP .BI \-dir\-mode " mode" (not supported by pycdlib-genisoimage) Overrides the mode of directories used to create the image to .IR mode , specified as 4 digits of permission bits as in .BR chmod (1). This option automatically enables Rock Ridge extensions. .TP .B \-dvd\-video (not supported by pycdlib-genisoimage) Generate a DVD-Video compliant UDF filesystem. This is done by sorting the order of the content of the appropriate files and by adding padding between the files if needed. Note that the sorting only works if the DVD-Video filenames include uppercase characters only. .IP Note that in order to get a DVD-Video compliant filesystem image, you need to prepare a DVD-Video compliant directory tree. This requires a directory .B VIDEO_TS (all caps) in the root directory of the resulting DVD, and usually another directory .BR AUDIO_TS . .B VIDEO_TS needs to include all needed files (filenames must be all caps) for a compliant DVD-Video filesystem. .TP .BI \-e " efi_boot_file" .TP .BI \-efi\-boot " efi_boot_file" Set EFI boot image name. .TP .B \-f .TP .B \-follow\-links (not supported by pycdlib-genisoimage) Follow symbolic links when generating the filesystem. When this option is not in use, symbolic links will be entered using Rock Ridge if enabled, otherwise they will be ignored. .TP .BI \-file\-mode " mode" (not supported by pycdlib-genisoimage) Overrides the mode of regular files used to create the image to .IR mode , specified as 4 digits of permission bits as in .BR chmod (1). This option automatically enables Rock Ridge extensions. .TP .B \-find (not supported by pycdlib-genisoimage) This option acts a separator. If it is used, all .B pycdlib-genisoimage options must be to the left of the .B \-find option. To the right of the .B \-find option, .B pycdlib-genisoimage accepts the .B find command line syntax only. .sp The .B find expression acts as a filter between the source of file names and the consumer, which is archiving engine. If the .B find expression evaluated as TRUE, then the related file is selected for processing, otherwise it is omited. .sp In order to make the evaluation of the .B find expression more convenient, .B pycdlib-genisoimage implements additional .B find primaries that have side effects on the file meta data. .B pycdlib-genisoimage implements the following additional .B find primaries: .RS .TP .B \-help Lists the available .BR find (1) syntax. .TP .BI \-chgrp " gname" The primary always evaluates as true; it sets the group of the file to .IR gname . .TP .BI \-chmod " mode" The primary always evaluates as true; it sets the permissions of the file to .IR mode . Octal and symbolic permissions are accepted for .I mode as with .BR chmod (1). .TP .BI \-chown " uname" The primary always evaluates as true; it sets the owner of the file to .IR uname . .TP .B \-false The primary always evaluates as false; it allows to make the result of the full expression different from the result of a part of the expression. .TP .B \-true The primary always evaluates as true; it allows to make the result of the full expression different from the result of a part of the expression. .PP The command line: .PP .B pycdlib-genisoimage -o o.iso -find . ( -type d -ls -o false ) -o ! -type d .PP lists all directories and puts all non-directories to the image .BR o.iso . .PP The command line: .PP .B pycdlib-genisoimage -o o.iso -find . ( -type d -chown root -o true ) .PP archives all directories so they appear to be owned by root in the archive, all non-directories are archived as they are in the file system. .PP Note that the .BR \-ls , .B \-exec and the .B \-ok primary cannot be used if .B stdin or stdout has not been redirected. .RE .TP .BI \-gid " gid" (not supported by pycdlib-genisoimage) Overrides the group ID read from the source files to the value of .IR gid . Specifying this option automatically enables Rock Ridge extensions. .TP .B \-gui (not supported by pycdlib-genisoimage) Switch the behaviour for a GUI. This currently makes the output more verbose but may have other effects in the future. .TP .B \-graft\-points (not supported by pycdlib-genisoimage) Allow use of graft points for filenames. If this option is used, all filenames are checked for graft points. The filename is divided at the first unescaped equal sign. All occurrences of `\(rs' and `=' characters must be escaped with `\(rs' if .B \-graft\-points has been specified. .TP .BI \-hide " glob" Hide any files matching .IR glob , a shell wildcard pattern, from being seen in the ISO9660 or Rock Ridge directory. .I glob may match any part of the filename or path. If .I glob matches a directory, the contents of that directory will be hidden. In order to match a directory name, make sure the pathname does not include a trailing `/' character. All the hidden files will still be written to the output CD image file. See also .BR \-hide\-joliet , and .IR README.hide . This option may be used multiple times. .TP .BI \-hide\-list " file" A file containing a list of shell wildcards to be hidden. See .BR \-hide . .TP .BI \-hidden " glob" Add the hidden (existence) ISO9660 directory attribute for files and directories matching .IR glob , a shell wildcard pattern. This attribute will prevent the files from being shown by some MS-DOS and Windows commands. .I glob may match any part of the filename or path. In order to match a directory name, make sure the pathname does not include a trailing `/' character. This option may be used multiple times. .TP .BI \-hidden\-list " file" A file containing a list of shell wildcards to get the hidden attribute. See .BR \-hidden . .TP .BI \-hide\-joliet " glob" Hide files and directories matching .IR glob , a shell wildcard pattern, from being seen in the Joliet directory. .I glob may match any part of the filename or path. If .I glob matches a directory, the contents of that directory will be hidden. In order to match a directory name, make sure the pathname does not include a trailing `/' character. All the hidden files will still be written to the output CD image file. This option is usually used with .BR \-hide . See also .IR README.hide . This option may be used multiple times. .TP .BI \-hide\-joliet\-list " file" A file containing a list of shell wildcards to be hidden from the Joliet tree. See .BR \-hide\-joliet . .TP .B \-hide\-joliet\-trans\-tbl (not supported by pycdlib-genisoimage) Hide the .I TRANS.TBL files from the Joliet tree. These files usually don't make sense in the Joliet world as they list the real name and the ISO9660 name which may both be different from the Joliet name. .TP .B \-hide\-rr\-moved Rename the directory .I RR_MOVED to .I .rr_moved in the Rock Ridge tree. It seems to be impossible to completely hide the .I RR_MOVED directory from the Rock Ridge tree. This option only makes the visible tree less confusing for people who don't know what this directory is for. If you need to have no .I RR_MOVED directory at all, you should use .BR \-D . Note that if .B \-D has been specified, the resulting filesystem is not ISO9660 level-1 compliant and will not be readable on MS-DOS. See also the .B NOTES section. .TP .BI \-hide\-udf " glob" Hide .I glob from being seen on the UDF directory. .I glob is a shell wild-card-style pattern that must match any part of the filename or path. Multiple globs may be hidden. If .I glob matches a directory, then the contents of that directory will be hidden. In order to match a directory name, make sure the pathname does not include a trailing '/' character. All the hidden files will still be written to the output CD image file. Should be used with the .B \-hide option. .TP .BI \-hide\-udf\-list " file" A file containing a list of .I globs to be hidden as above. .TP .BI \-input\-charset " charset" (not supported by pycdlib-genisoimage) Input charset that defines the characters used in local filenames. To get a list of valid charset names, call .BR "pycdlib-genisoimage \-input\-charset help" . To get a 1:1 mapping, you may use .B default as charset name. The default initial values are .I cp437 on DOS-based systems and .I iso8859-1 on all other systems. .TP .BI \-output\-charset " charset" (not supported by pycdlib-genisoimage) Output charset that defines the characters that will be used in Rock Ridge filenames. Defaults to the input charset. See .B CHARACTER SETS section below for more details. .TP .BI \-iso\-level " level" Set the ISO9660 conformance level. Valid numbers are 1 to 4. .IP With level 1, files may only consist of one section and filenames are restricted to 8.3 characters. .IP With level 2, files may only consist of one section. .IP With level 3, no restrictions (other than ISO-9660:1988) do apply. .IP With all ISO9660 levels from 1 to 3, all filenames are restricted to uppercase letters, numbers and underscores (_). Filenames are limited to 31 characters, directory nesting is limited to 8 levels, and pathnames are limited to 255 characters. .IP Level 4 officially does not exist but .B pycdlib-genisoimage maps it to ISO-9660:1999, which is ISO9660 version 2. .IP With level 4, an enhanced volume descriptor with version number and file structure version number set to 2 is emitted. Directory nesting is not limited to 8 levels, there is no need for a file to contain a dot and the dot has no special meaning, filenames do not have version numbers, .\" (f XXX ??? The character used for filling byte positions which are .\" specified to be characters is subject to agreement between the .\" originator and the recipient of the volume), and filenames can be up to 207 characters long, or 197 characters if Rock Ridge is used. .IP When creating Version 2 images, .B pycdlib-genisoimage emits an enhanced volume descriptor, similar but not identical to a primary volume descriptor. Be careful not to use broken software to make ISO9660 images bootable by assuming a second PVD copy and patching this putative PVD copy into an El Torito VD. .TP .B \-J Generate Joliet directory records in addition to regular ISO9660 filenames. This is primarily useful when the discs are to be used on Windows machines. Joliet filenames are specified in Unicode and each path component can be up to 64 Unicode characters long. Note that Joliet is not a standard \(em only Microsoft Windows and Linux systems can read Joliet extensions. For greater portability, consider using both Joliet and Rock Ridge extensions. .TP .B \-joliet\-long (not supported by pycdlib-genisoimage) Allow Joliet filenames to be up to 103 Unicode characters, instead of 64. This breaks the Joliet specification, but appears to work. Use with caution. .\" The number 103 is derived from: the maximum Directory Record Length .\" (254), minus the length of Directory Record (33), minus CD-ROM XA .\" System Use Extension Information (14), divided by the UTF-16 .\" character size (2). .TP .BI \-jcharset " charset" (not supported by pycdlib-genisoimage) A combination of .B \-J \-input\-charset .IR charset . .TP .B \-l .TP .B \-full\-iso9660\-filenames (not supported by pycdlib-genisoimage) Allow full 31-character filenames. Normally the ISO9660 filename will be in an 8.3 format which is compatible with MS-DOS, even though the ISO9660 standard allows filenames of up to 31 characters. If you use this option, the disc may be difficult to use on a MS-DOS system, but will work on most other systems. Use with caution. .TP .B \-L Outdated option; use .B \-allow\-leading\-dots instead. .TP .BI \-jigdo\-jigdo " jigdo_file" (not supported by pycdlib-genisoimage) Produce a .B jigdo .I .jigdo metadata file as well as the filesystem image. .TP .BI \-jigdo\-template " template_file" (not supported by pycdlib-genisoimage) Produce a .B jigdo .I .template file as well as the filesystem image. .TP .BI \-jigdo\-min\-file\-size " size" (not supported by pycdlib-genisoimage) Specify the minimum size for a file to be listed in the .I .jigdo file. Default (and minimum allowed) is 1KB. .TP .BI \-jigdo\-force\-md5 " path" (not supported by pycdlib-genisoimage) Specify a file pattern where files .I must be contained in the externally-supplied MD5 list as supplied by .BR \-md5\-list . .TP .BI \-jigdo\-exclude " path" (not supported by pycdlib-genisoimage) Specify a file pattern where files will not be listed in the .I .jigdo file. .TP .BI \-jigdo\-map " path" (not supported by pycdlib-genisoimage) Specify a pattern mapping for the jigdo file (e.g. .IR Debian=/mirror/debian ). .TP .BI \-md5\-list " md5_file" (not supported by pycdlib-genisoimage) Specify a file containing the MD5sums, sizes and pathnames of the files to be included in the .I .jigdo file. .TP .BI \-jigdo\-template\-compress " algorithm" (not supported by pycdlib-genisoimage) Specify a compression algorithm to use for template date. gzip and bzip2 are currently supported, and gzip is the default. .TP .BI \-log\-file " log_file" Redirect all error, warning and informational messages to .I log_file instead of the standard error. .TP .B \-long\-rr\-time (not supported by pycdlib-genisoimage) Use the long ISO-9660 time format for the file time stamps used in Rock Ridge. This time format allows to represent year 0 .. year 9999 with a granularity of 10ms. .sp The short ISO-9660 time format only allows to represent year 1900 .. year 2155 with a granularity of 1s. .TP .BI \-m " glob" Exclude files matching .IR glob , a shell wildcard pattern, from being written to CD-ROM. .I glob may match either the filename component or the full pathname. This option may be used multiple times. For example: .sp pycdlib-genisoimage \-o rom \-m \(aq*.o\(aq \-m core \-m foobar .sp would exclude all files ending in `.o', or called .IR core " or " foobar from the image. Note that if you had a directory called .IR foobar , it too (and of course all its descendants) would be excluded. .TP .BI \-exclude\-list " file" A file containing a list of shell wildcards to be excluded. See .BR \-m . .TP .B \-max\-iso9660\-filenames (not supported by pycdlib-genisoimage) Allow ISO9660 filenames to be up to 37 characters long. This option enables .B \-N as the extra name space is taken from the space reserved for file version numbers. .br This violates the ISO9660 standard, but it happens to work on many systems. Although a conforming application needs to provide a buffer space of at least 37 characters, discs created with this option may cause a buffer overflow in the reading operating system. Use with extreme care. .TP .BI \-M " path" .TP .BI \-M " device" .TP .BI \-dev " device" (not supported by pycdlib-genisoimage) Specifies path to existing ISO9660 image to be merged. The alternate form takes a SCSI device specifier that uses the same syntax as the .B dev= parameter of .BR wodim . The output of .B pycdlib-genisoimage will be a new session which should get written to the end of the image specified in .BR \-M . Typically this requires multisession capability for the CD recorder used to write the image. This option may only be used in conjunction with .BR \-C . .TP .BI \-modification\-date " date-spec" (not supported by pycdlib-genisoimage) Set the .B modification date in the primary volume descriptor (PVD) to a value different from the current time. This allows e.g. to set up an intentional UUID for .BR grub . .sp .ne 3 The format of .I date-spec is: .sp .nf \fIyyyy\fR[\fImm\fR[\fIdd\fR[\fIhh\fR[\fImm\fR[\fIss\fR]\|]\|]\|]\|][.\fIhh\fR][+-\fIghgm\fR] .fi .sp The fields are .BR year , .BR month , .BR "day of month" , .BR hour , .BR minute , .BR second , .BR "hundreds of a second" , .BR "GMT offset in hours and minutes" . The time is interpreted as local time. .sp Year and the GMT offset are four digit fields, all other fields take two digits. The GMT offset may be between -12 and +13 hours in 15 minute steps. Locations east to Greenwich have positive values. The value is the sum of the time zone offset and the effects from daylight saving time. Omited values are replaced by the minimal possible values. If the GMT offset is omited, it is computed from the local time value that has been supplied. .sp Between year and month as well as between month and day of month, a separator chosen from '/' and '-' may appear. In this case, the year may be a two digit number with values 69..99 representing 1969..1999 and values 00..68 representing 2000..2068. Between date and time spec, an optional space is permitted. Between hours and minutes as well as between minutes and seconds, an optional ':' separator is permitted. This allows .B pycdlib-genisoimage to parse the popular POSIX date format created by: .sp .nf \fBdate "+%Y-%m-%d %H:%M:%S %z"\fR .fi .sp Note that the possible range for .I date-spec for 32 bit programs is limited to values up to 2038 Jan 19 04:14:07 GMT. .TP .B \-N .TP .B \-omit\-version\-number (not supported by pycdlib-genisoimage) Omit version numbers from ISO9660 filenames. .br This violates the ISO9660 standard, but no one really uses the version numbers anyway. Use with caution. .TP .BI \-new\-dir\-mode " mode" (not supported by pycdlib-genisoimage) Specify the mode, a 4-digit number as used in .BR chmod (1), to use when creating new directories in the filesystem image. The default is 0555. .TP .B \-nobak .TP .B \-no\-bak Exclude backup files files on the ISO9660 filesystem; that is, filenames that contain the characters `~' or `#' or end in .IR .bak . These are typically backup files for Unix text editors. .TP .B \-no\-limit\-pathtables (not supported by pycdlib-genisoimage) A ISO-9660 filesystem contains path tables that contain a list of directories. This list may contain many directories but only 65535 of them may be parent directories. When .B \-no\-limit\-pathtables is in use, further parent directories will be folded to the root directory and the resulting filesystem will no longer be usable on .BR DOS . .TP .B \-no\-long\-rr\-time (not supported by pycdlib-genisoimage) Use the short ISO-9660 time format for the file time stamps used in Rock Ridge. This time format allows to represent year 1990 .. year 2155 with a granularity of one second. .TP .B \-force\-rr (not supported by pycdlib-genisoimage) Do not use the automatic Rock Ridge attributes recognition for previous sessions. This can work around problems with images created by, e.g., NERO Burning ROM. .TP .B \-no\-rr (not supported by pycdlib-genisoimage) Do not use the Rock Ridge attributes from previous sessions. This may help to avoid problems when .B pycdlib-genisoimage finds illegal Rock Ridge signatures on an old session. .TP .B \-no\-split\-symlink\-components (not supported by pycdlib-genisoimage) Don't split the symlink components, but begin a new Continuation Area (CE) instead. This may waste some space, but the SunOS 4.1.4 cdrom driver has a bug in reading split symlink components. .IP It is questionable whether this option is useful nowadays. .TP .B \-no\-split\-symlink\-fields (not supported by pycdlib-genisoimage) Don't split the symlink fields, but begin a new Continuation Area (CE) instead. This may waste some space, but the SunOS 4.1.4 and Solaris 2.5.1 cdrom driver have a bug in reading split symlink fields (a `/' can be dropped). .IP It is questionable whether this option is useful nowadays. .TP .BI \-o " filename" Specify the output file for the the ISO9660 filesystem image. This can be a disk file, a tape drive, or it can correspond directly to the device name of the optical disc writer. If not specified, stdout is used. Note that the output can also be a block device for a regular disk partition, in which case the ISO9660 filesystem can be mounted normally to verify that it was generated correctly. .TP .B \-pad (not supported by pycdlib-genisoimage) Pad the end of the whole image by 150 sectors (300 kB). This option is enabled by default. If used in combination with .BR \-B , padding is inserted between the ISO9660 partition and the boot partitions, such that the first boot partition starts on a sector number that is a multiple of 16. .IP The padding is needed as many operating systems (e.g. Linux) implement read-ahead bugs in their filesystem I/O. These bugs result in read errors on files that are located near the end of a track, particularly if the disc is written in Track At Once mode, or where a CD audio track follows the data track. .\" XXX: Someone should check to see if the Linux readahead bug is .\" XXX: still present, and update this comment accordingly. .TP .B \-no\-pad (not supported by pycdlib-genisoimage) Do not pad the end by 150 sectors (300 kB) and do not make the the boot partitions start on a multiple of 16 sectors. .TP .BI \-path\-list " file" A file containing a list of .I pathspec directories and filenames to be added to the ISO9660 filesystem. This list of pathspecs are processed after any that appear on the command line. If the argument is .IR \- , the list is read from the standard input. .TP .B \-P Outdated option; use .B \-publisher instead. .TP .BI \-publisher " publisher_id" Specifies a text string that will be written into the volume header. This should describe the publisher of the CD-ROM, usually with a mailing address and phone number. There is space for 128 characters. .TP .BI \-p " preparer_id" .TP .BI \-preparer " preparer_id" Specifies a text string that will be written into the volume header. This should describe the preparer of the CD-ROM, usually with a mailing address and phone number. There is space on the disc for 128 characters of information. The related Joliet entry is limited to 64 characters. .TP .B \-posix\-H (not supported by pycdlib-genisoimage) Follow all symbolic links encountered on command line when generating the filesystem. .TP .B \-posix\-L (not supported by pycdlib-genisoimage) Follow all symbolic links when generating the filesystem. When this option is not in use, symbolic links will be entered using Rock Ridge if enabled, otherwise the file will be ignored. .TP .B \-posix\-P (not supported by pycdlib-genisoimage) Do not follow symbolic links when generating the filesystem (this is the default). If .B \-posix\-P is specified after .B \-posix\-H or .BR \-posix\-L , the effect of these options will be reset. .TP .B \-print\-size Print estimated filesystem size in multiples of the sector size (2048 bytes) and exit. This option is needed for Disk At Once mode and with some CD-R drives when piping directly into .BR wodim , cases where .B wodim needs to know the size of the filesystem image in advance. Old versions of .B mkisofs wrote this information (among other information) to .IR stderr . As this turns out to be hard to parse, the number without any other information is now printed on .I stdout too. If you like to write a simple shell script, redirect .I stderr and catch the number from .IR stdout . This may be done with: .sp cdblocks=\` pycdlib-genisoimage \-print\-size \-quiet .\|.\|. \` .br pycdlib-genisoimage .\|.\|. | wodim .\|.\|. tsize=${cdblocks}s \- .TP .B \-quiet This makes .B pycdlib-genisoimage even less verbose. No progress output will be provided. .TP .B \-R .TP .B \-rock Generate SUSP and RR records using the Rock Ridge protocol to further describe the files on the ISO9660 filesystem. .TP .B \-r .TP .B \-rational\-rock This is like the \-R option, but file ownership and modes are set to more useful values. The uid and gid are set to zero, because they are usually only useful on the author's system, and not useful to the client. All the file read bits are set true, so that files and directories are globally readable on the client. If any execute bit is set for a file, set all of the execute bits, so that executables are globally executable on the client. If any search bit is set for a directory, set all of the search bits, so that directories are globally searchable on the client. All write bits are cleared, because the filesystem will be mounted read-only in any case. If any of the special mode bits are set, clear them, because file locks are not useful on a read-only filesystem, and set-id bits are not desirable for uid 0 or gid 0. When used on Win32, the execute bit is set on .I all files. This is a result of the lack of file permissions on Win32 and the Cygwin POSIX emulation layer. See also .BR \-uid ", " \-gid , .BR \-dir\-mode ", " \-file\-mode and .BR \-new\-dir\-mode . .TP .B \-relaxed\-filenames (not supported by pycdlib-genisoimage) Allows ISO9660 filenames to include all 7-bit ASCII characters except lowercase letters. .br This violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .BI \-root " dir" (not supported by pycdlib-genisoimage) Moves all files and directories into .I dir in the image. This is essentially the same as using .B \-graft\-points and adding .I dir in front of every pathspec, but is easier to use. .I dir may actually be several levels deep. It is created with the same permissions as other graft points. .TP .B \-rrip110 Create ISO-9660 file system images that follow the old Rrip Version-1.10 standard from 1993. This option may be needed if you know of systems that do not implement the Rrip protocol correctly and like the file system to be read by such a system. Currently no such system is known. .sp If a file system has been created with .BR \-rrip110 , the Rock Ridge attributes do not include inode number information. .TP .B \-rrip112 Create ISO-9660 file system images that follow the new Rrip Version-1.12 standard from 1994. .TP .BI \-old-root " dir" (not supported by pycdlib-genisoimage) This option is necessary when writing a multisession image and the previous (or even older) session was written with .B -root .IR dir . Using a directory name not found in the previous session causes .B pycdlib-genisoimage to abort with an error. Without this option, .B pycdlib-genisoimage would not be able to find unmodified files and would be forced to write their data into the image once more. .B \-root and .B \-old-root are meant to be used together to do incremental backups. The initial session would e.g. use: .B pycdlib-genisoimage \-root backup_1 .IR dirs . The next incremental backup with .B pycdlib-genisoimage \-root backup_2 \-old-root backup_1 .I dirs would take another snapshot of these directories. The first snapshot would be found in .BR backup_1 , the second one in .BR backup_2 , but only modified or new files need to be written into the second session. Without these options, new files would be added and old ones would be preserved. But old ones would be overwritten if the file was modified. Recovering the files by copying the whole directory back from CD would also restore files that were deleted intentionally. Accessing several older versions of a file requires support by the operating system to choose which sessions are to be mounted. .TP .BI \-s " sector type" .TP .BI \-sectype " sector type" (not supported by pycdlib-genisoimage) Set output sector type to e.g. data/xa1/raw. .TP .BI \-sort " sort_file" (not supported by pycdlib-genisoimage) Sort file locations on the media. Sorting is controlled by a file that contains pairs of filenames and sorting offset weighting. If the weighting is higher, the file will be located closer to the beginning of the media, if the weighting is lower, the file will be located closer to the end of the media. There must be only one space or tabs character between the filename and the weight and the weight must be the last characters on a line. The filename is taken to include all the characters up to, but not including the last space or tab character on a line. This is to allow for space characters to be in, or at the end of a filename. This option does .B not sort the order of the filenames that appear in the ISO9660 directory. It sorts the order in which the file data is written to the CD image, which is useful in order to optimize the data layout on a CD. See .B README.sort for more details. .TP .BI \-sparc\-boot " img_sun4,img_sun4c,img_sun4m,img_sun4d,img_sun4e" (not supported by pycdlib-genisoimage) See .B \-B above. .TP .BI \-sparc\-label " label" (not supported by pycdlib-genisoimage) Set the Sun disk label name for the Sun disk label that is created with .BR \-sparc-boot . .TP .B \-split\-output (not supported by pycdlib-genisoimage) Split the output image into several files of approximately 1 GB each. This helps to create DVD-sized ISO9660 images on operating systems without large file support. .B wodim will concatenate more than one file into a single track if writing to a DVD. To make .B \-split\-output work, .BI \-o " filename" must be specified. The resulting output images will be named: .IR filename_00 ", " filename_01 ", " filename_02 .... .TP .BI \-stream\-media\-size " #" (not supported by pycdlib-genisoimage) Select streaming operation and set the media size to # sectors. This allows you to pipe the output of the .BR tar (1) program into .B pycdlib-genisoimage and to create an ISO9660 filesystem without the need of an intermediate tar archive file. If this option has been specified, .B pycdlib-genisoimage reads from .I stdin and creates a file with the name .IR STREAM.IMG . The maximum size of the file (with padding) is 200 sectors less than the specified media size. If .B \-no\-pad has been specified, the file size is 50 sectors less than the specified media size. If the file is smaller, .B pycdlib-genisoimage will write padding. This may take awhile. .IP The option .B \-stream\-media\-size creates simple ISO9660 filesystems only and may not used together with multisession or hybrid filesystem options. .TP .BI \-stream\-file\-name " name" Reserved for future use. .TP .BI \-sunx86\-boot " UFS_img,,,AUX1_img" (not supported by pycdlib-genisoimage) Specifies a comma-separated list of filesystem images that are needed to make a bootable CD for Solaris x86 systems. .IP Note that partition 1 is used for the ISO9660 image and that partition 2 is the whole disk, so partition 1 and 2 may not be used by external partition data. The first image file is mapped to partition 0. There may be empty fields in the comma-separated list, and list entries for partition 1 and 2 must be empty. The maximum number of supported partitions is 8 (although the Solaris x86 partition table could support up to 16 partitions), so it is impossible to specify more than 6 partition images. This option is required to make a bootable CD for Solaris x86 systems. .IP If .B \-sunx86\-boot has been specified, the first sector of the resulting image will contain a PC fdisk label with a Solaris type 0x82 fdisk partition that starts at offset 512 and spans the whole CD. In addition, for the Solaris type 0x82 fdisk partition, there is a SVr4 disk label at offset 1024 in the first sector of the CD. This disk label specifies slice 0 for the first (usually UFS type) filesystem image that is used to boot the PC and slice 1 for the ISO9660 image. Slice 2 spans the whole CD slice 3 .\|.\|. slice 7 may be used for additional filesystem images that have been specified with this option. .IP A Solaris x86 boot CD uses a 1024 byte sized primary boot that uses the .B El-Torito no-emulation boot mode and a secondary generic boot that is in CD sectors 1\|.\|.15. For this reason, both .BI "-b " bootimage " \-no\-emul\-boot" and .BI \-G " genboot" must be specified. .TP .BI \-sunx86\-label " label" (not supported by pycdlib-genisoimage) Set the SVr4 disk label name for the SVr4 disk label that is created with .BR \-sunx86-boot . .TP .BI \-sysid " ID" Specifies the system ID. There is space for 32 characters. .TP .B \-T .TP .B \-translation\-table (not supported by pycdlib-genisoimage) Generate a file .I TRANS.TBL in each directory on the CD-ROM, which can be used on non-Rock\ Ridge-capable systems to help establish the correct filenames. There is also information present in the file that indicates the major and minor numbers for block and character devices, and each symlink has the name of the link file given. .TP .BI \-table\-name " table_name" (not supported by pycdlib-genisoimage) Alternative translation table filename (see above). Implies .BR \-T . If you are creating a multisession image you must use the same name as in the previous session. .TP .BI \-ucs\-level " level" Set Unicode conformance level in the Joliet SVD. The default level is 3. It may be set to 1..3 using this option. .TP .B \-UDF Include a .B UDF hybrid in the generated filesystem image. As .B pycdlib-genisoimage always creates a ISO-9660 filesystem, it is not possible to create UDF only images. Note that .B UDF wastes the space from sector ~20 to sector 256 at the beginning of the disk in addition to the space needed for real .B UDF data structures. .TP .B \-udf Rationalized UDF with user and group set to 0 and with simplified permissions. See .B \-r option for more information. .TP .B \-udf\-symlinks Support symlinks in .B UDF filesystems. This is the default. .TP .B \-no\-udf\-symlinks Do not support symlinks in .B UDF filesystems. .TP .BI \-uid " uid" (not supported by pycdlib-genisoimage) Overrides the uid read from the source files to the value of .IR uid . Specifying this option automatically enables Rock Ridge extensions. .TP .B \-use\-fileversion (not supported by pycdlib-genisoimage) The option .B \-use\-fileversion allows .B pycdlib-genisoimage to use file version numbers from the filesystem. If the option is not specified, .B pycdlib-genisoimage creates a version number of 1 for all files. File versions are strings in the range .I ;1 to .I ;32767 This option is the default on VMS. .TP .B \-U .TP .B \-untranslated\-filenames (not supported by pycdlib-genisoimage) Allows "untranslated" filenames, completely violating the ISO9660 standards described above. Enables the following flags: .B \-d \-l \-N \-allow\-leading\-dots \-relaxed\-filenames .BR "\-allow\-lowercase \-allow\-multidot \-no\-iso\-translate" . Allows more than one `.' character in the filename, as well as mixed-case filenames. This is useful on HP-UX, where the built-in .I cdfs filesystem does not recognize any extensions. Use with extreme caution. .TP .B \-no\-iso\-translate (not supported by pycdlib-genisoimage) Do not translate the characters `#' and `~' which are invalid for ISO9660 filenames. Although invalid, these characters are often used by Microsoft systems. .br This violates the ISO9660 standard, but it happens to work on many systems. Use with caution. .TP .BI \-V " volid" Specifies the volume ID (volume name or label) to be written into the master block. There is space for 32 characters. The volume ID is used as the mount point by the Solaris volume manager and as a label assigned to a disc on various other platforms such as Windows and Apple Mac OS. .TP .BI \-volset " ID" Specifies the volume set ID. There is space for 128 characters. .TP .BI \-volset\-size " #" Sets the volume set size to #. The volume set size is the number of CDs that are in a CD volume set. A volume set is a collection of one or more volumes, on which a set of files is recorded. .IP Volume Sets are not intended to be used to create a set numbered CDs that are part of e.g. a Operation System installation set of CDs. Volume Sets are rather used to record a big directory tree that would not fit on a single volume. Each volume of a Volume Set contains a description of all the directories and files that are recorded on the volumes where the sequence numbers are less than, or equal to, the assigned Volume Set Size of the current volume. .IP .B pycdlib-genisoimage currently does not support a .B \-volset\-size that is larger than 1. .IP The option .B \-volset\-size must be specified before .B \-volset\-seqno on each command line. .TP .BI \-volset\-seqno " #" Sets the volume set sequence number to #. The volume set sequence number is the index number of the current CD in a CD set. The option .B \-volset\-size must be specified before .B \-volset\-seqno on each command line. .TP .B \-v .TP .B \-verbose Verbose execution. If given twice on the command line, extra debug information will be printed. .TP .BI \-x " glob" Identical to .B \-m .IR glob . .TP .B \-XA Generate XA directory attruibutes. .TP .B \-xa Generate rationalized XA directory attruibutes. .TP .B \-z .TP .B \-transparent\-compression (not supported by pycdlib-genisoimage) Generate special .I RRIP records for transparently compressed files. This is only of use and interest for hosts that support transparent decompression, such as Linux 2.4.14 or later. You must specify .BR \-R " or " \-r to enable Rock Ridge, and generate compressed files using the .B mkzftree utility before running .BR pycdlib-genisoimage . Note that transparent compression is a nonstandard Rock Ridge extension. The resulting disks are only transparently readable if used on Linux. On other operating systems you will need to call .B mkzftree by hand to decompress the files. .TP .B \-scan\-for\-duplicates Keep a running list of file hashes, attempting to link as many files together as possible. This results in the smallest possible ISO image, but may be very slow, particular with large files. .\" ---------------------------------------- .SH "HFS OPTIONS" .TP .B \-hfs (not supported by pycdlib-genisoimage) Create an ISO9660/HFS hybrid CD. This option should be used in conjunction with the .BR \-map , .B \-magic and/or the various .I double dash options given below. .TP .B \-no\-hfs Do not create an ISO-9660/HFS hybrid CD even though other options may imply to do so. .TP .B \-apple (not supported by pycdlib-genisoimage) Create an ISO9660 CD with Apple's extensions. Similar to .BR \-hfs , except that the Apple Extensions to ISO9660 are added instead of creating an HFS hybrid volume. Former .B pycdlib-genisoimage versions did include Rock Ridge attributes by default if .B \-apple was specified. This versions of .B pycdlib-genisoimage does not do this anymore. If you like to have Rock Ridge attributes, you need to specify this separately. .TP .BI \-map " mapping_file" (not supported by pycdlib-genisoimage) Use the .I mapping_file to set the CREATOR and TYPE information for a file based on the filename's extension. A filename is mapped only if it is not one of the know Apple/Unix file formats. .TP .BI \-magic " magic_file" (not supported by pycdlib-genisoimage) The CREATOR and TYPE information is set by using a file's .I magic number (usually the first few bytes of a file). The .I magic_file is only used if a file is not one of the known Apple/Unix file formats, or the filename extension has not been mapped using .BR \-map . .TP .BI \-hfs\-creator " creator" (not supported by pycdlib-genisoimage) Set the default CREATOR for all files. Must be exactly 4 characters. .TP .BI \-hfs\-type " type" (not supported by pycdlib-genisoimage) Set the default TYPE for all files. Must be exactly 4 characters. .TP .B \-probe (not supported by pycdlib-genisoimage) Search the contents of files for all the known Apple/Unix file formats. However, the only way to check for .I MacBinary and .I AppleSingle files is to open and read them, so this option may increase processing time. It is better to use one or more .I double dash options given below if the Apple/Unix formats in use are known. .TP .B \-no\-desktop (not supported by pycdlib-genisoimage) Do not create (empty) Desktop files. New HFS Desktop files will be created when the CD is used on a Macintosh (and stored in the System Folder). By default, empty Desktop files are added to the HFS volume. .TP .B \-mac\-name (not supported by pycdlib-genisoimage) Use the HFS filename as the starting point for the ISO9660, Joliet and Rock Ridge filenames. .TP .BI \-boot\-hfs\-file " driver_file" (not supported by pycdlib-genisoimage) Installs the .I driver_file that .I may make the CD bootable on a Macintosh. .TP .B \-part (not supported by pycdlib-genisoimage) Generate an HFS partition table. By default, no partition table is generated, but some older Macintosh CD-ROM drivers need an HFS partition table on the CD-ROM to be able to recognize a hybrid CD-ROM. .TP .BI \-auto " AutoStart_file" (not supported by pycdlib-genisoimage) Make the HFS CD use the QuickTime 2.0 Autostart feature to launch an application or document. The given filename must be the name of a document or application located at the top level of the CD. The filename must be less than 12 characters. (Alpha). .TP .BI \-cluster\-size " size" (not supported by pycdlib-genisoimage) Set the size in bytes of the cluster or allocation units of PC Exchange files. Implies .BR \-\-exchange . .TP .BI \-hide\-hfs " glob" (not supported by pycdlib-genisoimage) Hide .IR glob , a shell wildcard pattern, from the HFS volume. The file or directory will still exist in the ISO9660 and/or Joliet directory. .I glob may match any part of the filename. Multiple globs may be excluded. Example: .sp pycdlib-genisoimage \-o rom \-hfs \-hide\-hfs \(aq*.o\(aq \-hide\-hfs foobar .sp would exclude all files ending in `.o' or called .I foobar from the HFS volume. Note that if you had a directory called .IR foobar , it too (and of course all its descendants) would be excluded. The .I glob can also be a path name relative to the source directories given on the command line. Example: .sp pycdlib-genisoimage \-o rom \-hfs \-hide\-hfs src/html src .sp would exclude just the file or directory called .I html from the .I src directory. Any other file or directory called .I html in the tree will not be excluded. Should be used with .B \-hide and/or .BR \-hide\-joliet . In order to match a directory name, make sure the pattern does not include a trailing `/' character. See .I README.hide for more details. .TP .BI \-hide\-hfs\-list " file" (not supported by pycdlib-genisoimage) Specify a file containing a list of wildcard patterns to be hidden as in .BR \-hide\-hfs . .TP .BI \-hfs\-volid " hfs_volid" (not supported by pycdlib-genisoimage) Volume name for the HFS partition. This is the name that is assigned to the disc on a Macintosh and replaces the .I volid used with .BR \-V . .TP .B \-icon\-position (not supported by pycdlib-genisoimage) Use the icon position information, if it exists, from the Apple/Unix file. The icons will appear in the same position as they would on a Macintosh desktop. Folder location and size on screen, its scroll positions, folder View (view as Icons, Small Icons, etc.) are also preserved. .\" This option may become set by default in the future. (Alpha). .TP .BI \-root\-info " file" (not supported by pycdlib-genisoimage) Set the location, size on screen, scroll positions, folder View etc. for the root folder of an HFS volume. See .I README.rootinfo for more information. (Alpha) .TP .BI \-prep\-boot " file" (not supported by pycdlib-genisoimage) PReP boot image file. Up to 4 are allowed. See .I README.prep_boot for more information. (Alpha) .TP .BI \-chrp\-boot (not supported by pycdlib-genisoimage) Add CHRP boot header. .TP .BI \-input\-hfs\-charset " charset" (not supported by pycdlib-genisoimage) Input charset that defines the characters used in HFS filenames when used with .BR \-mac\-name . The default charset is .I cp10000 (Mac Roman). .TP .BI \-output\-hfs\-charset " charset" (not supported by pycdlib-genisoimage) Output charset that defines the characters that will be used in the HFS filenames. Defaults to the input charset. .TP .B \-hfs\-unlock (not supported by pycdlib-genisoimage) By default, .B pycdlib-genisoimage will create an HFS volume that is locked. This option leaves the volume unlocked so that other applications (e.g. .BR hfsutils ) can modify the volume. .TP .BI \-hfs\-bless " folder_name" (not supported by pycdlib-genisoimage) "Bless" the given directory (folder). This is usually the .I System Folder and is used in creating HFS bootable CDs. The name of the directory must be the whole path name as .B pycdlib-genisoimage sees it. E.g., if the given pathspec is .I ./cddata and the required folder is called .IR "System Folder" , the whole path name is .I \(dq/cddata/System Folder\(dq (remember to use quotes if the name contains spaces). .TP .BI \-hfs\-parms " parameters" (not supported by pycdlib-genisoimage) Override certain parameters used to create the HFS filesystem. Unlikely to be used in normal circumstances. .TP .B \-\-cap (not supported by pycdlib-genisoimage) Look for AUFS CAP Macintosh files. Search for CAP Apple/Unix file formats only. Searching for the other possible Apple/Unix file formats is disabled, unless other .I double dash options are given. .TP .B \-\-netatalk (not supported by pycdlib-genisoimage) Look for NETATALK Macintosh files .TP .B \-\-double (not supported by pycdlib-genisoimage) Look for AppleDouble Macintosh files .TP .B \-\-ethershare (not supported by pycdlib-genisoimage) Look for Helios EtherShare Macintosh files .TP .B \-\-ushare (not supported by pycdlib-genisoimage) Look for IPT UShare Macintosh files .TP .B \-\-exchange (not supported by pycdlib-genisoimage) Look for PC Exchange Macintosh files .TP .B \-\-sgi (not supported by pycdlib-genisoimage) Look for SGI Macintosh files .TP .B \-\-xinet (not supported by pycdlib-genisoimage) Look for XINET Macintosh files .TP .B \-\-macbin (not supported by pycdlib-genisoimage) Look for MacBinary Macintosh files .TP .B \-\-single (not supported by pycdlib-genisoimage) Look for AppleSingle Macintosh files .TP .B \-\-dave (not supported by pycdlib-genisoimage) Look for Thursby Software Systems DAVE Macintosh files .TP .B \-\-sfm (not supported by pycdlib-genisoimage) Look for Microsoft's Services for Macintosh files (NT only) (Alpha) .TP .B \-\-osx\-double (not supported by pycdlib-genisoimage) Look for Mac OS X AppleDouble Macintosh files .TP .B \-\-osx\-hfs (not supported by pycdlib-genisoimage) Look for Mac OS X HFS Macintosh files .SH SEE ALSO genisoimage(1), pycdlib-explorer(1), pycdlib-extract-files(1) .SH AUTHOR Chris Lalancette ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0724356 pycdlib-1.11.0/pycdlib/0000775000175000017500000000000000000000000017431 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.633111 pycdlib-1.11.0/pycdlib/__init__.py0000664000175000017500000000027000000000000021541 0ustar00clalancetteclalancette00000000000000''' Pycdlib is a pure python library to parse, write (master), and create ISO9660 files. These files are suitable for writing to a CD or USB. ''' from .pycdlib import PyCdlib # NOQA ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.633111 pycdlib-1.11.0/pycdlib/backport_functools.py0000664000175000017500000002145400000000000023712 0ustar00clalancetteclalancette00000000000000# Vendored copy of https://github.com/jaraco/backports.functools_lru_cache/blob/master/backports/functools_lru_cache.py # MIT License: # Copyright Jason R. Coombs # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """backport_functools.py - Tools for working with functions and callable objects backported from python3 to python2. """ from __future__ import absolute_import import functools from collections import namedtuple from threading import RLock _CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) @functools.wraps(functools.update_wrapper) def update_wrapper(wrapper, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): """ Patch two bugs in functools.update_wrapper. """ # workaround for http://bugs.python.org/issue3445 assigned = tuple(attr for attr in assigned if hasattr(wrapped, attr)) wrapper = functools.update_wrapper(wrapper, wrapped, assigned, updated) # workaround for https://bugs.python.org/issue17482 wrapper.__wrapped__ = wrapped return wrapper class _HashedSeq(list): """ This class guarantees that hash() will be called no more than once per element. This is important because the lru_cache() will hash the key multiple times on a cache miss. """ __slots__ = ('hashvalue',) def __init__(self, tup): list.__init__(self) self[:] = tup self.hashvalue = hash(tup) def __hash__(self): return self.hashvalue def _make_key(args, kwds, typed): 'Make a cache key from optionally typed positional and keyword arguments' key = args if kwds: sorted_items = sorted(kwds.items()) key += (object(),) for item in sorted_items: key += item if typed: key += tuple(type(v) for v in args) if kwds: key += tuple(type(v) for k, v in sorted_items) elif len(key) == 1 and isinstance(key[0], (frozenset, int, str, type(None))): return key[0] return _HashedSeq(key) def lru_cache(maxsize=100, typed=False): """Least-recently-used cache decorator. If *maxsize* is set to None, the LRU features are disabled and the cache can grow without bound. If *typed* is True, arguments of different types will be cached separately. For example, f(3.0) and f(3) will be treated as distinct calls with distinct results. Arguments to the cached function must be hashable. View the cache statistics named tuple (hits, misses, maxsize, currsize) with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used """ # Users should only access the lru_cache through its public API: # cache_info, cache_clear, and f.__wrapped__ # The internals of the lru_cache are encapsulated for thread safety and # to allow the implementation to change (including a possible C version). def decorating_function(user_function): """Decorator implementation.""" cache = dict() stats = [0, 0] # make statistics updateable non-locally HITS, MISSES = 0, 1 # names for the stats fields make_key = _make_key cache_get = cache.get # bound method to lookup key or return None _len = len # localize the global len() function lock = RLock() # because linkedlist updates aren't threadsafe root = [] # root of the circular doubly linked list root[:] = [root, root, None, None] # initialize by pointing to self nonlocal_root = [root] # make updateable non-locally PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields if maxsize == 0: def wrapper(*args, **kwds): """Wrapper to only call user_function and update statistics.""" # no caching, just do a statistics update after a successful call result = user_function(*args, **kwds) stats[MISSES] += 1 return result elif maxsize is None: def wrapper(*args, **kwds): """Wrapper to cache without ordering or size limit.""" # simple caching without ordering or size limit key = make_key(args, kwds, typed) result = cache_get(key, root) # root used here as a unique not-found sentinel if result is not root: stats[HITS] += 1 return result result = user_function(*args, **kwds) cache[key] = result stats[MISSES] += 1 return result else: def wrapper(*args, **kwds): """Wrapper to cache with LRU algorithm.""" # size limited caching that tracks accesses by recency key = make_key(args, kwds, typed) if kwds or typed else args with lock: link = cache_get(key) if link is not None: # record recent use of the key by moving it to the front of the list root, = nonlocal_root link_prev, link_next, key, result = link link_prev[NEXT] = link_next link_next[PREV] = link_prev last = root[PREV] last[NEXT] = root[PREV] = link link[PREV] = last link[NEXT] = root stats[HITS] += 1 return result result = user_function(*args, **kwds) with lock: root, = nonlocal_root if key in cache: # getting here means that this same key was added to the # cache while the lock was released. since the link # update is already done, we need only return the # computed result and update the count of misses. pass elif _len(cache) >= maxsize: # use the old root to store the new key and result oldroot = root oldroot[KEY] = key oldroot[RESULT] = result # empty the oldest link and make it the new root root = nonlocal_root[0] = oldroot[NEXT] oldkey = root[KEY] root[KEY] = root[RESULT] = None # now update the cache dictionary for the new links del cache[oldkey] cache[key] = oldroot else: # put result in a new link at the front of the list last = root[PREV] link = [last, root, key, result] last[NEXT] = root[PREV] = cache[key] = link stats[MISSES] += 1 return result def cache_info(): """Report cache statistics""" with lock: return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) def cache_clear(): """Clear the cache and cache statistics""" with lock: cache.clear() root = nonlocal_root[0] root[:] = [root, root, None, None] stats[:] = [0, 0] wrapper.__wrapped__ = user_function wrapper.cache_info = cache_info wrapper.cache_clear = cache_clear return update_wrapper(wrapper, user_function) return decorating_function ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5589945 pycdlib-1.11.0/pycdlib/dates.py0000664000175000017500000002356300000000000021114 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Classes and utilities for ISO date support. ''' from __future__ import absolute_import import struct import time try: from functools import lru_cache except ImportError: from pycdlib.backport_functools import lru_cache # type: ignore from pycdlib import pycdlibexception from pycdlib import utils @lru_cache(maxsize=256) def string_to_timestruct(input_string): # type: (bytes) -> time.struct_time ''' A cacheable function to take an input string and decode it into a time.struct_time from the time module. If the string cannot be decoded because of an illegal value, then the all-zero time.struct_time will be returned instead. Parameters: input_string - The string to attempt to parse. Returns: A time.struct_time object representing the time. ''' try: timestruct = time.strptime(input_string.decode('utf-8'), VolumeDescriptorDate.TIME_FMT) except ValueError: # Ecma-119, 8.4.26.1 specifies that if the string was all the digit # zero, with the last byte 0, the time wasn't specified. In that # case, time.strptime() with our format will raise a ValueError. # In practice we have found that some ISOs specify various wacky # things in this field, so if we see *any* ValueError, we just # assume the date is unspecified and go with that. timestruct = time.struct_time((0, 0, 0, 0, 0, 0, 0, 0, 0)) return timestruct class DirectoryRecordDate(object): ''' A class to represent a Directory Record date as described in Ecma-119 section 9.1.5. The Directory Record date consists of the number of years since 1900, the month, the day of the month, the hour, the minute, the second, and the offset from GMT in 15 minute intervals. There are two main ways to use this class: either to instantiate and then parse a string to fill in the fields (the parse() method), or to create a new entry with a tm structure (the new() method). ''' FMT = '=BBBBBBb' __slots__ = ('_initialized', 'years_since_1900', 'month', 'day_of_month', 'hour', 'minute', 'second', 'gmtoffset') def __init__(self): # type: () -> None self._initialized = False def parse(self, datestr): # type: (bytes) -> None ''' Parse a Directory Record date out of a string. Parameters: datestr - The string to parse the date out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record Date already initialized') (self.years_since_1900, self.month, self.day_of_month, self.hour, self.minute, self.second, self.gmtoffset) = struct.unpack_from(self.FMT, datestr, 0) self._initialized = True def new(self): # type: () -> None ''' Create a new Directory Record date based on the current time. Parameters: tm - An optional argument that must be None Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record Date already initialized') # This algorithm was ported from cdrkit, genisoimage.c:iso9660_date() tm = time.time() local = time.localtime(tm) self.years_since_1900 = local.tm_year - 1900 self.month = local.tm_mon self.day_of_month = local.tm_mday self.hour = local.tm_hour self.minute = local.tm_min self.second = local.tm_sec self.gmtoffset = utils.gmtoffset_from_tm(tm, local) self._initialized = True def record(self): # type: () -> bytes ''' Return a string representation of the Directory Record date. Parameters: None. Returns: A string representing this Directory Record Date. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record Date not initialized') return struct.pack(self.FMT, self.years_since_1900, self.month, self.day_of_month, self.hour, self.minute, self.second, self.gmtoffset) def __ne__(self, other): return self.years_since_1900 != other.years_since_1900 or self.month != other.month or self.day_of_month != other.day_of_month or self.hour != other.hour or self.minute != other.minute or self.second != other.second or self.gmtoffset != other.gmtoffset class VolumeDescriptorDate(object): ''' A class to represent a Volume Descriptor Date as described in Ecma-119 section 8.4.26.1. The Volume Descriptor Date consists of a year (from 1 to 9999), month (from 1 to 12), day of month (from 1 to 31), hour (from 0 to 23), minute (from 0 to 59), second (from 0 to 59), hundredths of second, and offset from GMT in 15-minute intervals (from -48 to +52) fields. There are two main ways to use this class: either to instantiate and then parse a string to fill in the fields (the parse() method), or to create a new entry with a tm structure (the new() method). ''' TIME_FMT = '%Y%m%d%H%M%S' EMPTY_STRING = b'0' * 16 + b'\x00' __slots__ = ('_initialized', 'year', 'month', 'dayofmonth', 'hour', 'minute', 'second', 'hundredthsofsecond', 'gmtoffset', 'date_str') def __init__(self): # type: () -> None self._initialized = False def parse(self, datestr): # type: (bytes) -> None ''' Parse a Volume Descriptor Date out of a string. A string of all zeros is valid, which means that the date in this field was not specified. Parameters: datestr - string to be parsed Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date object is already initialized') if len(datestr) != 17: raise pycdlibexception.PyCdlibInvalidISO('Invalid ISO9660 date string') timestruct = string_to_timestruct(datestr[:-3]) self.year = timestruct.tm_year self.month = timestruct.tm_mon self.dayofmonth = timestruct.tm_mday self.hour = timestruct.tm_hour self.minute = timestruct.tm_min self.second = timestruct.tm_sec if timestruct.tm_year == 0 and timestruct.tm_mon == 0 and timestruct.tm_mday == 0 and timestruct.tm_hour == 0 and timestruct.tm_min == 0 and timestruct.tm_sec == 0: self.hundredthsofsecond = 0 self.gmtoffset = 0 self.date_str = self.EMPTY_STRING else: self.hundredthsofsecond = int(datestr[14:16]) self.gmtoffset, = struct.unpack_from('=b', datestr, 16) self.date_str = datestr self._initialized = True def record(self): # type: () -> bytes ''' Return the date string for this object. Parameters: None. Returns: Date as a string. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date is not initialized') return self.date_str def new(self, tm=0.0): # type: (float) -> None ''' Create a new Volume Descriptor Date. If tm is None, then this Volume Descriptor Date will be full of zeros (meaning not specified). If tm is not None, it is expected to be a struct_time object, at which point this Volume Descriptor Date object will be filled in with data from that struct_time. Parameters: tm - struct_time object to base new VolumeDescriptorDate off of, or 0.0 for an empty VolumeDescriptorDate. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date object is already initialized') if tm != 0.0: local = time.localtime(tm) self.year = local.tm_year self.month = local.tm_mon self.dayofmonth = local.tm_mday self.hour = local.tm_hour self.minute = local.tm_min self.second = local.tm_sec self.hundredthsofsecond = 0 self.gmtoffset = utils.gmtoffset_from_tm(tm, local) self.date_str = time.strftime(self.TIME_FMT, local).encode('utf-8') + '{:0<2}'.format(self.hundredthsofsecond).encode('utf-8') + struct.pack('=b', self.gmtoffset) else: self.year = 0 self.month = 0 self.dayofmonth = 0 self.hour = 0 self.minute = 0 self.second = 0 self.hundredthsofsecond = 0 self.gmtoffset = 0 self.date_str = self.EMPTY_STRING self._initialized = True def __ne__(self, other): return self.year != other.year or self.month != other.month or self.dayofmonth != other.dayofmonth or self.hour != other.hour or self.minute != other.minute or self.second != other.second or self.hundredthsofsecond != other.hundredthsofsecond or self.gmtoffset != other.gmtoffset or self.date_str != other.date_str ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602030619.3260207 pycdlib-1.11.0/pycdlib/dr.py0000664000175000017500000014742100000000000020421 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' The class to support ISO9660 Directory Records. ''' from __future__ import absolute_import import bisect import struct from pycdlib import dates from pycdlib import inode from pycdlib import pycdlibexception from pycdlib import rockridge from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import BinaryIO, List, Optional, Tuple, Union # NOQA pylint: disable=unused-import # NOTE: these imports have to be here to avoid circular deps from pycdlib import headervd # NOQA pylint: disable=unused-import from pycdlib import path_table_record # NOQA pylint: disable=unused-import class XARecord(object): ''' A class that represents an ISO9660 Extended Attribute record as defined in the Philips Yellow Book standard. ''' __slots__ = ('_initialized', '_group_id', '_user_id', '_attributes', '_filenum', '_pad_size') FMT = '=HHH2sB5s' def __init__(self): # type: () -> None self._pad_size = 0 self._initialized = False def parse(self, xastr, len_fi): # type: (bytes, int) -> bool ''' Parse an Extended Attribute Record out of a string. Parameters: xastr - The string to parse. len_fi - The length of the file identifier for this record. Returns: True if the data contains an XA record, False otherwise. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This XARecord is already initialized') even_size = len_fi + (len_fi % 2) # In a "typical" XA record, the record immediately follows the DR # record (but comes before the Rock Ridge record, if this is a Rock # Ridge ISO). However, we have seen ISOs (Windows 98 SE) that put some # padding between the end of the DR record and the XA record. As far # as I can tell, that padding is the size of the file identifier, # but rounded up to the nearest even number. We check both places for # the XA record. for offset in (0, even_size): parse_str = xastr[offset:] if len(parse_str) < struct.calcsize(self.FMT): return False (self._group_id, self._user_id, self._attributes, signature, self._filenum, unused) = struct.unpack_from(self.FMT, parse_str, 0) if signature != b'XA': continue if unused != b'\x00\x00\x00\x00\x00': raise pycdlibexception.PyCdlibInvalidISO('Unused fields should be 0') self._pad_size = offset break else: return False self._initialized = True return True def new(self): # type: () -> None ''' Create a new Extended Attribute Record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This XARecord is already initialized') # FIXME: we should allow the user to set these self._group_id = 0 self._user_id = 0 self._attributes = 0 self._filenum = 0 self._initialized = True def record(self): # type: () -> bytes ''' Record this Extended Attribute Record. Parameters: None. Returns: A string representing this Extended Attribute Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This XARecord is not initialized') return b'\x00' * self._pad_size + struct.pack(self.FMT, self._group_id, self._user_id, self._attributes, b'XA', self._filenum, b'\x00' * 5) @staticmethod def length(): # type: () -> int ''' A static method to return the size of an Extended Attribute Record. Parameters: None. Returns: The size of an Extended Attribute Record. ''' return 14 class DirectoryRecord(object): ''' A class that represents an ISO9660 directory record. ''' __slots__ = ('initialized', 'new_extent_loc', 'ptr', 'extents_to_here', 'offset_to_here', 'data_continuation', 'vd', 'children', 'rr_children', 'inode', '_printable_name', 'date', 'index_in_parent', 'dr_len', 'xattr_len', 'file_flags', 'file_unit_size', 'interleave_gap_size', 'len_fi', 'isdir', 'orig_extent_loc', 'data_length', 'seqnum', 'is_root', 'parent', 'rock_ridge', 'xa_record', 'file_ident') FILE_FLAG_EXISTENCE_BIT = 0 FILE_FLAG_DIRECTORY_BIT = 1 FILE_FLAG_ASSOCIATED_FILE_BIT = 2 FILE_FLAG_RECORD_BIT = 3 FILE_FLAG_PROTECTION_BIT = 4 FILE_FLAG_MULTI_EXTENT_BIT = 7 FMT = ' None self.initialized = False self.new_extent_loc = -1 self.ptr = None # type: Optional[path_table_record.PathTableRecord] self.extents_to_here = 1 self.offset_to_here = 0 self.data_continuation = None # type: Optional[DirectoryRecord] self.children = [] # type: List[DirectoryRecord] self.rr_children = [] # type: List[DirectoryRecord] self.index_in_parent = -1 self.is_root = False self.isdir = False self.rock_ridge = None # type: Optional[rockridge.RockRidge] self.xa_record = None # type: Optional[XARecord] self.inode = None # type: Optional[inode.Inode] def parse(self, vd, record, parent): # type: (headervd.PrimaryOrSupplementaryVD, bytes, Optional[DirectoryRecord]) -> str ''' Parse a directory record out of a string. Parameters: vd - The Volume Descriptor this record is part of. record - The string to parse for this record. parent - The parent of this record. Returns: The Rock Ridge version as a string if this Directory Record has Rock Ridge, '' otherwise. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') if len(record) > 255: # Since the length is supposed to be 8 bits, this should never # happen. raise pycdlibexception.PyCdlibInvalidISO('Directory record longer than 255 bytes!') # According to http://www.dubeyko.com/development/FileSystems/ISO9960/ISO9960.html, # the xattr_len is the number of bytes at the *beginning* of the file # extent. Since this is only a byte, it is necessarily limited to 255 # bytes. (self.dr_len, self.xattr_len, extent_location_le, extent_location_be, data_length_le, data_length_be_unused, dr_date, self.file_flags, self.file_unit_size, self.interleave_gap_size, seqnum_le, seqnum_be, self.len_fi) = struct.unpack_from(self.FMT, record[:33], 0) # In theory we should have a check here that checks to make sure that # the length of the record we were passed in matches the data record # length. However, we have seen ISOs in the wild where this is # incorrect, so we elide the check here. if extent_location_le != utils.swab_32bit(extent_location_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian (%d) and big-endian (%d) extent location disagree' % (extent_location_le, utils.swab_32bit(extent_location_be))) self.orig_extent_loc = extent_location_le # Theoretically, we should check to make sure that the little endian # data length is the same as the big endian data length. In practice, # though, we've seen ISOs where this is wrong. Skip the check, and just # pick the little-endian as the 'actual' size, and hope for the best. self.data_length = data_length_le if seqnum_le != utils.swab_16bit(seqnum_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian seqnum disagree') self.seqnum = seqnum_le self.date = dates.DirectoryRecordDate() self.date.parse(dr_date) # OK, we've unpacked what we can from the beginning of the string. Now # we have to use the len_fi to get the rest. self.parent = parent self.vd = vd if self.parent is None: self.is_root = True # A root directory entry should always be exactly 34 bytes. # However, we have seen ISOs in the wild that get this wrong, so we # elide a check for it. self.file_ident = bytes(bytearray([record[33]])) # A root directory entry should always have 0 as the identifier. # However, we have seen ISOs in the wild that don't have this set # properly to 0. In that case, we override what we parsed out from # the original with the correct value (\x00), and hope for the best. if self.file_ident != b'\x00': self.file_ident = b'\x00' self.isdir = True else: record_offset = 33 self.file_ident = record[record_offset:record_offset + self.len_fi] record_offset += self.len_fi if self.file_flags & (1 << self.FILE_FLAG_DIRECTORY_BIT): self.isdir = True if self.len_fi % 2 == 0: record_offset += 1 xa_rec = XARecord() if xa_rec.parse(record[record_offset:], self.len_fi): self.xa_record = xa_rec record_offset += len(self.xa_record.record()) if len(record[record_offset:]) >= 2 and record[record_offset:record_offset + 2] in (b'SP', b'RR', b'CE', b'PX', b'ER', b'ES', b'PN', b'SL', b'NM', b'CL', b'PL', b'TF', b'SF', b'RE', b'AL'): self.rock_ridge = rockridge.RockRidge() is_first_dir_record_of_root = False if self.parent.is_root: if self.file_ident == b'\x00': is_first_dir_record_of_root = True bytes_to_skip = 0 else: if not self.parent.children: raise pycdlibexception.PyCdlibInvalidISO('Parent has no dot child') if self.parent.children[0].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Dot child does not have Rock Ridge; ISO is corrupt') bytes_to_skip = self.parent.children[0].rock_ridge.bytes_to_skip else: if self.parent.rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Parent does not have Rock Ridge; ISO is corrupt') bytes_to_skip = self.parent.rock_ridge.bytes_to_skip self.rock_ridge.parse(record[record_offset:], is_first_dir_record_of_root, bytes_to_skip, False) if self.xattr_len != 0: if self.file_flags & (1 << self.FILE_FLAG_RECORD_BIT): raise pycdlibexception.PyCdlibInvalidISO('Record Bit not allowed with Extended Attributes') if self.file_flags & (1 << self.FILE_FLAG_PROTECTION_BIT): raise pycdlibexception.PyCdlibInvalidISO('Protection Bit not allowed with Extended Attributes') if self.rock_ridge is None: ret = '' else: ret = self.rock_ridge.rr_version if self.is_root: self._printable_name = b'/' elif self.file_ident == b'\x00': self._printable_name = b'.' elif self.file_ident == b'\x01': self._printable_name = b'..' else: self._printable_name = self.file_ident self.initialized = True return ret def _rr_new(self, rr_version, rr_name, rr_symlink_target, rr_relocated_child, rr_relocated, rr_relocated_parent, file_mode): # type: (str, bytes, bytes, bool, bool, bool, int) -> None ''' Internal method to add Rock Ridge to a Directory Record. Parameters: rr_version - A string containing the version of Rock Ridge to use for this record. rr_name - The Rock Ridge name to associate with this directory record. rr_symlink_target - The target for the symlink, if this is a symlink record (otherwise, None). rr_relocated_child - True if this is a directory record for a rock ridge relocated child. rr_relocated - True if this is a directory record for a relocated entry. rr_relocated_parent - True if this is a directory record for a rock ridge relocated parent. file_mode - The Unix file mode for this Rock Ridge entry. Returns: Nothing. ''' if self.parent is None: raise pycdlibexception.PyCdlibInternalError('Invalid call to create new Rock Ridge on root directory') self.rock_ridge = rockridge.RockRidge() is_first_dir_record_of_root = self.file_ident == b'\x00' and self.parent.is_root bytes_to_skip = 0 if self.xa_record is not None: bytes_to_skip = XARecord.length() self.dr_len = self.rock_ridge.new(is_first_dir_record_of_root, rr_name, file_mode, rr_symlink_target, rr_version, rr_relocated_child, rr_relocated, rr_relocated_parent, bytes_to_skip, self.dr_len, {}) # For files, we are done if not self.isdir: return # If this is a directory, we have to manipulate the file links # appropriately. if self.parent.is_root: if self.file_ident == b'\x00' or self.file_ident == b'\x01': # For the dot and dotdot children of the root, add one # directly to their Rock Ridge links. self.rock_ridge.add_to_file_links() else: # For all other children of the root, make sure to add one # to each of the dot and dotdot entries. if len(self.parent.children) < 2: raise pycdlibexception.PyCdlibInvalidISO('Expected at least 2 children of the root directory record, saw %d' % (len(self.parent.children))) if self.parent.children[0].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Dot child of directory has no Rock Ridge; ISO is corrupt') self.parent.children[0].rock_ridge.add_to_file_links() if self.parent.children[1].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Dot-dot child of directory has no Rock Ridge; ISO is corrupt') self.parent.children[1].rock_ridge.add_to_file_links() else: if self.parent.rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Parent of the entry did not have Rock Ridge, ISO is corrupt') if self.file_ident == b'\x00': # If we are adding the dot directory, increment the parent # file links and our file links. self.parent.rock_ridge.add_to_file_links() self.rock_ridge.add_to_file_links() elif self.file_ident == b'\x01': # If we are adding the dotdot directory, copy the file links # from the dot directory of the grandparent. if self.parent.parent is None: raise pycdlibexception.PyCdlibInternalError('Grandparent of the entry did not exist; this cannot be') if not self.parent.children: raise pycdlibexception.PyCdlibInvalidISO('Grandparent of the entry did not have a dot entry; ISO is corrupt') if self.parent.parent.children[0].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Grandparent dotdot entry did not have Rock Ridge; ISO is corrupt') self.rock_ridge.copy_file_links(self.parent.parent.children[0].rock_ridge) else: # For all other entries, increment the parents file links # and the parents dot file links. self.parent.rock_ridge.add_to_file_links() if not self.parent.children: raise pycdlibexception.PyCdlibInvalidISO('Parent of the entry did not have a dot entry; ISO is corrupt') if self.parent.children[0].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Dot child of the parent did not have a dot entry; ISO is corrupt') self.parent.children[0].rock_ridge.add_to_file_links() def _new(self, vd, name, parent, seqnum, isdir, length, xa): # type: (headervd.PrimaryOrSupplementaryVD, bytes, Optional[DirectoryRecord], int, bool, int, bool) -> None ''' Internal method to create a new Directory Record. Parameters: vd - The Volume Descriptor this record is part of. name - The name for this directory record. parent - The parent of this directory record. seqnum - The sequence number to associate with this directory record. isdir - Whether this directory record represents a directory. length - The length of the data for this directory record. xa - True if this is an Extended Attribute record. Returns: Nothing. ''' # Adding a new time should really be done when we are going to write # the ISO (in record()). Ecma-119 9.1.5 says: # # 'This field shall indicate the date and the time of the day at which # the information in the Extent described by the Directory Record was # recorded.' # # We create it here just to have something in the field, but we'll # redo the whole thing when we are mastering. self.date = dates.DirectoryRecordDate() self.date.new() if length > 2**32 - 1: raise pycdlibexception.PyCdlibInvalidInput('Maximum supported file length is 2^32-1') self.data_length = length self.file_ident = name self.isdir = isdir self.seqnum = seqnum # For a new directory record entry, there is no original_extent_loc, # so we leave it at None. self.orig_extent_loc = None self.len_fi = len(self.file_ident) self.dr_len = struct.calcsize(self.FMT) + self.len_fi # From Ecma-119, 9.1.6, the file flag bits are: # # Bit 0 - Existence - 0 for existence known, 1 for hidden # Bit 1 - Directory - 0 for file, 1 for directory # Bit 2 - Associated File - 0 for not associated, 1 for associated # Bit 3 - Record - 0=structure not in xattr, 1=structure in xattr # Bit 4 - Protection - 0=no owner and group, 1=owner and group in xattr # Bit 5 - Reserved # Bit 6 - Reserved # Bit 7 - Multi-extent - 0=final directory record, 1=not final directory record self.file_flags = 0 if self.isdir: self.file_flags |= (1 << self.FILE_FLAG_DIRECTORY_BIT) self.file_unit_size = 0 # FIXME: we don't support setting file unit size for now self.interleave_gap_size = 0 # FIXME: we don't support setting interleave gap size for now self.xattr_len = 0 # FIXME: we don't support xattrs for now self.parent = parent if parent is None: # If no parent, then this is the root self.is_root = True if xa: self.xa_record = XARecord() self.xa_record.new() self.dr_len += XARecord.length() self.dr_len += (self.dr_len % 2) if self.is_root: self._printable_name = b'/' elif self.file_ident == b'\x00': self._printable_name = b'.' elif self.file_ident == b'\x01': self._printable_name = b'..' else: self._printable_name = self.file_ident self.vd = vd self.initialized = True def new_symlink(self, vd, name, parent, rr_target, seqnum, rock_ridge, rr_name, xa): # type: (headervd.PrimaryOrSupplementaryVD, bytes, DirectoryRecord, bytes, int, str, bytes, bool) -> None ''' Create a new symlink Directory Record. This implies that the new record will be Rock Ridge. Parameters: vd - The Volume Descriptor this record is part of. name - The name for this directory record. parent - The parent of this directory record. rr_target - The symlink target for this directory record. seqnum - The sequence number for this directory record. rock_ridge - The version of Rock Ridge to use for this directory record. rr_name - The Rock Ridge name for this directory record. xa - True if this is an Extended Attribute record. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, name, parent, seqnum, False, 0, xa) if rock_ridge: self._rr_new(rock_ridge, rr_name, rr_target, False, False, False, 0o0120555) def new_file(self, vd, length, isoname, parent, seqnum, rock_ridge, rr_name, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, int, bytes, DirectoryRecord, int, str, bytes, bool, int) -> None ''' Create a new file Directory Record. Parameters: vd - The Volume Descriptor this record is part of. length - The length of the data. isoname - The name for this directory record. parent - The parent of this directory record. seqnum - The sequence number for this directory record. rock_ridge - Whether to make this a Rock Ridge directory record. rr_name - The Rock Ridge name for this directory record. xa - True if this is an Extended Attribute record. file_mode - The POSIX file mode for this entry. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, isoname, parent, seqnum, False, length, xa) if rock_ridge: self._rr_new(rock_ridge, rr_name, b'', False, False, False, file_mode) def new_root(self, vd, seqnum, log_block_size): # type: (headervd.PrimaryOrSupplementaryVD, int, int) -> None ''' Create a new root Directory Record. Parameters: vd - The Volume Descriptor this record is part of. seqnum - The sequence number for this directory record. log_block_size - The logical block size to use. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, b'\x00', None, seqnum, True, log_block_size, False) def new_dot(self, vd, parent, seqnum, rock_ridge, log_block_size, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, DirectoryRecord, int, str, int, bool, int) -> None ''' Create a new 'dot' Directory Record. Parameters: vd - The Volume Descriptor this record is part of. parent - The parent of this directory record. seqnum - The sequence number for this directory record. rock_ridge - Whether to make this a Rock Ridge directory record. log_block_size - The logical block size to use. xa - True if this is an Extended Attribute record. file_mode - The POSIX file mode to set for this directory. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, b'\x00', parent, seqnum, True, log_block_size, xa) if rock_ridge: self._rr_new(rock_ridge, b'', b'', False, False, False, file_mode) def new_dotdot(self, vd, parent, seqnum, rock_ridge, log_block_size, rr_relocated_parent, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, DirectoryRecord, int, str, int, bool, bool, int) -> None ''' Create a new 'dotdot' Directory Record. Parameters: vd - The Volume Descriptor this record is part of. parent - The parent of this directory record. seqnum - The sequence number for this directory record. rock_ridge - Whether to make this a Rock Ridge directory record. log_block_size - The logical block size to use. rr_relocated_parent - True if this is a Rock Ridge relocated parent. xa - True if this is an Extended Attribute record. file_mode - The POSIX file mode to set for this directory. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, b'\x01', parent, seqnum, True, log_block_size, xa) if rock_ridge: self._rr_new(rock_ridge, b'', b'', False, False, rr_relocated_parent, file_mode) def new_dir(self, vd, name, parent, seqnum, rock_ridge, rr_name, log_block_size, rr_relocated_child, rr_relocated, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, bytes, DirectoryRecord, int, str, bytes, int, bool, bool, bool, int) -> None ''' Create a new directory Directory Record. Parameters: vd - The Volume Descriptor this record is part of. name - The name for this directory record. parent - The parent of this directory record. seqnum - The sequence number for this directory record. rock_ridge - Whether to make this a Rock Ridge directory record. rr_name - The Rock Ridge name for this directory record. log_block_size - The logical block size to use. rr_relocated_child - True if this is a Rock Ridge relocated child. rr_relocated - True if this is a Rock Ridge relocated entry. xa - True if this is an Extended Attribute record. file_mode - The POSIX file mode to set for this directory. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record already initialized') self._new(vd, name, parent, seqnum, True, log_block_size, xa) if rock_ridge: self._rr_new(rock_ridge, rr_name, b'', rr_relocated_child, rr_relocated, False, file_mode) if rr_relocated_child and self.rock_ridge: # Relocated Rock Ridge entries are not exactly treated as # directories, so fix things up here. self.isdir = False self.file_flags = 0 self.rock_ridge.add_to_file_links() def change_existence(self, is_hidden): # type: (bool) -> None ''' Change the ISO9660 existence flag of this Directory Record. Parameters: is_hidden - True if this Directory Record should be hidden, False otherwise. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') if is_hidden: self.file_flags |= (1 << self.FILE_FLAG_EXISTENCE_BIT) else: self.file_flags &= ~(1 << self.FILE_FLAG_EXISTENCE_BIT) def _recalculate_extents_and_offsets(self, index, logical_block_size): # type: (int, int) -> Tuple[int, int] ''' Internal method to recalculate the extents and offsets associated with children of this directory record. Parameters: index - The index at which to start the recalculation. logical_block_size - The block size to use for comparisons. Returns: A tuple where the first element is the total number of extents required by the children and where the second element is the offset into the last extent currently being used. ''' if index == 0: dirrecord_offset = 0 num_extents = 1 else: dirrecord_offset = self.children[index - 1].offset_to_here num_extents = self.children[index - 1].extents_to_here for i in range(index, len(self.children)): c = self.children[i] dirrecord_len = c.dr_len if (dirrecord_offset + dirrecord_len) > logical_block_size: num_extents += 1 dirrecord_offset = 0 dirrecord_offset += dirrecord_len c.extents_to_here = num_extents c.offset_to_here = dirrecord_offset c.index_in_parent = i return num_extents, dirrecord_offset def _add_child(self, child, logical_block_size, allow_duplicate, check_overflow): # type: (DirectoryRecord, int, bool, bool) -> bool ''' An internal method to add a child to this object. Note that this is called both during parsing and when adding a new object to the system, so it shouldn't have any functionality that is not appropriate for both. Parameters: child - The child directory record object to add. logical_block_size - The size of a logical block for this volume descriptor. allow_duplicate - Whether to allow duplicate names, as there are situations where duplicate children are allowed. check_overflow - Whether to check for overflow; if we are parsing, we don't want to do this. Returns: True if adding this child caused the directory to overflow into another extent, False otherwise. ''' if not self.isdir: raise pycdlibexception.PyCdlibInvalidInput('Trying to add a child to a record that is not a directory') # First ensure that this is not a duplicate. For speed purposes, we # recognize that bisect_left will always choose an index to the *left* # of a duplicate child. Thus, to check for duplicates we only need to # see if the child to be added is a duplicate with the entry that # bisect_left returned. index = bisect.bisect_left(self.children, child) if index != len(self.children) and self.children[index].file_ident == child.file_ident: if not self.children[index].is_associated_file() and not child.is_associated_file(): if not (self.rock_ridge is not None and self.file_identifier() == b'RR_MOVED'): if not allow_duplicate: raise pycdlibexception.PyCdlibInvalidInput('Failed adding duplicate name to parent') self.children[index].data_continuation = child self.children[index].file_flags |= (1 << self.FILE_FLAG_MULTI_EXTENT_BIT) index += 1 self.children.insert(index, child) if child.rock_ridge is not None and not child.is_dot() and not child.is_dotdot(): lo = 0 hi = len(self.rr_children) while lo < hi: mid = (lo + hi) // 2 rr = self.rr_children[mid].rock_ridge if rr is not None: if rr.name() < child.rock_ridge.name(): lo = mid + 1 else: hi = mid else: raise pycdlibexception.PyCdlibInternalError('Expected all children to have Rock Ridge, but one did not') rr_index = lo self.rr_children.insert(rr_index, child) # We now have to check if we need to add another logical block. # We have to iterate over the entire list again, because where we # placed this last entry may rearrange the empty spaces in the blocks # that we've already allocated. num_extents, offset_unused = self._recalculate_extents_and_offsets(index, logical_block_size) overflowed = False if check_overflow and (num_extents * logical_block_size > self.data_length): overflowed = True # When we overflow our data length, we always add a full block. self.data_length += logical_block_size # We also have to make sure to update the length of the dot child, # as that should always reflect the length. self.children[0].data_length = self.data_length # We also have to update all of the dotdot entries. If this is # the root directory record (no parent), we first update the root # dotdot entry. In all cases, we update the dotdot entry of all # children that are directories. if self.parent is None: self.children[1].data_length = self.data_length for c in self.children: if not c.is_dir(): continue if len(c.children) > 1: c.children[1].data_length = self.data_length return overflowed def add_child(self, child, logical_block_size, allow_duplicate=False): # type: (DirectoryRecord, int, bool) -> bool ''' Add a new child to this directory record. Parameters: child - The child directory record object to add. logical_block_size - The size of a logical block for this volume descriptor. allow_duplicate - Whether to allow duplicate names, as there are situations where duplicate children are allowed. Returns: True if adding this child caused the directory to overflow into another extent, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self._add_child(child, logical_block_size, allow_duplicate, True) def track_child(self, child, logical_block_size, allow_duplicate=False): # type: (DirectoryRecord, int, bool) -> None ''' Track an existing child of this directory record. Parameters: child - The child directory record object to add. logical_block_size - The size of a logical block for this volume descriptor. allow_duplicate - Whether to allow duplicate names, as there are situations where duplicate children are allowed. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') self._add_child(child, logical_block_size, allow_duplicate, False) def remove_child(self, child, index, logical_block_size): # type: (DirectoryRecord, int, int) -> bool ''' Remove a child from this Directory Record. Parameters: child - The child DirectoryRecord object to remove. index - The index of the child into this DirectoryRecord children list. logical_block_size - The size of a logical block on this volume descriptor. Returns: True if removing this child caused an underflow, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') if index < 0: # This should never happen raise pycdlibexception.PyCdlibInternalError('Invalid child index to remove') # Unfortunately, Rock Ridge specifies that a CL 'directory' is replaced # by a *file*, not another directory. Thus, we can't just depend on # whether this child is marked as a directory by the file flags during # parse time. Instead, we check if this is either a true directory, # or a Rock Ridge CL entry, and in either case try to manipulate the # file links. if child.rock_ridge is not None: if child.isdir or child.rock_ridge.child_link_record_exists(): if len(self.children) < 2: raise pycdlibexception.PyCdlibInvalidISO('Expected a dot and dotdot entry, but missing; ISO is corrupt') if self.children[0].rock_ridge is None or self.children[1].rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Missing Rock Ridge entry on dot or dotdot; ISO is corrupt') if self.parent is None: self.children[0].rock_ridge.remove_from_file_links() self.children[1].rock_ridge.remove_from_file_links() else: if self.rock_ridge is None: raise pycdlibexception.PyCdlibInvalidISO('Child has Rock Ridge, but parent does not; ISO is corrupt') self.rock_ridge.remove_from_file_links() self.children[0].rock_ridge.remove_from_file_links() del self.children[index] # We now have to check if we need to remove a logical block. # We have to iterate over the entire list again, because where we # removed this last entry may rearrange the empty spaces in the blocks # that we've already allocated. num_extents, dirrecord_offset = self._recalculate_extents_and_offsets(index, logical_block_size) underflow = False total_size = (num_extents - 1) * logical_block_size + dirrecord_offset if (self.data_length - total_size) > logical_block_size: self.data_length -= logical_block_size # We also have to make sure to update the length of the dot child, # as that should always reflect the length. self.children[0].data_length = self.data_length # We also have to update all of the dotdot entries. If this is # the root directory record (no parent), we first update the root # dotdot entry. In all cases, we update the dotdot entry of all # children that are directories. if self.parent is None: self.children[1].data_length = self.data_length for c in self.children: if not c.is_dir(): continue if len(c.children) > 1: c.children[1].data_length = self.data_length underflow = True return underflow def is_dir(self): # type: () -> bool ''' Determine whether this Directory Record is a directory. Parameters: None. Returns: True if this DirectoryRecord object is a directory, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.isdir def is_file(self): # type: () -> bool ''' Determine whether this Directory Record is a file. Parameters: None. Returns: True if this DirectoryRecord object is a file, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return not self.isdir def is_symlink(self): # type: () -> bool ''' Determine whether this Directory Record is a Rock Ridge symlink. If using this to distinguish between symlinks, files, and directories, it is important to call this API *first*; symlinks are also considered files. Parameters: None. Returns: True if this Directory Record object is a symlink, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.rock_ridge is not None and self.rock_ridge.is_symlink() def is_dot(self): # type: () -> bool ''' Determine whether this Directory Record is a 'dot' entry. Parameters: None. Returns: True if this DirectoryRecord object is a 'dot' entry, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.file_ident == b'\x00' def is_dotdot(self): # type: () -> bool ''' Determine whether this Directory Record is a 'dotdot' entry. Parameters: None. Returns: True if this DirectoryRecord object is a 'dotdot' entry, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.file_ident == b'\x01' def directory_record_length(self): # type: () -> int ''' Determine the length of this Directory Record. Parameters: None. Returns: The length of this Directory Record. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.dr_len def _extent_location(self): ''' An internal method to get the location of this Directory Record on the ISO. Parameters: None. Returns: Extent location of this Directory Record on the ISO. ''' if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def extent_location(self): # type: () -> int ''' Get the location of this Directory Record on the ISO. Parameters: None. Returns: Extent location of this Directory Record on the ISO. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self._extent_location() def file_identifier(self): # type: () -> bytes ''' Get the identifier of this Directory Record. Parameters: None. Returns: String representing the identifier of this Directory Record. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self._printable_name def record(self): # type: () -> bytes ''' Generate the string representing this Directory Record. Parameters: None. Returns: String representing this Directory Record. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') # Ecma-119 9.1.5 says the date should reflect the time when the # record was written, so we make a new date now and use that to # write out the record. self.date = dates.DirectoryRecordDate() self.date.new() padlen = struct.calcsize(self.FMT) + self.len_fi padstr = b'\x00' * (padlen % 2) extent_loc = self._extent_location() xa_rec = b'' if self.xa_record is not None: xa_rec = self.xa_record.record() rr_rec = b'' if self.rock_ridge is not None: rr_rec = self.rock_ridge.record_dr_entries() outlist = [struct.pack(self.FMT, self.dr_len, self.xattr_len, extent_loc, utils.swab_32bit(extent_loc), self.data_length, utils.swab_32bit(self.data_length), self.date.record(), self.file_flags, self.file_unit_size, self.interleave_gap_size, self.seqnum, utils.swab_16bit(self.seqnum), self.len_fi) + self.file_ident + padstr + xa_rec + rr_rec] outlist.append(b'\x00' * (len(outlist[0]) % 2)) return b''.join(outlist) def is_associated_file(self): # type: () -> bool ''' Determine whether this file is 'associated' with another file on the ISO. Parameters: None. Returns: True if this file is associated with another file on the ISO, False otherwise. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') return self.file_flags & (1 << self.FILE_FLAG_ASSOCIATED_FILE_BIT) def set_ptr(self, ptr): # type: (path_table_record.PathTableRecord) -> None ''' Set the Path Table Record associated with this Directory Record. Parameters: ptr - The path table record to associate with this Directory Record. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') self.ptr = ptr def set_data_location(self, current_extent, tag_location): # pylint: disable=unused-argument # type: (int, int) -> None ''' Set the new extent location that the data for this Directory Record should live at. Parameters: current_extent - The new extent. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') self.new_extent_loc = current_extent if self.ptr is not None: self.ptr.update_extent_location(current_extent) def get_data_length(self): # type: () -> int ''' Get the length of the data that this Directory Record points to. Parameters: None. Returns: The length of the data that this Directory Record points to. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') if self.inode is not None: return self.inode.get_data_length() return self.data_length def set_data_length(self, length): # type: (int) -> None ''' Set the length of the data that this Directory Record points to. Parameters: length - The new length for the data. Returns: The length of the data that this Directory Record points to. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('Directory Record not initialized') self.data_length = length ############# START BACKWARDS COMPATIBILITY ############################### # We have a few downstream users that are using 'data_fp', # 'original_data_location', 'DATA_ON_ORIGINAL_ISO', 'DATA_IN_EXTERNAL_FP', # and 'fp_offset' directly. For backwards compatibility # we define properties here that access these. Note that this won't work # in all circumstances, but is good enough for a read-only client. @property def data_fp(self): # type: () -> Optional[Union[BinaryIO, str]] ''' Backwards compatibility property for 'data_fp'. ''' if self.inode is None: return None return self.inode.data_fp @property def original_data_location(self): # type: () -> Optional[int] ''' Backwards compatibility property for 'original_data_location'. ''' if self.inode is None: return None return self.inode.original_data_location @property def DATA_ON_ORIGINAL_ISO(self): # type: () -> int ''' Backwards compatibility property for 'DATA_ON_ORIGINAL_ISO'. ''' return inode.Inode.DATA_ON_ORIGINAL_ISO @property def DATA_IN_EXTERNAL_FP(self): # type: () -> int ''' Backwards compatibility property for 'DATA_IN_EXTERNAL_FP'. ''' return inode.Inode.DATA_IN_EXTERNAL_FP @property def fp_offset(self): # type: () -> Optional[int] ''' Backwards compatibility property for 'fp_offset'. ''' if self.inode is None: return None return self.inode.fp_offset ############# END BACKWARDS COMPATIBILITY ################################# def __lt__(self, other): # This method is used for the bisect.insort_left() when adding a child. # It needs to return whether self is less than other. Here we use the # ISO9660 sorting order which is essentially: # # 1. The \x00 is always the 'dot' record, and is always first. # 2. The \x01 is always the 'dotdot' record, and is always second. # 3. Other entries are sorted lexically; this does not exactly match # the sorting method specified in Ecma-119, but does OK for now. # # Ecma-119 Section 9.3 specifies that we need to pad out the shorter of # the two files with 0x20 (spaces), then compare byte-by-byte until # they differ. However, we can more easily just do the string equality # comparison, since it will always be the case that 0x20 will be less # than any of the other allowed characters in the strings. if self.file_ident == b'\x00': if other.file_ident == b'\x00': return False return True if other.file_ident == b'\x00': return False if self.file_ident == b'\x01': if other.file_ident == b'\x00': return False return True if other.file_ident == b'\x01': # If self.file_ident was '\x00', it would have been caught above. return False return self.file_ident < other.file_ident def __ne__(self, other): # type: (object) -> bool if not isinstance(other, DirectoryRecord): return NotImplemented # Note that we very specifically do not check the extent_location when # comparing directory records. In a lazy-extent assigning world, the # extents are not reliable, so we just rely on the rest of the fields to # tell us if two directory records are the same. return self.dr_len != other.dr_len or self.xattr_len != other.xattr_len or self.data_length != other.data_length or self.date != other.date or self.file_flags != other.file_flags or self.file_unit_size != other.file_unit_size or self.interleave_gap_size != other.interleave_gap_size or self.seqnum != other.seqnum or self.len_fi != other.len_fi or self.file_ident != other.file_ident def __eq__(self, other): # type: (object) -> bool return not self.__ne__(other) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600303763.3726304 pycdlib-1.11.0/pycdlib/eltorito.py0000664000175000017500000011136300000000000021651 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Classes to support El Torito. ''' from __future__ import absolute_import from __future__ import print_function import os import struct from pycdlib import pycdlibexception from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import List, Union # NOQA pylint: disable=unused-import # NOTE: these imports have to be here to avoid circular deps from pycdlib import dr # NOQA pylint: disable=unused-import from pycdlib import headervd # NOQA pylint: disable=unused-import from pycdlib import inode # NOQA pylint: disable=unused-import from pycdlib import udf as udfmod # NOQA pylint: disable=unused-import class EltoritoBootInfoTable(object): ''' A class that represents an El Torito Boot Info Table. The Boot Info Table is an optional table that may be patched into the boot file at offset 8, and is 64-bytes long. ''' __slots__ = ('_initialized', 'orig_len', 'csum', 'vd', 'inode') def __init__(self): # type: () -> None self._initialized = False def parse(self, vd, datastr, ino): # type: (headervd.PrimaryOrSupplementaryVD, bytes, inode.Inode) -> bool ''' Parse a boot info table out of a string. Parameters: vd - The Volume Descriptor associated with this Boot Info Table. datastr - The string to parse the boot info table out of. ino - The Inode associated with the boot file. Returns: True if this is a valid El Torito Boot Info Table, False otherwise. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Eltorito Boot Info Table is already initialized') # http://xpt.sourceforge.net/techdocs/media/cd/cd09-BootableCDs/ # suggests that this is all little-endian, so we'll take its # word for it for now. (pvd_extent, rec_extent, self.orig_len, self.csum) = struct.unpack_from(' None ''' Create a new boot info table. Parameters: vd - The volume descriptor to associate with this boot info table. ino - The Inode associated with this Boot Info Table. orig_len - The original length of the file before the boot info table was patched into it. csum - The checksum for the boot file, starting at the byte after the boot info table. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Eltorito Boot Info Table is already initialized') self.vd = vd self.orig_len = orig_len self.csum = csum self.inode = ino self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this boot info table. Parameters: None. Returns: A string representing this boot info table. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Eltorito Boot Info Table not initialized') return struct.pack(' int ''' Static method to return the length of the boot info table header (ignoring the 40 bytes of padding). Parameters: None. Returns: An integer describing the length of the boot info table header. ''' return 16 class EltoritoValidationEntry(object): ''' A class that represents an El Torito Validation Entry. El Torito requires that the first entry in the El Torito Boot Catalog be a validation entry. ''' __slots__ = ('_initialized', 'platform_id', 'id_string', 'checksum') # An El Torito validation entry consists of: # Offset 0x0: Header ID (0x1) # Offset 0x1: Platform ID (0 for x86, 1 for PPC, 2 for Mac) # Offset 0x2-0x3: Reserved, must be 0 # Offset 0x4-0x1b: ID String for manufacturer of CD # Offset 0x1c-0x1d: Checksum of all bytes. # Offset 0x1e: Key byte 0x55 # Offset 0x1f: Key byte 0xaa FMT = ' None self._initialized = False @staticmethod def _checksum(data): # type: (bytes) -> int ''' A static method to compute the checksum on the ISO. Note that this is *not* a 1's complement checksum; when an addition overflows, the carry bit is discarded, not added to the end. Parameters: data - The data to compute the checksum over. Returns: The checksum of the data. ''' def identity(x): # type: (int) -> int ''' The identity function so we can use a function for python2/3 compatibility. ''' return x if isinstance(data, str): myord = ord elif isinstance(data, bytes): myord = identity csum = 0 for i, val in enumerate(data): short = (myord(val) << (8 * (i % 2))) & 0xffff # Because we are looping through bytes, and because we know the # above conversion can shift us up by a maximum of 8 bits, we know # that the sign bit is at offset 15. If it is signed, OR with # 0xffff0000 to make sure it is sign-extended. adder = short if short & (1 << 15): adder |= 0xffff0000 csum += adder return (-csum) & 0xffff def parse(self, valstr): # type: (bytes) -> None ''' Parse an El Torito Validation Entry out of a string. Parameters: valstr - The string to parse the El Torito Validation Entry out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Validation Entry already initialized') (header_id, self.platform_id, reserved_unused, self.id_string, self.checksum, keybyte1, keybyte2) = struct.unpack_from(self.FMT, valstr, 0) if header_id != 1: raise pycdlibexception.PyCdlibInvalidISO('El Torito Validation entry header ID not 1') if self.platform_id not in (0, 1, 2, 0xef): raise pycdlibexception.PyCdlibInvalidISO('El Torito Validation entry platform ID not valid') if keybyte1 != 0x55: raise pycdlibexception.PyCdlibInvalidISO('El Torito Validation entry first keybyte not 0x55') if keybyte2 != 0xaa: raise pycdlibexception.PyCdlibInvalidISO('El Torito Validation entry second keybyte not 0xaa') # Now that we've done basic checking, calculate the checksum of the # validation entry and make sure it is right. if self._checksum(valstr) != 0: raise pycdlibexception.PyCdlibInvalidISO('El Torito Validation entry checksum not correct') self._initialized = True def new(self, platform_id): # type: (int) -> None ''' Create a new El Torito Validation Entry. Parameters: platform_id - The platform ID to set for this validation entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Validation Entry already initialized') if platform_id not in (0, 1, 2, 0xef): raise pycdlibexception.PyCdlibInvalidInput('Invalid platform ID (must be one of 0, 1, 2, or 0xef)') self.platform_id = platform_id self.id_string = b'\x00' * 24 # FIXME: let the user set this self.checksum = 0 self.checksum = self._checksum(self._record()) self._initialized = True def _record(self): # type: () -> bytes ''' An internal method to generate a string representing this El Torito Validation Entry. Parameters: None. Returns: String representing this El Torito Validation Entry. ''' return struct.pack(self.FMT, 1, self.platform_id, 0, self.id_string, self.checksum, 0x55, 0xaa) def record(self): # type: () -> bytes ''' Generate a string representing this El Torito Validation Entry. Parameters: None. Returns: String representing this El Torito Validation Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Validation Entry not initialized') return self._record() class EltoritoEntry(object): ''' A class that represents an El Torito Entry (Initial or Section). ''' __slots__ = ('_initialized', 'inode', 'boot_indicator', 'boot_media_type', 'load_segment', 'system_type', 'sector_count', 'load_rba', 'selection_criteria_type', 'selection_criteria') # An El Torito entry consists of: # Offset 0x0: Boot indicator (0x88 for bootable, 0x00 for # non-bootable) # Offset 0x1: Boot media type. One of 0x0 for no emulation, # 0x1 for 1.2M diskette emulation, 0x2 for 1.44M # diskette emulation, 0x3 for 2.88M diskette # emulation, or 0x4 for Hard Disk emulation. # Offset 0x2-0x3: Load Segment - if 0, use traditional 0x7C0. # Offset 0x4: System Type - copy of Partition Table byte 5 # Offset 0x5: Unused, must be 0 # Offset 0x6-0x7: Sector Count - Number of virtual sectors to store # during initial boot. # Offset 0x8-0xb: Load RBA - Start address of virtual disk. # For Initial Entry: # Offset 0xc-0x1f: Unused, must be 0. # For Section Entry: # Offset 0xc: Selection criteria type # Offset 0xd-0x1f: Selection critera FMT = ' None self._initialized = False def parse(self, valstr): # type: (bytes) -> None ''' Parse an El Torito Entry out of a string. Parameters: valstr - The string to parse the El Torito Entry out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry already initialized') (self.boot_indicator, self.boot_media_type, self.load_segment, self.system_type, unused1, self.sector_count, self.load_rba, self.selection_criteria_type, self.selection_criteria) = struct.unpack_from(self.FMT, valstr, 0) if self.boot_indicator not in (0x88, 0x00): raise pycdlibexception.PyCdlibInvalidISO('Invalid El Torito initial entry boot indicator') if self.boot_media_type > 4: raise pycdlibexception.PyCdlibInvalidISO('Invalid El Torito boot media type') # FIXME: check that the system type matches the partition table if unused1 != 0: raise pycdlibexception.PyCdlibInvalidISO('El Torito unused field must be 0') # According to the specification, the El Torito unused end field (bytes # 0xc - 0x1f, unused2 field) should be all zero. However, we have found # ISOs in the wild where that is not the case, so skip that particular # check here. self._initialized = True def new(self, sector_count, load_seg, media_name, system_type, bootable): # type: (int, int, str, int, bool) -> None ''' Create a new El Torito Entry. Parameters: sector_count - The number of sectors to assign to this El Torito Entry. load_seg - The load segment address of the boot image. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. system_type - The partition type to assign to the entry. bootable - Whether this entry is bootable. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry already initialized') if media_name == 'noemul': media_type = self.MEDIA_NO_EMUL elif media_name == 'floppy': if sector_count == 2400: media_type = self.MEDIA_12FLOPPY elif sector_count == 2880: media_type = self.MEDIA_144FLOPPY elif sector_count == 5760: media_type = self.MEDIA_288FLOPPY else: raise pycdlibexception.PyCdlibInvalidInput('Invalid sector count for floppy media type; must be 2400, 2880, or 5760') # With floppy booting, the sector_count always ends up being 1 sector_count = 1 elif media_name == 'hdemul': media_type = self.MEDIA_HD_EMUL # With HD emul booting, the sector_count always ends up being 1 sector_count = 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid media name '%s'" % (media_name)) if bootable: self.boot_indicator = 0x88 else: self.boot_indicator = 0 self.boot_media_type = media_type self.load_segment = load_seg self.system_type = system_type self.sector_count = sector_count self.load_rba = 0 # This will get set later self.selection_criteria_type = 0 # FIXME: allow the user to set this self.selection_criteria = b''.ljust(19, b'\x00') self._initialized = True def get_rba(self): # type: () -> int ''' Get the load_rba for this El Torito Entry. Parameters: None. Returns: The load RBA for this El Torito Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') return self.load_rba def set_data_location(self, current_extent, tag_location): # pylint: disable=unused-argument # type: (int, int) -> None ''' Update the extent (and RBA) for this entry. Parameters: current_extent - The new extent to set for this entry. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') self.load_rba = current_extent def set_inode(self, ino): # type: (inode.Inode) -> None ''' Set the Inode associated with this El Torito Entry. Parameters: ino - The Inode object corresponding to this entry. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') self.inode = ino def record(self): # type: () -> bytes ''' Generate a string representing this El Torito Entry. Parameters: None. Returns: String representing this El Torito Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') return struct.pack(self.FMT, self.boot_indicator, self.boot_media_type, self.load_segment, self.system_type, 0, self.sector_count, self.load_rba, self.selection_criteria_type, self.selection_criteria) def length(self): # type: () -> int ''' Get the length, in bytes, of this El Torito Entry. Parameters: None. Returns: An integer representing the length in bytes of this El Torito Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') # According to El Torito, the sector count is in virtual sectors, which # are defined to be 512 bytes. return self.sector_count * 512 def set_data_length(self, length): # type: (int) -> None ''' Set the length of data for this El Torito Entry. Parameters: length - The new length for the El Torito Entry. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Entry not initialized') self.sector_count = utils.ceiling_div(length, 512) class EltoritoSectionHeader(object): ''' A class that represents an El Torito Section Header. ''' __slots__ = ('_initialized', 'header_indicator', 'platform_id', 'num_section_entries', 'id_string', 'section_entries') FMT = ' None self._initialized = False self.section_entries = [] # type: List[EltoritoEntry] def parse(self, valstr): # type: (bytes) -> None ''' Parse an El Torito section header from a string. Parameters: valstr - The string to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header already initialized') (self.header_indicator, self.platform_id, self.num_section_entries, self.id_string) = struct.unpack_from(self.FMT, valstr, 0) self._initialized = True def new(self, id_string, platform_id): # type: (bytes, int) -> None ''' Create a new El Torito section header. Parameters: id_string - The ID to use for this section header. platform_id - The platform ID for this section header. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header already initialized') # We always assume this is the last section, until we are told otherwise # via set_record_not_last. self.header_indicator = 0x91 self.platform_id = platform_id self.num_section_entries = 0 self.id_string = id_string self._initialized = True def add_parsed_entry(self, entry): # type: (EltoritoEntry) -> None ''' Add a parsed entry to the list of entries of this header. If the number of parsed entries exceeds what was expected from the initial parsing of the header, this method will throw an Exception. Parameters: entry - The EltoritoEntry object to add to the list of entries. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header not initialized') if len(self.section_entries) >= self.num_section_entries: raise pycdlibexception.PyCdlibInvalidISO('El Torito section had more entries than expected by section header; ISO is corrupt') self.section_entries.append(entry) def add_new_entry(self, entry): # type: (EltoritoEntry) -> None ''' Add a completely new entry to the list of entries of this header. Parameters: entry - The new EltoritoEntry object to add to the list of entries. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header not initialized') self.num_section_entries += 1 self.section_entries.append(entry) def set_record_not_last(self): # type: () -> None ''' Set this Section Header so that it is *not* the last one in the Boot Catalog; this is used when a new header is added. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header not initialized') self.header_indicator = 0x90 def record(self): # type: () -> bytes ''' Get a string representing this El Torito section header. Parameters: None. Returns: A string representing this El Torito section header. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header not initialized') outlist = [struct.pack(self.FMT, self.header_indicator, self.platform_id, self.num_section_entries, self.id_string)] for entry in self.section_entries: outlist.append(entry.record()) return b''.join(outlist) class EltoritoBootCatalog(object): ''' A class that represents an El Torito Boot Catalog. The boot catalog is the basic unit of El Torito, and is expected to contain a validation entry, an initial entry, and zero or more section entries. ''' __slots__ = ('_initialized', 'dirrecords', 'br', 'initial_entry', 'validation_entry', 'sections', 'standalone_entries', 'state') EXPECTING_VALIDATION_ENTRY = 1 EXPECTING_INITIAL_ENTRY = 2 EXPECTING_SECTION_HEADER_OR_DONE = 3 def __init__(self, br): # type: (headervd.BootRecord) -> None self.dirrecords = [] # type: List[Union[dr.DirectoryRecord, udfmod.UDFFileEntry]] self._initialized = False self.br = br self.initial_entry = EltoritoEntry() self.validation_entry = EltoritoValidationEntry() self.sections = [] # type: List[EltoritoSectionHeader] self.standalone_entries = [] # type: List[EltoritoEntry] self.state = self.EXPECTING_VALIDATION_ENTRY def parse(self, valstr): # type: (bytes) -> bool ''' Parse an El Torito Boot Catalog out of a string. Parameters: valstr - The string to parse the El Torito Boot Catalog out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog already initialized') if self.state == self.EXPECTING_VALIDATION_ENTRY: # The first entry in an El Torito boot catalog is the Validation # Entry. A Validation entry consists of 32 bytes (described in # detail in the parse_eltorito_validation_entry() method). self.validation_entry.parse(valstr) self.state = self.EXPECTING_INITIAL_ENTRY elif self.state == self.EXPECTING_INITIAL_ENTRY: # The next entry is the Initial/Default entry. An Initial/Default # entry consists of 32 bytes (described in detail in the # parse_eltorito_initial_entry() method). self.initial_entry.parse(valstr) self.state = self.EXPECTING_SECTION_HEADER_OR_DONE else: val = bytes(bytearray([valstr[0]])) if val == b'\x00': # An empty entry tells us we are done parsing El Torito. Do # some sanity checks. last_section_index = len(self.sections) - 1 for index, sec in enumerate(self.sections): if sec.num_section_entries != len(sec.section_entries): raise pycdlibexception.PyCdlibInvalidISO('El Torito section header specified %d entries, only saw %d' % (sec.num_section_entries, len(sec.section_entries))) if index != last_section_index: if sec.header_indicator != 0x90: raise pycdlibexception.PyCdlibInvalidISO('Intermediate El Torito section header not properly specified') # In theory, we should also make sure that the very last # section has a header_indicator of 0x91. However, we # have seen ISOs in the wild (FreeBSD 11.0 amd64) in which # this is not the case, so we skip that check. self._initialized = True elif val in (b'\x90', b'\x91'): # A Section Header Entry section_header = EltoritoSectionHeader() section_header.parse(valstr) self.sections.append(section_header) elif val in (b'\x88', b'\x00'): # A Section Entry. According to El Torito 2.4, a Section Entry # must follow a Section Header, but we have seen ISOs in the # wild that do not follow this (Mageia 4 ISOs, for instance). # To deal with this, we get a little complicated here. If there # is a previous section header, and the length of the entries # attached to it is less than the number of entries it should # have, then we attach this entry to that header. If there is # no previous section header, or if the previous section header # is already 'full', then we make this a standalone entry. secentry = EltoritoEntry() secentry.parse(valstr) if self.sections and len(self.sections[-1].section_entries) < self.sections[-1].num_section_entries: self.sections[-1].add_parsed_entry(secentry) else: self.standalone_entries.append(secentry) elif val == b'\x44': # A Section Entry Extension self.sections[-1].section_entries[-1].selection_criteria += valstr[2:] else: raise pycdlibexception.PyCdlibInvalidISO('Invalid El Torito Boot Catalog entry') return self._initialized def new(self, br, ino, sector_count, load_seg, media_name, system_type, platform_id, bootable): # type: (headervd.BootRecord, inode.Inode, int, int, str, int, int, bool) -> None ''' Create a new El Torito Boot Catalog. Parameters: br - The boot record that this El Torito Boot Catalog is associated with. ino - The Inode to associate with the initial entry. sector_count - The number of sectors for the initial entry. load_seg - The load segment address of the boot image. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. system_type - The partition type the entry should be. platform_id - The platform id to set in the validation entry. bootable - Whether this entry should be bootable. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog already initialized') # Create the El Torito validation entry self.validation_entry.new(platform_id) self.initial_entry.new(sector_count, load_seg, media_name, system_type, bootable) self.initial_entry.set_inode(ino) ino.linked_records.append((self.initial_entry, False)) self.br = br self._initialized = True def add_section(self, ino, sector_count, load_seg, media_name, system_type, efi, bootable): # type: (inode.Inode, int, int, str, int, bool, bool) -> None ''' Add an section header and entry to this Boot Catalog. Parameters: ino - The Inode object to associate with the new Entry. sector_count - The number of sectors to assign to the new Entry. load_seg - The load segment address of the boot image. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. system_type - The type of partition this entry should be. efi - Whether this section is an EFI section. bootable - Whether this entry should be bootable. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog not initialized') # The Eltorito Boot Catalog can only be 2048 bytes (1 extent). By # default, the first 64 bytes are used by the Validation Entry and the # Initial Entry, which leaves 1984 bytes. Each section takes up 32 # bytes for the Section Header and 32 bytes for the Section Entry, for # a total of 64 bytes, so we can have a maximum of 1984/64 = 31 # sections. if len(self.sections) == 31: raise pycdlibexception.PyCdlibInvalidInput('Too many El Torito sections') sec = EltoritoSectionHeader() platform_id = self.validation_entry.platform_id if efi: platform_id = 0xef sec.new(b'\x00' * 28, platform_id) secentry = EltoritoEntry() secentry.new(sector_count, load_seg, media_name, system_type, bootable) secentry.set_inode(ino) ino.linked_records.append((secentry, False)) sec.add_new_entry(secentry) if self.sections: self.sections[-1].set_record_not_last() self.sections.append(sec) def record(self): # type: () -> bytes ''' Generate a string representing this El Torito Boot Catalog. Parameters: None. Returns: A string representing this El Torito Boot Catalog. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog not initialized') outlist = [self.validation_entry.record(), self.initial_entry.record()] for sec in self.sections: outlist.append(sec.record()) for entry in self.standalone_entries: outlist.append(entry.record()) return b''.join(outlist) def add_dirrecord(self, rec): # type: (Union[dr.DirectoryRecord, udfmod.UDFFileEntry]) -> None ''' Set the Directory Record associated with this Boot Catalog. Parameters: rec - The DirectoryRecord object to associate with this Boot Catalog. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog not initialized') self.dirrecords.append(rec) def _extent_location(self): # type: () -> int ''' An internal method to get the extent location of this Boot Catalog. Parameters: None. Returns: The extent location of this Boot Catalog. ''' return struct.unpack_from(' int ''' Get the extent location of this El Torito Boot Catalog. Parameters: None. Returns: Integer extent location of this El Torito Boot Catalog. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog not initialized') return self._extent_location() def update_catalog_extent(self, current_extent): # type: (int) -> None ''' Update the extent associated with this Boot Catalog. Parameters: current_extent - New extent to associate with this Boot Catalog Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Boot Catalog not initialized') packed = struct.pack(' int ''' A function to sanity check an El Torito Hard Drive Master Boot Record (HDMBR). On success, it returns the system_type (also known as the partition type) that should be fed into the rest of the El Torito methods. On failure, it raises an exception. Parameters: disk_mbr - The data to look in. sector_count - The number of sectors expected in the MBR. bootable - Whether this MBR is bootable. Returns: The system (or partition) type the should be fed into the rest of El Torito. ''' # The MBR that we want to see to do hd emulation boot for El Torito is a standard # x86 MBR, documented here: # https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout # # In brief, it should consist of 512 bytes laid out like: # Offset 0x0 - 0x1BD: Bootstrap code area # Offset 0x1BE - 0x1CD: Partition entry 1 # Offset 0x1CE - 0x1DD: Partition entry 2 # Offset 0x1DE - 0x1ED: Partition entry 3 # Offset 0x1EE - 0x1FD: Partition entry 4 # Offset 0x1FE: 0x55 # Offset 0x1FF: 0xAA # # Each partition entry above should consist of: # Offset 0x0: Active (bit 7 set) or inactive (all zeros) # Offset 0x1 - 0x3: CHS address of first sector in partition # Offset 0x1: Head # Offset 0x2: Sector in bits 0-5, bits 6-7 are high bits of of cylinder # Offset 0x3: bits 0-7 of cylinder # Offset 0x4: Partition type (almost all of these are valid, see https://en.wikipedia.org/wiki/Partition_type) # Offset 0x5 - 0x7: CHS address of last sector in partition (same format as first sector) # Offset 0x8 - 0xB: LBA of first sector in partition # Offset 0xC - 0xF: number of sectors in partition PARTITION_TYPE_UNUSED = 0x0 PARTITION_STATUS_ACTIVE = 0x80 (bootstrap_unused, part1, part2, part3, part4, keybyte1, keybyte2) = struct.unpack_from('=446s16s16s16s16sBB', disk_mbr, 0) if keybyte1 != 0x55 or keybyte2 != 0xAA: raise pycdlibexception.PyCdlibInvalidInput('Invalid magic on HD MBR') parts = [part1, part2, part3, part4] system_type = PARTITION_TYPE_UNUSED for part in parts: (status, s_head, s_seccyl, s_cyl, parttype, e_head, e_seccyl, e_cyl, lba_unused, num_sectors_unused) = struct.unpack('=BBBBBBBBLL', part) if parttype == PARTITION_TYPE_UNUSED: continue if system_type != PARTITION_TYPE_UNUSED: raise pycdlibexception.PyCdlibInvalidInput('Boot image has multiple partitions') if bootable and status != PARTITION_STATUS_ACTIVE: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: partition not marked active', file=devnull) cyl = ((s_seccyl & 0xC0) << 10) | s_cyl sec = s_seccyl & 0x3f if cyl != 0 or s_head != 1 or sec != 1: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: partition does not start at 0/1/1', file=devnull) cyl = ((e_seccyl & 0xC0) << 10) | e_cyl sec = e_seccyl & 0x3f geometry_sectors = (cyl + 1) * (e_head + 1) * sec if sector_count != geometry_sectors: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: image size does not match geometry', file=devnull) system_type = parttype if system_type == PARTITION_TYPE_UNUSED: raise pycdlibexception.PyCdlibInvalidInput('Boot image has no partitions') return system_type ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4881055 pycdlib-1.11.0/pycdlib/facade.py0000664000175000017500000010706100000000000021213 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Facade classes to make the main PyCdlib object easier to use. ''' from __future__ import absolute_import from pycdlib import dr from pycdlib import pycdlibexception from pycdlib import udf as udfmod from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import BinaryIO, Generator, Optional, Tuple # NOQA pylint: disable=unused-import # NOTE: these imports have to be here to avoid circular deps from pycdlib import pycdlib # NOQA pylint: disable=unused-import from pycdlib import pycdlibio # NOQA pylint: disable=unused-import def iso_path_to_rr_name(iso_path, interchange_level, is_dir): # type: (str, int, bool) -> str ''' Take an absolute ISO path and generate a corresponding Rock Ridge basename. Parameters: iso_path - The absolute iso_path to generate a Rock Ridge name from. interchange_level - The interchange level at which to operate. is_dir - Whether this will be a directory or not. Returns: The Rock Ridge name as a string. ''' if iso_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput("iso_path must start with '/'") namesplit = utils.split_path(utils.normpath(iso_path)) iso_name = namesplit.pop() if is_dir: rr_name = utils.mangle_dir_for_iso9660(iso_name.decode('utf-8'), interchange_level) else: basename, ext = utils.mangle_file_for_iso9660(iso_name.decode('utf-8'), interchange_level) rr_name = '.'.join([basename, ext]) return rr_name class PyCdlibISO9660(object): ''' The class representing the PyCdlib ISO9660 facade. ''' __slots__ = ('pycdlib_obj',) def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None self.pycdlib_obj = pycdlib_obj def get_file_from_iso(self, local_path, iso_path): # type: (str, str) -> None ''' Fetch a single file from the ISO via an absolute ISO path and write it out to a local file. Parameters: local_path - The local file to write to. iso_path - The absolute ISO9660 path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso(local_path, iso_path=iso_path) def get_file_from_iso_fp(self, outfp, iso_path): # type: (BinaryIO, str) -> None ''' Fetch a single file from the ISO via an absolute ISO path and write it out to the file object. Parameters: outfp - The file object to write data to. iso_path - The absolute ISO9660 path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso_fp(outfp, iso_path=iso_path) def add_fp(self, fp, length, iso_path): # type: (BinaryIO, int, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the ISO9660 context (and by extension, the Rock Ridge context). If the ISO is a Rock Ridge one, then a Rock Ridge name will be generated from the ISO path. For more control over which contexts a file shows up in, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Note that the caller must ensure that 'fp' remains open for the lifetime of the PyCdlib object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. To have PyCdlib manage this automatically, use 'add_file' instead. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. iso_path - The ISO9660 absolute path to the file destination on the ISO. Returns: Nothing. ''' rr_name = None if self.pycdlib_obj.has_rock_ridge(): rr_name = iso_path_to_rr_name(iso_path, self.pycdlib_obj.interchange_level, False) self.pycdlib_obj.add_fp(fp, length, iso_path=iso_path, rr_name=rr_name) def add_file(self, filename, iso_path): # type: (str, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the ISO9660 context (and by extension, the Rock Ridge context). If the ISO is a Rock Ridge one, then a Rock Ridge name will be generated from the ISO path. For more control over which contexts in which a file shows up, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Parameters: filename - The filename to use for the data contents for the new file. iso_path - The ISO9660 absolute path to the file destination on the ISO. Returns: Nothing. ''' rr_name = None if self.pycdlib_obj.has_rock_ridge(): rr_name = iso_path_to_rr_name(iso_path, self.pycdlib_obj.interchange_level, False) self.pycdlib_obj.add_file(filename, iso_path=iso_path, rr_name=rr_name) def add_directory(self, iso_path): # type: (str) -> None ''' Add a directory to the ISO9660 context (and by extension, the Rock Ridge context). If the ISO is a Rock Ridge one, then a Rock Ridge name will be generated from the ISO path. For more control over which contexts in which a directory shows up, use the regular PyCdlib object (not this facade). Parameters: iso_path - The ISO9660 absolute path to use for the directory. Returns: Nothing. ''' rr_name = None if self.pycdlib_obj.has_rock_ridge(): rr_name = iso_path_to_rr_name(iso_path, self.pycdlib_obj.interchange_level, True) self.pycdlib_obj.add_directory(iso_path=iso_path, rr_name=rr_name) def rm_file(self, iso_path): # type: (str) -> None ''' Remove a file from the ISO. This removes the data and the listing of the file from all contexts. Due to some complexities of the ISO format, removal of zero-byte files from all contexts does not automatically happen, so this method may need to be called on more than one facade for zero-byte files. Parameters: iso_path - The path to the file to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_file(iso_path=iso_path) def rm_directory(self, iso_path): # type: (str) -> None ''' Remove a directory from the ISO. This removes the directory from just the ISO9660 context (and by extension, the Rock Ridge context). The directory must be empty. Parameters: iso_path - The path to the directory to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_directory(iso_path=iso_path) def list_children(self, iso_path): # type: (str) -> Generator ''' Generate a list of all of the file/directory objects in the specified location on the ISO. Parameters: iso_path - The absolute path on the ISO to list the children for. Yields: Children of this path. Returns: Nothing. ''' return self.pycdlib_obj.list_children(iso_path=iso_path) def get_record(self, iso_path): # type: (str) -> dr.DirectoryRecord ''' Get the directory record for a particular ISO path. Parameters: iso_path - The absolute path on the ISO9660 filesystem to get the record for. Returns: A dr.DirectoryRecord object that represents the path. ''' ret = self.pycdlib_obj.get_record(iso_path=iso_path) if not isinstance(ret, dr.DirectoryRecord): raise pycdlibexception.PyCdlibInternalError('Invalid return type from get_record') return ret def walk(self, iso_path): # type: (str) -> Generator ''' Walk the entries on the ISO, starting at the given ISO path. Similar to os.walk(), yield a 3-tuple of (path-to-here, dirlist, filelist) for each directory level. Parameters: iso_path - The absolute ISO path to the starting entry on the ISO. Yields: 3-tuples of (path-to-here, dirlist, filelist) Returns: Nothing. ''' return self.pycdlib_obj.walk(iso_path=iso_path) def open_file_from_iso(self, iso_path): # type: (str) -> pycdlibio.PyCdlibIO ''' Open a file for reading in a context manager. This allows the user to operate on the file in user-defined chunks (utilizing the read() method of the returned context manager). Parameters: iso_path - The absolute ISO path to the file on the ISO. Returns: A PyCdlibIO object allowing access to the file. ''' return self.pycdlib_obj.open_file_from_iso(iso_path=iso_path) class PyCdlibJoliet(object): ''' The class representing the PyCdlib Joliet facade. ''' __slots__ = ('pycdlib_obj',) def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None if not pycdlib_obj.has_joliet(): raise pycdlibexception.PyCdlibInvalidInput('Can only instantiate a Joliet facade for a Joliet ISO') self.pycdlib_obj = pycdlib_obj def get_file_from_iso(self, local_path, joliet_path): # type: (str, str) -> None ''' Fetch a single file from the ISO via an absolute Joliet path and write it out to a local file. Parameters: local_path - The local file to write to. joliet_path - The absolute Joliet path to lookup on the ISO (exclusive with iso_path, rr_path, and udf_path). Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso(local_path, joliet_path=joliet_path) def get_file_from_iso_fp(self, outfp, joliet_path): # type: (BinaryIO, str) -> None ''' Fetch a single file from the ISO via an absolute Joliet path and write it out to the file object. Parameters: outfp - The file object to write data to. joliet_path - The absolute Joliet path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso_fp(outfp, joliet_path=joliet_path) def add_fp(self, fp, length, joliet_path): # type: (BinaryIO, int, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the Joliet context. For more control over which contexts a file shows up in, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Note that the caller must ensure that 'fp' remains open for the lifetime of the PyCdlib object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. To have PyCdlib manage this automatically, use 'add_file' instead. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. joliet_path - The Joliet absolute path to the file destination on the ISO. Returns: Nothing. ''' # Since Joliet is a totally separate namespace from ISO9660/Rock Ridge, # there is no reliable way to find the ISO9660 path from the Joliet # path. Thus, when using the Joliet facade, any updates to Joliet will # only be reflected on the Joliet side of the ISO. self.pycdlib_obj.add_fp(fp, length, joliet_path=joliet_path) def add_file(self, filename, joliet_path): # type: (str, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the Joliet context. For more control over which contexts in which a file shows up, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Parameters: filename - The filename to use for the data contents for the new file. joliet_path - The Joliet absolute path to the file destination on the ISO. Returns: Nothing. ''' # Since Joliet is a totally separate namespace from ISO9660/Rock Ridge, # there is no reliable way to find the ISO9660 path from the Joliet # path. Thus, when using the Joliet facade, any updates to Joliet will # only be reflected on the Joliet side of the ISO. self.pycdlib_obj.add_file(filename, joliet_path=joliet_path) def add_directory(self, joliet_path): # type: (str) -> None ''' Add a directory to the Joliet context. For more control over which contexts in which a directory shows up, use the regular PyCdlib object (not this facade). Parameters: iso_path - The ISO9660 absolute path to use for the directory. Returns: Nothing. ''' self.pycdlib_obj.add_directory(joliet_path=joliet_path) def rm_file(self, joliet_path): # type: (str) -> None ''' Remove a file from the ISO. This removes the data and the listing of the file from all contexts. Due to some complexities of the ISO format, removal of zero-byte files from all contexts does not automatically happen, so this method may need to be called on more than one facade for zero-byte files. Parameters: joliet_path - The path to the file to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_file(joliet_path=joliet_path) def rm_directory(self, joliet_path): # type: (str) -> None ''' Remove a directory from the ISO. This removes the directory from just the Joliet context. The directory must be empty. Parameters: joliet_path - The path to the directory to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_directory(joliet_path=joliet_path) def list_children(self, joliet_path): # type: (str) -> Generator ''' Generate a list of all of the file/directory objects in the specified location on the ISO. Parameters: joliet_path - The absolute path on the Joliet portion of the ISO to list the children for. Yields: Children of this path. Returns: Nothing. ''' return self.pycdlib_obj.list_children(joliet_path=joliet_path) def get_record(self, joliet_path): # type: (str) -> dr.DirectoryRecord ''' Get the directory record for a particular Joliet path. Parameters: joliet_path - The absolute path on the Joliet filesystem to get the record for. Returns: A dr.DirectoryRecord object that represents the path. ''' ret = self.pycdlib_obj.get_record(joliet_path=joliet_path) if not isinstance(ret, dr.DirectoryRecord): raise pycdlibexception.PyCdlibInternalError('Invalid return type from get_record') return ret def walk(self, joliet_path): # type: (str) -> Generator ''' Walk the entries on the Joliet portion of the ISO, starting at the given Joliet path. Similar to os.walk(), yield a 3-tuple of (path-to-here, dirlist, filelist) for each directory level. Parameters: joliet_path - The absolute Joliet path to the starting entry on the ISO. Yields: 3-tuples of (path-to-here, dirlist, filelist) Returns: Nothing. ''' return self.pycdlib_obj.walk(joliet_path=joliet_path) def open_file_from_iso(self, joliet_path): # type: (str) -> pycdlibio.PyCdlibIO ''' Open a file for reading in a context manager. This allows the user to operate on the file in user-defined chunks (utilizing the read() method of the returned context manager). Parameters: joliet_path - The absolute Joliet path to the file on the ISO. Returns: A PyCdlibIO object allowing access to the file. ''' return self.pycdlib_obj.open_file_from_iso(joliet_path=joliet_path) class PyCdlibRockRidge(object): ''' The class representing the PyCdlib Rock Ridge facade. ''' __slots__ = ('pycdlib_obj',) def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None if not pycdlib_obj.has_rock_ridge(): raise pycdlibexception.PyCdlibInvalidInput('Can only instantiate a Rock Ridge facade for a Rock Ridge ISO') self.pycdlib_obj = pycdlib_obj def get_file_from_iso(self, local_path, rr_path): # type: (str, str) -> None ''' Fetch a single file from the ISO via an absolute Rock Ridge path and write it out to a local file. Parameters: local_path - The local file to write to. rr_path - The absolute Rock Ridge path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso(local_path, rr_path=rr_path) def get_file_from_iso_fp(self, outfp, rr_path): # type: (BinaryIO, str) -> None ''' Fetch a single file from the ISO via an absolute Rock Ridge path and write it out to the file object. Parameters: outfp - The file object to write data to. joliet_path - The absolute Joliet path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso_fp(outfp, rr_path=rr_path) def _rr_path_to_iso_path(self, rr_path): # type: (str) -> str ''' An internal method to convert an ISO9660 path to a Rock Ridge one. This is accomplished by finding the Rock Ridge directory record, then reconstructing the ISO path by walking up to the root. Parameters: rr_path - The Rock Ridge path to generate an ISO9660 path for. Returns: The ISO9660 path corresponding to the Rock Ridge path. ''' if rr_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput("rr_path must start with '/'") record = self.pycdlib_obj._find_rr_record(utils.normpath(rr_path)) # pylint: disable=protected-access if record.is_root: iso_path = b'/' else: iso_path = b'' parent = record # type: Optional[dr.DirectoryRecord] while parent is not None: if not parent.is_root: iso_path = b'/' + parent.file_identifier() + iso_path parent = parent.parent return iso_path.decode('utf-8') def _rr_path_to_iso_path_and_rr_name(self, rr_path, is_dir): # type: (str, bool) -> Tuple[str, str] ''' An internal method to split a Rock Ridge absolute path into an absolute ISO9660 path and a Rock Ridge name. This is accomplished by finding the Rock Ridge directory record of the parent, then reconstructing the ISO parent path by walking up to the root. Parameters: rr_path - The absolute Rock Ridge path to generate for. is_dir - Whether this path is a directory or a file. Returns: A tuple where the first element is the absolute ISO9660 path of the parent, and the second element is the Rock Ridge name. ''' if rr_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput("rr_path must start with '/'") namesplit = utils.split_path(utils.normpath(rr_path)) rr_name = namesplit.pop() rr_parent_path = b'/' + b'/'.join(namesplit) parent_record = self.pycdlib_obj._find_rr_record(rr_parent_path) # pylint: disable=protected-access if parent_record.is_root: iso_parent_path = b'/' else: iso_parent_path = b'' parent = parent_record # type: Optional[dr.DirectoryRecord] while parent is not None: if not parent.is_root: iso_parent_path = b'/' + parent.file_identifier() + iso_parent_path parent = parent.parent if is_dir: iso_name = utils.mangle_dir_for_iso9660(rr_name.decode('utf-8'), self.pycdlib_obj.interchange_level) else: basename, ext = utils.mangle_file_for_iso9660(rr_name.decode('utf-8'), self.pycdlib_obj.interchange_level) iso_name = '.'.join([basename, ext]) iso_path = iso_parent_path.decode('utf-8') + '/' + iso_name return iso_path, rr_name.decode('utf-8') def add_fp(self, fp, length, rr_path, file_mode): # type: (BinaryIO, int, str, int) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the Rock Ridge context (and by extension, the ISO9660 context). For more control over which contexts a file shows up in, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Note that the caller must ensure that 'fp' remains open for the lifetime of the PyCdlib object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. To have PyCdlib manage this automatically, use 'add_file' instead. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. rr_path - The Rock Ridge absolute path to the file destination on the ISO. file_mode - The POSIX file_mode to apply to this file. If this is None (the default), the permissions from the original file are used. Returns: Nothing. ''' iso_path, rr_name = self._rr_path_to_iso_path_and_rr_name(rr_path, False) self.pycdlib_obj.add_fp(fp, length, iso_path, rr_name=rr_name, file_mode=file_mode) def add_file(self, filename, rr_path, file_mode): # type: (str, str, int) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the Rock Ridge context (and by extension, the ISO9660 context). For more control over which contexts in which a file shows up, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Parameters: filename - The filename to use for the data contents for the new file. rr_path - The Rock Ridge absolute path to the file destination on the ISO. file_mode - The POSIX file_mode to apply to this file. If this is None (the default), the permissions from the original file are used. Returns: Nothing. ''' iso_path, rr_name = self._rr_path_to_iso_path_and_rr_name(rr_path, False) self.pycdlib_obj.add_file(filename, iso_path, rr_name=rr_name, file_mode=file_mode) def add_directory(self, rr_path, file_mode): # type: (str, int) -> None ''' Add a directory to the Rock Ridge context (and by extension, the ISO9660 context). For more control over which contexts in which a directory shows up, use the regular PyCdlib object (not this facade). Parameters: rr_path - The Rock Ridge absolute path to use for the directory. file_mode - The POSIX file_mode to apply to this file. If this is None (the default), the permissions from the original file are used. Returns: Nothing. ''' iso_path, rr_name = self._rr_path_to_iso_path_and_rr_name(rr_path, True) self.pycdlib_obj.add_directory(iso_path=iso_path, rr_name=rr_name, file_mode=file_mode) def rm_file(self, rr_path): # type: (str) -> None ''' Remove a file from the ISO. This removes the data and the listing of the file from all contexts. Due to some complexities of the ISO format, removal of zero-byte files from all contexts does not automatically happen, so this method may need to be called on more than one facade for zero-byte files. Parameters: rr_path - The path to the file to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_file(self._rr_path_to_iso_path(rr_path)) def rm_directory(self, rr_path): # type: (str) -> None ''' Remove a directory from the ISO. This removes the directory from just the Rock Ridge context (and by extension, the ISO9660 context). The directory must be empty. Parameters: rr_path - The path to the directory to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_directory(self._rr_path_to_iso_path(rr_path)) def add_symlink(self, rr_symlink_path, rr_target_path): # type: (str, str) -> None ''' Add a symlink from rr_symlink_name to the rr_path. Parameters: rr_symlink_path - The absolute Rock Ridge path of the symlink itself on the ISO. rr_target_path - The Rock Ridge path of the entry on the ISO that the symlink points to. Returns: Nothing. ''' symlink_path, rr_symlink_name = self._rr_path_to_iso_path_and_rr_name(rr_symlink_path, False) self.pycdlib_obj.add_symlink(symlink_path, rr_symlink_name=rr_symlink_name, rr_path=rr_target_path) def list_children(self, rr_path): # type: (str) -> Generator ''' Generate a list of all of the file/directory objects in the specified location on the ISO. Parameters: rr_path - The absolute path on the ISO to list the children for. Yields: Children of this path. Returns: Nothing. ''' return self.pycdlib_obj.list_children(rr_path=rr_path) def get_record(self, rr_path): # type: (str) -> dr.DirectoryRecord ''' Get the directory record for a particular ISO path. Parameters: rr_path - The absolute path on the Rock Ridge filesystem to get the record for. Returns: A dr.DirectoryRecord object that represents the path. ''' ret = self.pycdlib_obj.get_record(rr_path=rr_path) if not isinstance(ret, dr.DirectoryRecord): raise pycdlibexception.PyCdlibInternalError('Invalid return type from get_record') return ret def walk(self, rr_path): # type: (str) -> Generator ''' Walk the entries on the ISO, starting at the given Rock Ridge path. Similar to os.walk(), yield a 3-tuple of (path-to-here, dirlist, filelist) for each directory level. Parameters: rr_path - The absolute Rock Ridge path to the starting entry on the ISO. Yields: 3-tuples of (path-to-here, dirlist, filelist) Returns: Nothing. ''' return self.pycdlib_obj.walk(rr_path=rr_path) def open_file_from_iso(self, rr_path): # type: (str) -> pycdlibio.PyCdlibIO ''' Open a file for reading in a context manager. This allows the user to operate on the file in user-defined chunks (utilizing the read() method of the returned context manager). Parameters: rr_path - The absolute Rock Ridge path to the file on the ISO. Returns: A PyCdlibIO object allowing access to the file. ''' return self.pycdlib_obj.open_file_from_iso(rr_path=rr_path) class PyCdlibUDF(object): ''' The class representing the PyCdlib UDF facade. ''' __slots__ = ('pycdlib_obj',) def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None if not pycdlib_obj.has_udf(): raise pycdlibexception.PyCdlibInvalidInput('Can only instantiate a UDF facade for a UDF ISO') self.pycdlib_obj = pycdlib_obj def get_file_from_iso(self, local_path, udf_path): # type: (str, str) -> None ''' Fetch a single file from the ISO via an absolute UDF path and write it out to a local file. Parameters: local_path - The local file to write to. udf_path - The absolute UDF path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso(local_path, udf_path=udf_path) def get_file_from_iso_fp(self, outfp, udf_path): # type: (BinaryIO, str) -> None ''' Fetch a single file from the ISO via an absolute UDF path and write it out to the file object. Parameters: outfp - The file object to write data to. udf_path - The absolute UDF path to lookup on the ISO. Returns: Nothing. ''' self.pycdlib_obj.get_file_from_iso_fp(outfp, udf_path=udf_path) def add_fp(self, fp, length, udf_path): # type: (BinaryIO, int, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the UDF context. For more control over which contexts a file shows up in, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Note that the caller must ensure that 'fp' remains open for the lifetime of the PyCdlib object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. To have PyCdlib manage this automatically, use 'add_file' instead. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. udf_path - The UDF absolute path to the file destination on the ISO. Returns: Nothing. ''' self.pycdlib_obj.add_fp(fp, length, udf_path=udf_path) def add_file(self, filename, udf_path): # type: (str, str) -> None ''' Add a file to the ISO. While using this facade, a file will only be added to the UDF context. For more control over which contexts in which a file shows up, use the 'add_hard_link' API and/or use the regular PyCdlib object (not this facade). Parameters: filename - The filename to use for the data contents for the new file. udf_path - The UDF absolute path to the file destination on the ISO. Returns: Nothing. ''' self.pycdlib_obj.add_file(filename, udf_path=udf_path) def add_directory(self, udf_path): # type: (str) -> None ''' Add a directory to the UDF context. For more control over which contexts in which a directory shows up, use the regular PyCdlib object (not this facade). Parameters: udf_path - The UDF absolute path to use for the directory. Returns: Nothing. ''' self.pycdlib_obj.add_directory(udf_path=udf_path) def rm_file(self, udf_path): # type: (str) -> None ''' Remove a file from the ISO. This removes the data and the listing of the file from all contexts. Due to some complexities of the ISO format, removal of zero-byte files from all contexts does not automatically happen, so this method may need to be called on more than one facade for zero-byte files. Parameters: udf_path - The path to the file to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_file(udf_path=udf_path) def rm_directory(self, udf_path): # type: (str) -> None ''' Remove a directory from the ISO. This removes the directory from just the UDF context. The directory must be empty. Parameters: udf_path - The path to the directory to remove. Returns: Nothing. ''' self.pycdlib_obj.rm_directory(udf_path=udf_path) def add_symlink(self, udf_symlink_path, udf_target): # type: (str, str) -> None ''' Add a symlink from udf_symlink_path to udf_target. Parameters: udf_symlink_path - The UDF path of the symlink itself on the ISO. udf_target - The UDF name of the entry on the ISO that the symlink points to. Returns: Nothing. ''' self.pycdlib_obj.add_symlink(udf_symlink_path=udf_symlink_path, udf_target=udf_target) def list_children(self, udf_path): # type: (str) -> Generator ''' Generate a list of all of the file/directory objects in the specified location on the ISO. Parameters: udf_path - The absolute path on the ISO to list the children for. Yields: Children of this path. Returns: Nothing. ''' return self.pycdlib_obj.list_children(udf_path=udf_path) def get_record(self, udf_path): # type: (str) -> udfmod.UDFFileEntry ''' Get the directory record for a particular UDF path. Parameters: udf_path - The absolute path on the UDF filesystem to get the record for. Returns: A udf.UDFFileEntry object that represents the path. ''' ret = self.pycdlib_obj.get_record(udf_path=udf_path) if not isinstance(ret, udfmod.UDFFileEntry): raise pycdlibexception.PyCdlibInternalError('Invalid return type from get_record') return ret def walk(self, udf_path): # type: (str) -> Generator ''' Walk the entries on the ISO, starting at the given UDF path. Similar to os.walk(), yield a 3-tuple of (path-to-here, dirlist, filelist) for each directory level. Parameters: udf_path - The absolute UDF path to the starting entry on the ISO. Yields: 3-tuples of (path-to-here, dirlist, filelist) Returns: Nothing. ''' return self.pycdlib_obj.walk(udf_path=udf_path) def open_file_from_iso(self, udf_path): # type: (str) -> pycdlibio.PyCdlibIO ''' Open a file for reading in a context manager. This allows the user to operate on the file in user-defined chunks (utilizing the read() method of the returned context manager). Parameters: udf_path - The absolute UDF path to the file on the ISO. Returns: A PyCdlibIO object allowing access to the file. ''' return self.pycdlib_obj.open_file_from_iso(udf_path=udf_path) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600482001.0847325 pycdlib-1.11.0/pycdlib/headervd.py0000664000175000017500000016157500000000000021604 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Implementation of header Volume Descriptors for Ecma-119/ISO9660. ''' from __future__ import absolute_import import struct import time from pycdlib import dates from pycdlib import dr from pycdlib import pycdlibexception from pycdlib import rockridge from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import List, Tuple # NOQA pylint: disable=unused-import VOLUME_DESCRIPTOR_TYPE_BOOT_RECORD = 0 VOLUME_DESCRIPTOR_TYPE_PRIMARY = 1 VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY = 2 VOLUME_DESCRIPTOR_TYPE_VOLUME_PARTITION = 3 VOLUME_DESCRIPTOR_TYPE_SET_TERMINATOR = 255 allzero = b'\x00' * 2048 class PrimaryOrSupplementaryVD(object): ''' A class representing a Primary or Supplementary Volume Descriptor on this ISO. These are the first things on the ISO that are parsed, and contain all of the basic information about the ISO. ''' __slots__ = ('rr_ce_blocks', 'system_identifier', 'volume_identifier', 'path_table_location_le', 'optional_path_table_location_le', 'path_table_location_be', 'optional_path_table_location_be', 'volume_set_identifier', 'copyright_file_identifier', 'abstract_file_identifier', 'bibliographic_file_identifier', 'file_structure_version', 'application_use', 'set_size', 'publisher_identifier', 'preparer_identifier', 'application_identifier', 'volume_creation_date', 'volume_modification_date', 'volume_expiration_date', 'volume_effective_date', '_vd_type', 'escape_sequences', 'flags', 'version', '_initialized', 'space_size', 'log_block_size', 'root_dir_record', 'path_tbl_size', 'path_table_num_extents', 'seqnum', 'new_extent_loc', 'orig_extent_loc') FMT = ' None self._initialized = False self.space_size = 0 self.log_block_size = 0 self.root_dir_record = dr.DirectoryRecord() self.path_tbl_size = 0 self.path_table_num_extents = 0 self.seqnum = 0 self.new_extent_loc = -1 # Only used for PVD self.rr_ce_blocks = [] # type: List[rockridge.RockRidgeContinuationBlock] self._vd_type = vd_type def parse(self, vd, extent_loc): # type: (bytes, int) -> None ''' Parse a Volume Descriptor out of a string. Parameters: vd - The string containing the Volume Descriptor. extent_loc - The location on the ISO of this Volume Descriptor. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Primary Volume Descriptor is already initialized') ################ PVD VERSION ###################### (descriptor_type, identifier, self.version, self.flags, self.system_identifier, self.volume_identifier, unused1, space_size_le, space_size_be, self.escape_sequences, set_size_le, set_size_be, seqnum_le, seqnum_be, logical_block_size_le, logical_block_size_be, path_table_size_le, path_table_size_be, self.path_table_location_le, self.optional_path_table_location_le, self.path_table_location_be, self.optional_path_table_location_be, root_dir_record, self.volume_set_identifier, pub_ident_str, prepare_ident_str, app_ident_str, self.copyright_file_identifier, self.abstract_file_identifier, self.bibliographic_file_identifier, vol_create_date_str, vol_mod_date_str, vol_expire_date_str, vol_effective_date_str, self.file_structure_version, unused2, self.application_use, zero_unused) = struct.unpack_from(self.FMT, vd, 0) # According to Ecma-119, 8.4.1, the primary volume descriptor type # should be 1. if descriptor_type != self._vd_type: raise pycdlibexception.PyCdlibInvalidISO('Invalid volume descriptor') # According to Ecma-119, 8.4.2, the identifier should be 'CD001'. if identifier != b'CD001': raise pycdlibexception.PyCdlibInvalidISO('invalid CD isoIdentification') # According to Ecma-119, 8.4.3, the version should be 1 (or 2 for # ISO9660:1999) expected_versions = [1] if self._vd_type == VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY: expected_versions.append(2) if self.version not in expected_versions: raise pycdlibexception.PyCdlibInvalidISO('Invalid volume descriptor version %d' % (self.version)) # According to Ecma-119, 8.4.4, the first flags field should be 0 for a Primary. if self._vd_type == VOLUME_DESCRIPTOR_TYPE_PRIMARY and self.flags != 0: raise pycdlibexception.PyCdlibInvalidISO('PVD flags field is not zero') # According to Ecma-119, 8.4.5, the first unused field (after the # system identifier and volume identifier) should be 0. if unused1 != 0: raise pycdlibexception.PyCdlibInvalidISO('data in 2nd unused field not zero') # According to Ecma-119, 8.4.9, the escape sequences for a PVD should # be 32 zero-bytes. However, we have seen ISOs in the wild (Fantastic # Night Dreams - Cotton Original (Japan).cue from the psx redump # collection) that don't have this set to 0, so allow anything here. # According to Ecma-119, 8.4.30, the file structure version should be 1. # However, we have seen ISOs in the wild that that don't have this # properly set to one. In those cases, forcibly set it to one and let # it pass. if self._vd_type == VOLUME_DESCRIPTOR_TYPE_PRIMARY: if self.file_structure_version != 1: self.file_structure_version = 1 elif self._vd_type == VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY: if self.file_structure_version not in (1, 2): raise pycdlibexception.PyCdlibInvalidISO('File structure version expected to be 1') # According to Ecma-119, 8.4.31, the second unused field should be 0. if unused2 != 0: raise pycdlibexception.PyCdlibInvalidISO('data in 2nd unused field not zero') # According to Ecma-119, the last 653 bytes of the VD should be all 0. # However, we have seen ISOs in the wild that do not follow this, so # relax the check. # Check to make sure that the little-endian and big-endian versions # of the parsed data agree with each other. if space_size_le != utils.swab_32bit(space_size_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian space size disagree') self.space_size = space_size_le if set_size_le != utils.swab_16bit(set_size_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian set size disagree') self.set_size = set_size_le if seqnum_le != utils.swab_16bit(seqnum_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian seqnum disagree') self.seqnum = seqnum_le if logical_block_size_le != utils.swab_16bit(logical_block_size_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian logical block size disagree') self.log_block_size = logical_block_size_le if path_table_size_le != utils.swab_32bit(path_table_size_be): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian path table size disagree') self.path_tbl_size = path_table_size_le self.path_table_num_extents = utils.ceiling_div(self.path_tbl_size, 4096) * 2 self.path_table_location_be = utils.swab_32bit(self.path_table_location_be) self.publisher_identifier = FileOrTextIdentifier() self.publisher_identifier.parse(pub_ident_str) self.preparer_identifier = FileOrTextIdentifier() self.preparer_identifier.parse(prepare_ident_str) self.application_identifier = FileOrTextIdentifier() self.application_identifier.parse(app_ident_str) self.volume_creation_date = dates.VolumeDescriptorDate() self.volume_creation_date.parse(vol_create_date_str) self.volume_modification_date = dates.VolumeDescriptorDate() self.volume_modification_date.parse(vol_mod_date_str) self.volume_expiration_date = dates.VolumeDescriptorDate() self.volume_expiration_date.parse(vol_expire_date_str) self.volume_effective_date = dates.VolumeDescriptorDate() self.volume_effective_date.parse(vol_effective_date_str) self.root_dir_record.parse(self, root_dir_record, None) self.orig_extent_loc = extent_loc self._initialized = True def new(self, flags, sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa, version, escape_sequence): # type: (int, bytes, bytes, int, int, int, bytes, bytes, bytes, bytes, bytes, bytes, bytes, float, bytes, bool, int, bytes) -> None ''' Create a new Volume Descriptor. Parameters: flags - The flags to set for this Volume Descriptor (must be 0 for a Primay Volume Descriptor). sys_ident - The system identification string to use on the new ISO. vol_ident - The volume identification string to use on the new ISO. set_size - The size of the set of ISOs this ISO is a part of. seqnum - The sequence number of the set of this ISO. log_block_size - The logical block size to use for the ISO. While ISO9660 technically supports sizes other than 2048 (the default), this almost certainly doesn't work. vol_set_ident - The volume set identification string to use on the new ISO. pub_ident_str - The publisher identification string to use on the new ISO. preparer_ident_str - The preparer identification string to use on the new ISO. app_ident_str - The application identification string to use on the new ISO. copyright_file - The name of a file at the root of the ISO to use as the copyright file. abstract_file - The name of a file at the root of the ISO to use as the abstract file. bibli_file - The name of a file at the root of the ISO to use as the bibliographic file. vol_expire_date - The date that this ISO will expire at. app_use - Arbitrary data that the application can stuff into the primary volume descriptor of this ISO. xa - Whether to embed XA data into the volume descriptor. version - What version to assign to the header (ignored). escape_sequence - The escape sequence to assign to this volume descriptor (must be empty for a PVD, or empty or a valid Joliet escape sequence for an SVD). Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Primary Volume Descriptor is already initialized') encoding = 'ascii' if self._vd_type == VOLUME_DESCRIPTOR_TYPE_PRIMARY: if flags != 0: raise pycdlibexception.PyCdlibInvalidInput('Non-zero flags not allowed for a PVD') if escape_sequence != b'': raise pycdlibexception.PyCdlibInvalidInput('Non-empty escape sequence not allowed for a PVD') if version != 1: raise pycdlibexception.PyCdlibInvalidInput('Only version 1 supported for a PVD') self.escape_sequences = b'\x00' * 32 elif self._vd_type == VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY: if version not in (1, 2): raise pycdlibexception.PyCdlibInvalidInput('Only version 1 and version 2 supported for a Supplementary Volume Descriptor') if escape_sequence in (b'%/@', b'%/C', b'%/E'): encoding = 'utf-16_be' self.escape_sequences = escape_sequence.ljust(32, b'\x00') self.file_structure_version = version self.version = version self.flags = 0 if len(sys_ident) > 32: raise pycdlibexception.PyCdlibInvalidInput('The system identifer has a maximum length of 32') self.system_identifier = utils.encode_space_pad(sys_ident, 32, encoding) if len(vol_ident) > 32: raise pycdlibexception.PyCdlibInvalidInput('The volume identifier has a maximum length of 32') self.volume_identifier = utils.encode_space_pad(vol_ident, 32, encoding) # The space_size is the number of extents (2048-byte blocks) in the # ISO. We know we will at least have the system area (16 extents), # and this VD (1 extent) to start with; the rest will be added later. self.space_size = 17 self.set_size = set_size if seqnum > set_size: raise pycdlibexception.PyCdlibInvalidInput('Sequence number must be less than or equal to set size') self.seqnum = seqnum self.log_block_size = log_block_size # The path table size is in bytes, and is always at least 10 bytes # (for the root directory record). self.path_tbl_size = 10 self.path_table_num_extents = utils.ceiling_div(self.path_tbl_size, 4096) * 2 # By default the Little Endian Path Table record starts at extent 19 # (right after the Volume Terminator). self.path_table_location_le = 19 # By default the Big Endian Path Table record starts at extent 21 # (two extents after the Little Endian Path Table Record). self.path_table_location_be = 21 # FIXME: we don't support the optional path table location right now self.optional_path_table_location_le = 0 self.optional_path_table_location_be = 0 self.root_dir_record.new_root(self, seqnum, self.log_block_size) if len(vol_set_ident) > 128: raise pycdlibexception.PyCdlibInvalidInput('The maximum length for the volume set identifier is 128') self.volume_set_identifier = utils.encode_space_pad(vol_set_ident, 128, encoding) self.publisher_identifier = FileOrTextIdentifier() self.publisher_identifier.new(utils.encode_space_pad(pub_ident_str, 128, encoding)) self.preparer_identifier = FileOrTextIdentifier() self.preparer_identifier.new(utils.encode_space_pad(preparer_ident_str, 128, encoding)) self.application_identifier = FileOrTextIdentifier() self.application_identifier.new(utils.encode_space_pad(app_ident_str, 128, encoding)) self.copyright_file_identifier = utils.encode_space_pad(copyright_file, 37, encoding) self.abstract_file_identifier = utils.encode_space_pad(abstract_file, 37, encoding) self.bibliographic_file_identifier = utils.encode_space_pad(bibli_file, 37, encoding) now = time.time() self.volume_creation_date = dates.VolumeDescriptorDate() self.volume_creation_date.new(now) # We make a valid volume modification date here, but it will get # overwritten during record(). self.volume_modification_date = dates.VolumeDescriptorDate() self.volume_modification_date.new(now) self.volume_expiration_date = dates.VolumeDescriptorDate() self.volume_expiration_date.new(vol_expire_date) self.volume_effective_date = dates.VolumeDescriptorDate() self.volume_effective_date.new(now) if xa: if len(app_use) > 141: raise pycdlibexception.PyCdlibInvalidInput('Cannot have XA and an app_use of > 140 bytes') self.application_use = app_use.ljust(141, b' ') self.application_use += b'CD-XA001' + b'\x00' * 18 self.application_use = self.application_use.ljust(512, b' ') else: if len(app_use) > 512: raise pycdlibexception.PyCdlibInvalidInput('The maximum length for the application use is 512') self.application_use = app_use.ljust(512, b' ') self._initialized = True def copy(self, orig): # type: (PrimaryOrSupplementaryVD) -> None ''' Populate and initialize this VD object from the contents of an old VD. Parameters: orig_pvd - The original VD to copy data from. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is already initialized') self.version = orig.version self.flags = orig.flags self.system_identifier = orig.system_identifier self.volume_identifier = orig.volume_identifier self.escape_sequences = orig.escape_sequences self.space_size = orig.space_size self.set_size = orig.set_size self.seqnum = orig.seqnum self.log_block_size = orig.log_block_size self.path_tbl_size = orig.path_tbl_size self.path_table_location_le = orig.path_table_location_le self.optional_path_table_location_le = orig.optional_path_table_location_le self.path_table_location_be = orig.path_table_location_be self.optional_path_table_location_be = orig.optional_path_table_location_be # Root dir record is a DirectoryRecord object, and we want this copy to hold # onto exactly the same reference as the original self.root_dir_record = orig.root_dir_record self.volume_set_identifier = orig.volume_set_identifier # publisher_identifier is a FileOrTextIdentifier object, and we want this copy to # hold onto exactly the same reference as the original self.publisher_identifier = orig.publisher_identifier # preparer_identifier is a FileOrTextIdentifier object, and we want this copy to # hold onto exactly the same reference as the original self.preparer_identifier = orig.preparer_identifier # application_identifier is a FileOrTextIdentifier object, and we want this copy to # hold onto exactly the same reference as the original self.application_identifier = orig.application_identifier self.copyright_file_identifier = orig.copyright_file_identifier self.abstract_file_identifier = orig.abstract_file_identifier self.bibliographic_file_identifier = orig.bibliographic_file_identifier # volume_creation_date is a VolumeDescriptorDate object, and we want this copy to # hold onto exactly the same reference as the original self.volume_creation_date = orig.volume_creation_date # volume_expiration_date is a VolumeDescriptorDate object, and we want this copy to # hold onto exactly the same reference as the original self.volume_expiration_date = orig.volume_expiration_date # volume_effective_date is a VolumeDescriptorDate object, and we want this copy to # hold onto exactly the same reference as the original self.volume_effective_date = orig.volume_effective_date self.file_structure_version = orig.file_structure_version self.application_use = orig.application_use self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this Volume Descriptor. Parameters: None. Returns: A string representing this Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') vol_mod_date = dates.VolumeDescriptorDate() vol_mod_date.new(time.time()) return struct.pack(self.FMT, self._vd_type, b'CD001', self.version, self.flags, self.system_identifier, self.volume_identifier, 0, self.space_size, utils.swab_32bit(self.space_size), self.escape_sequences, self.set_size, utils.swab_16bit(self.set_size), self.seqnum, utils.swab_16bit(self.seqnum), self.log_block_size, utils.swab_16bit(self.log_block_size), self.path_tbl_size, utils.swab_32bit(self.path_tbl_size), self.path_table_location_le, self.optional_path_table_location_le, utils.swab_32bit(self.path_table_location_be), self.optional_path_table_location_be, self.root_dir_record.record(), self.volume_set_identifier, self.publisher_identifier.record(), self.preparer_identifier.record(), self.application_identifier.record(), self.copyright_file_identifier, self.abstract_file_identifier, self.bibliographic_file_identifier, self.volume_creation_date.record(), vol_mod_date.record(), self.volume_expiration_date.record(), self.volume_effective_date.record(), self.file_structure_version, 0, self.application_use, b'\x00' * 653) def track_rr_ce_entry(self, extent, offset, length): # type: (int, int, int) -> rockridge.RockRidgeContinuationBlock ''' Start tracking a new Rock Ridge Continuation Entry entry in this Volume Descriptor, at the extent, offset, and length provided. Since Rock Ridge Continuation Blocks are shared across multiple Rock Ridge Directory Records, the most logical place to track them is in the PVD. This method is expected to be used during parse time, when an extent, offset and length are already assigned to the entry. Parameters: extent - The extent that this Continuation Entry lives at. offset - The offset within the extent that this Continuation Entry lives at. length - The length of this Continuation Entry. Returns: The object representing the block in which the Continuation Entry was placed in. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Primary Volume Descriptor is not initialized') for block in self.rr_ce_blocks: if block.extent_location() == extent: break else: # We didn't find it in the list, add it block = rockridge.RockRidgeContinuationBlock(extent, self.log_block_size) self.rr_ce_blocks.append(block) block.track_entry(offset, length) return block def add_rr_ce_entry(self, length): # type: (int) -> Tuple[bool, rockridge.RockRidgeContinuationBlock, int] ''' Add a new Rock Ridge Continuation Entry to this PVD; see track_rr_ce_entry() above for why we track these in the PVD. This method is used to add a new Continuation Entry anywhere it fits in the list of Continuation Blocks. If it doesn't fit in any of the existing blocks, a new block for it is allocated. Parameters: length - The length of the Continuation Entry that should be added. Returns: A 3-tuple consisting of whether we added a new block, the object representing the block that this entry was added to, and the offset within the block that the entry was added to. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Primary Volume Descriptor is not initialized') added_block = False for block in self.rr_ce_blocks: offset = block.add_entry(length) if offset is not None: break else: # We didn't find a block this would fit in; add one. block = rockridge.RockRidgeContinuationBlock(0, self.log_block_size) self.rr_ce_blocks.append(block) offset = block.add_entry(length) added_block = True return (added_block, block, offset) def clear_rr_ce_entries(self): # type: () -> None ''' Clear out all of the extent locations of all Rock Ridge Continuation Entries that the PVD is tracking. This can be used to reset all data before assigning new data. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Primary Volume Descriptor is not initialized') for block in self.rr_ce_blocks: block.set_extent_location(-1) def path_table_size(self): # type: () -> int ''' Get the path table size of the Volume Descriptor. Parameters: None. Returns: Path table size in bytes. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') return self.path_tbl_size def add_to_space_size(self, addition_bytes): # type: (int) -> None ''' Add bytes to the space size tracked by this Volume Descriptor. Parameters: addition_bytes - The number of bytes to add to the space size. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') # The 'addition' parameter is expected to be in bytes, but the space # size we track is in extents. Round up to the next extent. self.space_size += utils.ceiling_div(addition_bytes, self.log_block_size) def remove_from_space_size(self, removal_bytes): # type: (int) -> None ''' Remove bytes from the volume descriptor. Parameters: removal_bytes - The number of bytes to remove. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') # The 'removal' parameter is expected to be in bytes, but the space # size we track is in extents. Round up to the next extent. self.space_size -= utils.ceiling_div(removal_bytes, self.log_block_size) def root_directory_record(self): # type: () -> dr.DirectoryRecord ''' Get a handle to this Volume Descriptor's root directory record. Parameters: None. Returns: DirectoryRecord object representing this Volume Descriptor's root directory record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') return self.root_dir_record def logical_block_size(self): # type: () -> int ''' Get this Volume Descriptor's logical block size. Parameters: None. Returns: Size of this Volume Descriptor's logical block size in bytes. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') return self.log_block_size def add_to_ptr_size(self, ptr_size): # type: (int) -> bool ''' Add the space for a path table record to the volume descriptor. Parameters: ptr_size - The length of the Path Table Record being added to this Volume Descriptor. Returns: True if extents need to be added to the Volume Descriptor, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') # First add to the path table size. self.path_tbl_size += ptr_size if (utils.ceiling_div(self.path_tbl_size, 4096) * 2) > self.path_table_num_extents: # If we overflowed the path table size, then we need to update the # space size. Since we always add two extents for the little and # two for the big, add four total extents. The locations will be # fixed up during reshuffle_extents. self.path_table_num_extents += 2 return True return False def remove_from_ptr_size(self, ptr_size): # type: (int) -> bool ''' Remove the space for a path table record from the volume descriptor. Parameters: ptr_size - The length of the Path Table Record being removed from this Volume Descriptor. Returns: True if extents need to be removed from the Volume Descriptor, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') # Next remove from the Path Table Record size. self.path_tbl_size -= ptr_size new_extents = utils.ceiling_div(self.path_tbl_size, 4096) * 2 need_remove_extents = False if new_extents > self.path_table_num_extents: # This should never happen. raise pycdlibexception.PyCdlibInvalidInput('This should never happen') if new_extents < self.path_table_num_extents: self.path_table_num_extents -= 2 need_remove_extents = True return need_remove_extents def sequence_number(self): # type: () -> int ''' Get this Volume Descriptor's sequence number. Parameters: None. Returns: This Volume Descriptor's sequence number. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') return self.seqnum def copy_sizes(self, othervd): # type: (PrimaryOrSupplementaryVD) -> None ''' Copy the path_tbl_size, path_table_num_extents, and space_size from another volume descriptor. Parameters: othervd - The other volume descriptor to copy from. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.space_size = othervd.space_size self.path_tbl_size = othervd.path_tbl_size self.path_table_num_extents = othervd.path_table_num_extents def extent_location(self): # type: () -> int ''' Get this Volume Descriptor's extent location. Parameters: None. Returns: Integer of this Volume Descriptor's extent location. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this PVD/SVD. Parameters: extent - The new extent location to set for this PVD/SVD. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent def is_pvd(self): # type: () -> bool ''' Determine whether this Volume Descriptor is a Primary one. Parameters: None. Returns: True if this Volume Descriptor is Primary, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') return self._vd_type == VOLUME_DESCRIPTOR_TYPE_PRIMARY def __ne__(self, other): return self.version != other.version or self.flags != other.flags or self.system_identifier != other.system_identifier or self.volume_identifier != other.volume_identifier or self.escape_sequences != other.escape_sequences or self.space_size != other.space_size or self.set_size != other.set_size or self.seqnum != other.seqnum or self.log_block_size != other.log_block_size or self.path_tbl_size != other.path_tbl_size or self.path_table_location_le != other.path_table_location_le or self.optional_path_table_location_le != other.optional_path_table_location_le or self.path_table_location_be != other.path_table_location_be or self.optional_path_table_location_be != other.optional_path_table_location_be or self.root_dir_record != other.root_dir_record or self.volume_set_identifier != other.volume_set_identifier or self.publisher_identifier != other.publisher_identifier or self.preparer_identifier != other.preparer_identifier or self.application_identifier != other.application_identifier or self.copyright_file_identifier != other.copyright_file_identifier or self.abstract_file_identifier != other.abstract_file_identifier or self.bibliographic_file_identifier != other.bibliographic_file_identifier or self.volume_creation_date != other.volume_creation_date or self.volume_modification_date != other.volume_modification_date or self.volume_expiration_date != other.volume_expiration_date or self.volume_effective_date != other.volume_effective_date or self.file_structure_version != other.file_structure_version or self.application_use != other.application_use def pvd_factory(sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa): # type: (bytes, bytes, int, int, int, bytes, bytes, bytes, bytes, bytes, bytes, bytes, float, bytes, bool) -> PrimaryOrSupplementaryVD ''' An internal function to create a Primary Volume Descriptor. Parameters: sys_ident - The system identification string to use on the new ISO. vol_ident - The volume identification string to use on the new ISO. set_size - The size of the set of ISOs this ISO is a part of. seqnum - The sequence number of the set of this ISO. log_block_size - The logical block size to use for the ISO. While ISO9660 technically supports sizes other than 2048 (the default), this almost certainly doesn't work. vol_set_ident - The volume set identification string to use on the new ISO. pub_ident_str - The publisher identification string to use on the new ISO. preparer_ident_str - The preparer identification string to use on the new ISO. app_ident_str - The application identification string to use on the new ISO. copyright_file - The name of a file at the root of the ISO to use as the copyright file. abstract_file - The name of a file at the root of the ISO to use as the abstract file. bibli_file - The name of a file at the root of the ISO to use as the bibliographic file. vol_expire_date - The date that this ISO will expire at. app_use - Arbitrary data that the application can stuff into the primary volume descriptor of this ISO. xa - Whether to add the ISO9660 Extended Attribute extensions to this ISO. The default is False. Returns: The newly created Primary Volume Descriptor. ''' pvd = PrimaryOrSupplementaryVD(VOLUME_DESCRIPTOR_TYPE_PRIMARY) pvd.new(0, sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa, 1, b'') return pvd def enhanced_vd_factory(sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa): # type: (bytes, bytes, int, int, int, bytes, bytes, bytes, bytes, bytes, bytes, bytes, float, bytes, bool) -> PrimaryOrSupplementaryVD ''' An internal function to create an Enhanced Volume Descriptor for ISO 1999. Parameters: sys_ident - The system identification string to use on the new ISO. vol_ident - The volume identification string to use on the new ISO. set_size - The size of the set of ISOs this ISO is a part of. seqnum - The sequence number of the set of this ISO. log_block_size - The logical block size to use for the ISO. While ISO9660 technically supports sizes other than 2048 (the default), this almost certainly doesn't work. vol_set_ident - The volume set identification string to use on the new ISO. pub_ident_str - The publisher identification string to use on the new ISO. preparer_ident_str - The preparer identification string to use on the new ISO. app_ident_str - The application identification string to use on the new ISO. copyright_file - The name of a file at the root of the ISO to use as the copyright file. abstract_file - The name of a file at the root of the ISO to use as the abstract file. bibli_file - The name of a file at the root of the ISO to use as the bibliographic file. vol_expire_date - The date that this ISO will expire at. app_use - Arbitrary data that the application can stuff into the primary volume descriptor of this ISO. xa - Whether to add the ISO9660 Extended Attribute extensions to this ISO. The default is False. Returns: The newly created Enhanced Volume Descriptor. ''' svd = PrimaryOrSupplementaryVD(VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY) svd.new(0, sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa, 2, b'') return svd def joliet_vd_factory(joliet, sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa): # type: (int, bytes, bytes, int, int, int, bytes, bytes, bytes, bytes, bytes, bytes, bytes, float, bytes, bool) -> PrimaryOrSupplementaryVD ''' An internal function to create an Joliet Volume Descriptor. Parameters: joliet - The joliet version to use, one of 1, 2, or 3. sys_ident - The system identification string to use on the new ISO. vol_ident - The volume identification string to use on the new ISO. set_size - The size of the set of ISOs this ISO is a part of. seqnum - The sequence number of the set of this ISO. log_block_size - The logical block size to use for the ISO. While ISO9660 technically supports sizes other than 2048 (the default), this almost certainly doesn't work. vol_set_ident - The volume set identification string to use on the new ISO. pub_ident_str - The publisher identification string to use on the new ISO. preparer_ident_str - The preparer identification string to use on the new ISO. app_ident_str - The application identification string to use on the new ISO. copyright_file - The name of a file at the root of the ISO to use as the copyright file. abstract_file - The name of a file at the root of the ISO to use as the abstract file. bibli_file - The name of a file at the root of the ISO to use as the bibliographic file. vol_expire_date - The date that this ISO will expire at. app_use - Arbitrary data that the application can stuff into the primary volume descriptor of this ISO. xa - Whether to add the ISO9660 Extended Attribute extensions to this ISO. The default is False. Returns: The newly created Joliet Volume Descriptor. ''' if joliet == 1: escape_sequence = b'%/@' elif joliet == 2: escape_sequence = b'%/C' elif joliet == 3: escape_sequence = b'%/E' else: raise pycdlibexception.PyCdlibInvalidInput('Invalid Joliet level; must be 1, 2, or 3') svd = PrimaryOrSupplementaryVD(VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY) svd.new(0, sys_ident, vol_ident, set_size, seqnum, log_block_size, vol_set_ident, pub_ident_str, preparer_ident_str, app_ident_str, copyright_file, abstract_file, bibli_file, vol_expire_date, app_use, xa, 1, escape_sequence) return svd class FileOrTextIdentifier(object): ''' A class to represent a file or text identifier as specified in Ecma-119 section 8.4.20 (Primary Volume Descriptor Publisher Identifier), section 8.4.21 (Primary Volume Descriptor Data Preparer Identifier), and section 8.4.22 (Primary Volume Descriptor Application Identifier). This identifier can either be a text string or the name of a file. If it is a file, then the first byte will be 0x5f, the file should exist in the root directory record, and the file should be ISO level 1 interchange compliant (no more than 8 characters for the name and 3 characters for the extension). There are two main ways to use this class: either to instantiate and then parse a string to fill in the fields (the parse() method), or to create a new entry with a text string and whether this is a filename or not (the new() method). ''' __slots__ = ('_initialized', 'text') def __init__(self): # type: () -> None self._initialized = False def parse(self, ident_str): # type: (bytes) -> None ''' Parse a file or text identifier out of a string. Parameters: ident_str - The string to parse the file or text identifier from. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This File or Text identifier is already initialized') self.text = ident_str # FIXME: we do not support a file identifier here. In the future, we # might want to implement this. self._initialized = True def new(self, text): # type: (bytes) -> None ''' Create a new file or text identifier. Parameters: text - The text to store into the identifier. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This File or Text identifier is already initialized') if len(text) != 128: raise pycdlibexception.PyCdlibInvalidInput('Length of text must be 128') self.text = text self._initialized = True def record(self): # type: () -> bytes ''' Returns the file or text identification string suitable for recording. Parameters: None. Returns: The text representing this identifier. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This File or Text identifier is not initialized') return self.text def __eq__(self, other): # type: (object) -> bool if not isinstance(other, FileOrTextIdentifier): return NotImplemented return self.text == other.text def __ne__(self, other): # type: (object) -> bool return not self.__eq__(other) class VolumeDescriptorSetTerminator(object): ''' A class that represents a Volume Descriptor Set Terminator. The VDST signals the end of volume descriptors on the ISO. ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc') FMT = '=B5sB2041s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, vd, extent_loc): # type: (bytes, int) -> None ''' Parse a Volume Descriptor Set Terminator out of a string. Parameters: vd - The string to parse. extent_loc - The extent this VDST is currently located at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Volume Descriptor Set Terminator already initialized') (descriptor_type, identifier, version, zero_unused) = struct.unpack_from(self.FMT, vd, 0) # According to Ecma-119, 8.3.1, the volume descriptor set terminator # type should be 255 if descriptor_type != VOLUME_DESCRIPTOR_TYPE_SET_TERMINATOR: raise pycdlibexception.PyCdlibInvalidISO('Invalid VDST descriptor type') # According to Ecma-119, 8.3.2, the identifier should be 'CD001' if identifier != b'CD001': raise pycdlibexception.PyCdlibInvalidISO('Invalid VDST identifier') # According to Ecma-119, 8.3.3, the version should be 1 if version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid VDST version') # According to Ecma-119, 8.3.4, the rest of the terminator should be 0; # however, we have seen ISOs in the wild that put stuff into this field. # Just ignore it. self.orig_extent_loc = extent_loc self._initialized = True def new(self): # type: () -> None ''' Create a new Volume Descriptor Set Terminator. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Volume Descriptor Set Terminator already initialized') self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this Volume Descriptor Set Terminator. Parameters: None. Returns: String representing this Volume Descriptor Set Terminator. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Volume Descriptor Set Terminator not initialized') return struct.pack(self.FMT, VOLUME_DESCRIPTOR_TYPE_SET_TERMINATOR, b'CD001', 1, b'\x00' * 2041) def extent_location(self): # type: () -> int ''' Get this Volume Descriptor Set Terminator's extent location. Parameters: None. Returns: Integer extent location. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Volume Descriptor Set Terminator not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this Volume Descriptor Set Terminator. Parameters: extent - The new extent location to set for this Volume Descriptor Set Terminator. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent def vdst_factory(): # type: () -> VolumeDescriptorSetTerminator ''' An internal function to create a new Volume Descriptor Set Terminator. Parameters: None. Returns: The newly created Volume Descriptor Set Terminator. ''' vdst = VolumeDescriptorSetTerminator() vdst.new() return vdst class BootRecord(object): ''' A class representing an ISO9660 Boot Record. ''' __slots__ = ('_initialized', 'boot_system_identifier', 'boot_identifier', 'boot_system_use', 'orig_extent_loc', 'new_extent_loc') FMT = '=B5sB32s32s1977s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, vd, extent_loc): # type: (bytes, int) -> None ''' Parse a Boot Record out of a string. Parameters: vd - The string to parse the Boot Record out of. extent_loc - The extent location this Boot Record is current at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Boot Record already initialized') (descriptor_type, identifier, version, self.boot_system_identifier, self.boot_identifier, self.boot_system_use) = struct.unpack_from(self.FMT, vd, 0) # According to Ecma-119, 8.2.1, the boot record type should be 0 if descriptor_type != VOLUME_DESCRIPTOR_TYPE_BOOT_RECORD: raise pycdlibexception.PyCdlibInvalidISO('Invalid boot record descriptor type') # According to Ecma-119, 8.2.2, the identifier should be 'CD001' if identifier != b'CD001': raise pycdlibexception.PyCdlibInvalidISO('Invalid boot record identifier') # According to Ecma-119, 8.2.3, the version should be 1 if version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid boot record version') self.orig_extent_loc = extent_loc self._initialized = True def new(self, boot_system_id): # type: (bytes) -> None ''' Create a new Boot Record. Parameters: boot_system_id - The system identifier to associate with this Boot Record. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Boot Record already initialized') self.boot_system_identifier = boot_system_id.ljust(32, b'\x00') self.boot_identifier = b'\x00' * 32 self.boot_system_use = b'\x00' * 1977 # This will be set later self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this Boot Record. Parameters: None. Returns: A string representing this Boot Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Boot Record not initialized') return struct.pack(self.FMT, VOLUME_DESCRIPTOR_TYPE_BOOT_RECORD, b'CD001', 1, self.boot_system_identifier, self.boot_identifier, self.boot_system_use) def update_boot_system_use(self, boot_sys_use): # type: (bytes) -> None ''' Update the boot system use field of this Boot Record. Parameters: boot_sys_use - The new boot system use field for this Boot Record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Boot Record not initialized') if len(boot_sys_use) != 1977: raise pycdlibexception.PyCdlibInternalError('Boot system use field must be 1977 bytes') self.boot_system_use = boot_sys_use def extent_location(self): # type: () -> int ''' Get the extent location of this Boot Record. Parameters: None. Returns: Integer extent location of this Boot Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Boot Record not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this Boot Record. Parameters: extent - The new extent location to set for this Boot Record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent class VersionVolumeDescriptor(object): ''' A class representing a Version Volume Descriptor. This volume descriptor is not mentioned in any of the standards, but is included by genisoimage, so it is modeled here. ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', '_data') def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> bool ''' Do a parse of a Version Volume Descriptor. This consists of seeing whether the data is either all zero or starts with 'MKI', and if so, setting the extent location of the Version Volume Descriptor properly. Parameters: data - The potential version data. extent - The location of the extent on the original ISO of this Version Volume Descriptor. Returns: True if the data passed in is a Version Descriptor, False otherwise. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Version Volume Descriptor is already initialized') if data[:3] == b'MKI' or data == allzero: # OK, we have a version descriptor. self._data = data self.orig_extent_loc = extent self._initialized = True return True return False def new(self, log_block_size): # type: (int) -> None ''' Create a new Version Volume Descriptor. Parameters: log_block_size - The size of one extent. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This Version Volume Descriptor is already initialized') self._data = b'\x00' * log_block_size self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this Version Volume Descriptor. Note that right now, this is always a string of zeros. Parameters: log_block_size - The logical block size to use when generating this string. Returns: A string representing this Version Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Version Volume Descriptor is not initialized') return self._data def extent_location(self): # type: () -> int ''' Get the extent location of this Version Volume Descriptor. Parameters: None. Returns: An integer representing the extent location of this Version Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Version Volume Descriptor is not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this Version Volume Descriptor. Parameters: extent - The new extent location to set for this Version Volume Descriptor. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent def version_vd_factory(log_block_size): # type: (int) -> VersionVolumeDescriptor ''' An internal function to create a new Version Volume Descriptor. Parameters: log_block_size - The size of one extent. Returns: The newly created Version Volume Descriptor. ''' version_vd = VersionVolumeDescriptor() version_vd.new(log_block_size) return version_vd ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5649946 pycdlib-1.11.0/pycdlib/inode.py0000664000175000017500000001536200000000000021110 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2018-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' PyCdlib Inode class. ''' from __future__ import absolute_import from pycdlib import pycdlibexception # For mypy annotations if False: # pylint: disable=using-constant-test from typing import BinaryIO, List, Optional, Tuple, Union # NOQA pylint: disable=unused-import # NOTE: this import has to be here to avoid circular deps from pycdlib import dr # NOQA pylint: disable=unused-import from pycdlib import eltorito # NOQA pylint: disable=unused-import from pycdlib import udf # NOQA pylint: disable=unused-import class Inode(object): ''' A class that represents an inode, the pointer to a piece of data (not metadata) on an ISO. ''' __slots__ = ('_initialized', 'new_extent_loc', 'orig_extent_loc', 'linked_records', 'data_length', 'manage_fp', 'data_fp', 'original_data_location', 'fp_offset', 'boot_info_table', 'num_udf') DATA_ON_ORIGINAL_ISO = 1 DATA_IN_EXTERNAL_FP = 2 def __init__(self): # type: () -> None self.linked_records = [] # type: List[Tuple[Union[eltorito.EltoritoEntry, udf.UDFFileEntry, dr.DirectoryRecord], bool]] self._initialized = False self.data_length = 0 self.num_udf = 0 self.boot_info_table = None # type: Optional[eltorito.EltoritoBootInfoTable] self.new_extent_loc = -1 def new(self, length, fp, manage_fp, offset): # type: (int, Union[BinaryIO, str], bool, int) -> None ''' Initialize a new Inode. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is already initialized') self.data_length = length self.data_fp = fp self.manage_fp = manage_fp self.fp_offset = offset self.original_data_location = self.DATA_IN_EXTERNAL_FP self._initialized = True def parse(self, extent, length, fp, log_block_size): # type: (int, int, BinaryIO, int) -> None ''' Parse an existing Inode. This just saves off the extent for later use. Parameters: extent - The original extent that the data lives at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is already initialized') self.orig_extent_loc = extent self.data_length = length self.data_fp = fp self.manage_fp = False self.fp_offset = extent * log_block_size self.original_data_location = self.DATA_ON_ORIGINAL_ISO self._initialized = True def extent_location(self): # type: () -> int ''' Get the current location of this Inode on the ISO. Parameters: None. Returns: The extent location of this Inode on the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the current location of this Inode on the ISO. Parameters: extent - The new extent location for this Inode. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is not initialized') self.new_extent_loc = extent def get_data_length(self): # type: () -> int ''' Get the length of the data pointed to by this Inode. Parameters: None. Returns: The length of the data pointed to by this Inode. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is not initialized') return self.data_length def add_boot_info_table(self, boot_info_table): # type: (eltorito.EltoritoBootInfoTable) -> None ''' Add a boot info table to this Inode. Parameters: boot_info_table - The Boot Info Table object to add to this Inode. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is not initialized') self.boot_info_table = boot_info_table def update_fp(self, fp, length): # type: (BinaryIO, int) -> None ''' Update the Inode to use a different file object and length. Parameters: fp - A file object that contains the data for this Inode. length - The length of the data. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Inode is not initialized') self.original_data_location = self.DATA_IN_EXTERNAL_FP self.data_fp = fp self.data_length = length self.fp_offset = 0 class InodeOpenData(object): ''' A class to be a contextmanager for opening data on a DirectoryRecord object. ''' __slots__ = ('ino', 'logical_block_size', 'data_fp') def __init__(self, ino, logical_block_size): # type: (Inode, int) -> None self.ino = ino self.logical_block_size = logical_block_size def __enter__(self): if self.ino.manage_fp: # In the case that we are managing the FP, the data_fp member # actually contains the filename, not the fp. Use that to # our advantage here. self.data_fp = open(self.ino.data_fp, 'rb') else: self.data_fp = self.ino.data_fp if self.ino.original_data_location == self.ino.DATA_ON_ORIGINAL_ISO: self.data_fp.seek(self.ino.orig_extent_loc * self.logical_block_size) else: self.data_fp.seek(self.ino.fp_offset) return self.data_fp, self.ino.data_length def __exit__(self, *args): if self.ino.manage_fp: self.data_fp.close() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600634724.3524783 pycdlib-1.11.0/pycdlib/isohybrid.py0000664000175000017500000011351000000000000022000 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Implementation of ISO hybrid support. ''' from __future__ import absolute_import import random import struct import sys import uuid from pycdlib import pycdlibexception # For mypy annotations if False: # pylint: disable=using-constant-test from typing import List, Optional, Tuple # NOQA pylint: disable=unused-import APM_PARTS = 3 GPT_SIZE = 128 // 4 + 2 crc32_table = (0, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D) have_py_3 = True if sys.version_info.major == 2: have_py_3 = False def crc32(data): # type: (bytes) -> int ''' Calculate the CRC32 over a range of bytes. Parameters: data - The array of bytes to calculate the CRC32 over. Returns: The CRC32 of the data. ''' crc = 0xFFFFFFFF if have_py_3: for x in data: crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_table[(crc ^ x) & 0xFF] else: for x in data: crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_table[(crc ^ ord(x)) & 0xFF] # type: ignore return crc ^ 0xffffffff class APMPartHeader(object): ''' A class that represents an APM (Apple Partition Map) Partition Header. ''' __slots__ = ('_initialized', 'map_count', 'start_block', 'block_count', 'name', 'type_desc', 'data_start', 'data_count', 'status', 'boot_start', 'boot_count', 'boot_load', 'boot_load2', 'boot_entry', 'boot_entry2', 'boot_cksum', 'processor', 'driver_sig') FMT = '>HHLLL32s32sLLLLLLLLLL16sL372s' MAC_PARTITION_MAGIC = 0x504d def __init__(self): # type: () -> None self._initialized = False def parse(self, instr): # type: (bytes) -> None ''' Parse an APM Partition Header out of existing data. Parameters: instr - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This APMPartHeader object is already initialized') (sig, resv_unused, self.map_count, self.start_block, self.block_count, name, type_desc, self.data_start, self.data_count, self.status, self.boot_start, self.boot_count, self.boot_load, self.boot_load2, self.boot_entry, self.boot_entry2, self.boot_cksum, self.processor, self.driver_sig, padding_unused) = struct.unpack_from(self.FMT, instr, 0) if sig != self.MAC_PARTITION_MAGIC: raise pycdlibexception.PyCdlibInvalidISO('Invalid APM signature') self.name = name.decode('ascii').rstrip('\x00') self.type_desc = type_desc.decode('ascii').rstrip('\x00') self._initialized = True def new(self, name, type_desc, status): # type: (str, str, int) -> None ''' Create a new APM Partition Header. Parameters: name - The name for this partition map. type_desc - The type of this partition map. status - The status for this partition map. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This APMPartHeader object is already initialized') self.map_count = 3 self.start_block = 0 # this will get set later self.block_count = 0 # this will get set later self.name = name self.type_desc = type_desc self.data_start = 0 self.data_count = 0 # this will get set later self.status = status self.boot_start = 0 self.boot_count = 0 self.boot_load = 0 self.boot_load2 = 0 self.boot_entry = 0 self.boot_entry2 = 0 self.boot_cksum = 0 self.processor = b'' self.driver_sig = 0 self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this APM Partition Header. Parameters: None. Returns: A bytestring representing this APM Partition Header. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This APMPartHeader object is not initialized') return struct.pack(self.FMT, self.MAC_PARTITION_MAGIC, 0, self.map_count, self.start_block, self.block_count, self.name.encode('ascii'), self.type_desc.encode('ascii'), self.data_start, self.data_count, self.status, self.boot_start, self.boot_count, self.boot_load, self.boot_load2, self.boot_entry, self.boot_entry2, self.boot_cksum, self.processor, self.driver_sig, b'\x00' * 372) class GPTPartHeader(object): ''' A class that represents a GPT Partition Header. ''' __slots__ = ('_initialized', 'part_guid', 'part_type_guid', 'first_lba', 'last_lba', 'attributes', 'name') FMT = '<16s16sQQ8s72s' BASIC_PARTITION = b'\xa2\xa0\xd0\xeb\xe5\xb9\x33\x44\x87\xc0\x68\xb6\xb7\x26\x99\xc7' HFS_PARTITION = b'\x00\x53\x46\x48\x00\x00\xaa\x11\xaa\x11\x00\x30\x65\x43\xec\xac' def __init__(self): # type: () -> None self._initialized = False def parse(self, instr): # type: (bytes) -> int ''' Parse a GPT Partition Header out of existing data. Parameters: instr - The data to parse. Returns: The number of bytes consumed. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTPartHeader object is already initialized') (self.part_type_guid, part_guid, self.first_lba, self.last_lba, self.attributes, name) = struct.unpack_from(self.FMT, instr[:struct.calcsize(self.FMT)], 0) if self.part_type_guid not in (self.BASIC_PARTITION, self.HFS_PARTITION): raise pycdlibexception.PyCdlibInvalidISO('Invalid Partition Type UUID') self.name = name.decode('utf-16_le').rstrip('\x00') self.part_guid = uuid.UUID(bytes=part_guid) self._initialized = True return struct.calcsize(self.FMT) def new(self, is_basic, name): # type: (bool, str) -> None ''' Create a new GPT Partition Header. Parameters: is_basic - Whether this is a basic GPTPartHeader (True), or an HFS one (False). name - Bytestring containing the name to store. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTPartHeader object is already initialized') if is_basic: self.part_type_guid = self.BASIC_PARTITION else: self.part_type_guid = self.HFS_PARTITION self.part_guid = uuid.uuid4() self.first_lba = 0 self.last_lba = 0 # this will be set later self.attributes = b'\x00' * 8 self.name = name self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing this GPT Partition Header. Parameters: None. Returns: A bytestring representing this GPT Partition Header. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTPartHeader object is not initialized') return struct.pack(self.FMT, self.part_type_guid, self.part_guid.bytes, self.first_lba, self.last_lba, self.attributes, self.name.encode('utf-16_le')) class GPTHeader(object): ''' A class that represents a GPT Header. ''' __slots__ = ('_initialized', 'current_lba', 'backup_lba', 'first_usable_lba', 'last_usable_lba', 'disk_guid', 'partition_entries_lba', 'num_parts', 'size_of_partition_entries') FMT = '<8s4s4sLLQQQQ16sQLLL420s' GPT_SIG = b'\x45\x46\x49\x20\x50\x41\x52\x54' GPT_REV = b'\x00\x00\x01\x00' GPT_HEADER_SIZE = b'\x5c\x00\x00\x00' def __init__(self): # type: () -> None self._initialized = False def parse(self, instr): # type: (bytes) -> int ''' Parse a GPT Header out of existing data. Parameters: instr - The data to parse. Returns: The number of bytes consumed. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTHeader object is already initialized') (sig, revision, header_size, header_crc_unused, resv1_unused, self.current_lba, self.backup_lba, self.first_usable_lba, self.last_usable_lba, disk_guid, self.partition_entries_lba, self.num_parts, self.size_of_partition_entries, partition_entries_crc_unused, resv2_unused) = struct.unpack_from(self.FMT, instr[:struct.calcsize(self.FMT)], 0) if sig != self.GPT_SIG: raise pycdlibexception.PyCdlibInvalidISO('Failed to find GPT signature while parsing GPT Header') if revision != self.GPT_REV: raise pycdlibexception.PyCdlibInvalidISO('Failed to find GPT revision while parsing GPT Header') if header_size != self.GPT_HEADER_SIZE: raise pycdlibexception.PyCdlibInvalidISO('Invalid GPT Header size while parsing GPT Header') self.disk_guid = uuid.UUID(bytes=disk_guid) self._initialized = True return struct.calcsize(self.FMT) def new(self, mac): # type: (bool) -> None ''' Create a new GPT Header. Parameters: mac - Whether this GPT has Mac support. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTHeader object is already initialized') self.current_lba = 0 # this will be set later self.backup_lba = 0 # this will be set laster gpt_size = GPT_SIZE if mac: gpt_size += (APM_PARTS * 4) + 2 self.first_usable_lba = gpt_size self.last_usable_lba = 0 # this will be set later self.disk_guid = uuid.uuid4() self.partition_entries_lba = 0 # this will be set later self.num_parts = 128 self.size_of_partition_entries = 128 self._initialized = True def set_lbas(self, current, backup): # type: (int, int) -> None ''' Set the current and backup LBAs for this GPT Header. Parameters: current - The current LBA to set for this GPT Header. backup - The backup LBA to set for this GPT Header. Returns: Nothing ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTHeader object is not initialized') self.current_lba = current self.backup_lba = backup def set_last_usable_lba(self, iso_size_and_padding): # type: (int) -> None ''' Set the last usable LBA for this GPT Header. Parameters: iso_size_and_padding - The size of the ISO with padding. Returns: Nothing ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTHeader object is not initialized') self.last_usable_lba = iso_size_and_padding // 512 - GPT_SIZE def record(self, part_entries_crc): # type: (int) -> bytes ''' Generate a string representing this GPT Header. Parameters: None. Returns: A bytestring representing this GPT Header. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPTHeader object is not initialized') rec = struct.pack(self.FMT, self.GPT_SIG, self.GPT_REV, self.GPT_HEADER_SIZE, 0, 0, self.current_lba, self.backup_lba, self.first_usable_lba, self.last_usable_lba, self.disk_guid.bytes, self.partition_entries_lba, self.num_parts, self.size_of_partition_entries, part_entries_crc, b'\x00' * 420) header_crc = crc32(rec[:92]) header_packed = struct.pack(' None self.is_primary = is_primary self.header = GPTHeader() self.parts = [] # type: List[GPTPartHeader] self.apm_parts = [] # type: List[APMPartHeader] self._initialized = False def parse_primary(self, instr, mac): # type: (bytes, bool) -> None ''' Parse a primary GPT. Parameters: instr - The string containing the data to parse. mac - Whether this GPT contains mac data as well. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPT object is already initialized') if not self.is_primary: raise pycdlibexception.PyCdlibInternalError('Cannot parse primary with a secondary GPT') offset = 512 offset += self.header.parse(instr[offset:]) if mac: # Now go looking for the APMs offset = 2048 for i_unused in range(0, APM_PARTS): apm_part = APMPartHeader() apm_part.parse(instr[offset:]) self.apm_parts.append(apm_part) offset += 2048 offset = self.header.partition_entries_lba * 512 for i_unused in range(0, self.header.num_parts): # Some GPT implementations have large numbers of "empty" # partition headers (like syslinux). We could store a slew # of these empty partition headers, but that's just a waste # of memory. Instead we peek ahead and once we see one of # these, we assume we are done parsing. if instr[offset:offset + 2] == b'\x00\x00': break part = GPTPartHeader() offset += part.parse(instr[offset:]) self.parts.append(part) self._initialized = True def parse_secondary_header(self, instr): # type: (bytes) -> None ''' Parse a secondary GPT Header. Parameters: instr - The string containing the data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPT object is already initialized') if self.is_primary: raise pycdlibexception.PyCdlibInternalError('Cannot parse secondary header with a primary GPT') self.header.parse(instr) def parse_secondary_partitions(self, instr): # type: (bytes) -> None ''' Parse a secondary GPT set of partitions. Parameters: instr - The string containing the data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPT object is already initialized') offset = 0 for i in range(0, self.header.num_parts): # Some GPT implementations have large numbers of "empty" # partition headers (like syslinux). We could store a slew # of these empty partition headers, but that's just a waste # of memory. Instead we peek ahead and once we see one of # these, we assume we are done parsing. if instr[offset:offset + 2] == b'\x00\x00': break part = GPTPartHeader() offset += part.parse(instr[offset:]) self.parts.append(part) self._initialized = True def new(self, mac): # type: (bool) -> None ''' Create a new GPT. Parameters: mac - Whether this is a GPT for a Macintosh boot. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPT object is already initialized') self.header.new(mac) if self.is_primary: hole = 0 if mac: hole = (APM_PARTS * 4) + 2 self.header.partition_entries_lba = 2 + hole part1 = GPTPartHeader() part1.new(True, 'ISOHybrid ISO') self.parts.append(part1) part2 = GPTPartHeader() part2.new(True, 'ISOHybrid') self.parts.append(part2) if mac: part3 = GPTPartHeader() part3.new(False, 'ISOHybrid') self.parts.append(part3) if self.is_primary: apm = APMPartHeader() apm.new('Apple', 'Apple_partition_map', 0x3) self.apm_parts.append(apm) apm2 = APMPartHeader() apm2.new('EFI', 'Apple_HFS', 0x33) self.apm_parts.append(apm2) apm3 = APMPartHeader() apm3.new('EFI', 'Apple_HFS', 0x33) self.apm_parts.append(apm3) self._initialized = True def record(self): # type: () -> bytes ''' Create a string representing this GPT. Parameters: None. Returns: A bytestring representing this GPT. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This GPT object is not initialized') # The partitions really go later, but since we need the partition # string so we can calculate the CRC for the header, we do it all. tmplist = [] for part in self.parts: tmplist.append(part.record()) part_data = b''.join(tmplist) if self.is_primary: outlist = [self.header.record(crc32(part_data))] if self.apm_parts: outlist.append(b'\x00' * 1024) for apm_part in self.apm_parts: raw = apm_part.record() pad = b'\x00' * (2048 - len(raw)) outlist.extend([raw, pad]) outlist.append(part_data) # Write out all of the "empty" partitions. outlist.append(b'\x00' * (self.header.num_parts - len(self.parts)) * 128) else: outlist = [part_data] # Write out all of the "empty" partitions. outlist.append(b'\x00' * (self.header.num_parts - len(self.parts)) * 128) outlist.append(self.header.record(crc32(part_data))) return b''.join(outlist) class IsoHybrid(object): ''' A class that represents an ISO hybrid; that is, an ISO that can be booted via CD or via an alternate boot mechanism (such as USB). ''' __slots__ = ('_initialized', 'header', 'mbr', 'rba', 'mbr_id', 'part_entry', 'bhead', 'bsect', 'bcyle', 'ptype', 'ehead', 'part_offset', 'geometry_heads', 'geometry_sectors', 'efi', 'efi_lba', 'efi_count', 'mac', 'mac_lba', 'mac_count', 'primary_gpt', 'secondary_gpt') FMT = '<400sLLLH' ORIG_HEADER = b'\x33\xed' + b'\x90' * 30 MAC_AFP = b'\x45\x52\x08\x00\x00\x00\x90\x90' + b'\x00' * 24 EFI_HEADER = b'\x00\xfe\xff\xff\xef\xfe\xff\xff' MAC_HEADER = b'\x00\xfe\xff\xff\x00\xfe\xff\xff' def __init__(self): # type: () -> None self.part_entry = -1 self.efi = False self.mac = False self.primary_gpt = GPT(True) self.secondary_gpt = GPT(False) self._initialized = False def parse(self, instr): # type: (bytes) -> bool ''' Parse ISO hybridization info out of an existing ISO. Parameters: instr - The data for the ISO hybridization. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is already initialized') if len(instr) < 512: raise pycdlibexception.PyCdlibInternalError('Invalid IsoHybrid MBR passed') if instr[0:32] == self.ORIG_HEADER: self.header = self.ORIG_HEADER elif instr[0:32] == self.MAC_AFP: self.header = self.MAC_AFP else: # If we didn't see anything that we expected, then this is not an # IsoHybrid ISO, so just quietly return False return False (self.mbr, self.rba, unused1, self.mbr_id, unused2) = struct.unpack_from(self.FMT, instr[:32 + struct.calcsize(self.FMT)], 32) if unused1 != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid IsoHybrid unused1') if unused2 != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid IsoHybrid unused2') offset = 32 + struct.calcsize(self.FMT) for i in range(1, 5): if bytes(bytearray([instr[offset]])) == b'\x80': self.part_entry = i (const_unused, self.bhead, self.bsect, self.bcyle, self.ptype, self.ehead, esect_unused, ecyle, self.part_offset, psize) = struct.unpack_from(' 63: self.geometry_sectors = 63 if self.efi: self.primary_gpt.parse_primary(instr, self.mac) self._initialized = True return True def parse_secondary_gpt_header(self, instr): # type: (bytes) -> None ''' Parse the secondary GPT Header. Parameters: instr - The data to parse for the GPT Header. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') self.secondary_gpt.parse_secondary_header(instr) def parse_secondary_gpt_partitions(self, instr): # type: (bytes) -> None ''' Parse the secondary GPT Partitions. Parameters: instr - The data to parse for the partitions. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') self.secondary_gpt.parse_secondary_partitions(instr) def new(self, efi, mac, part_entry, mbr_id, part_offset, geometry_sectors, geometry_heads, part_type): # type: (bool, bool, int, Optional[int], int, int, int, int) -> None ''' Add ISO hybridization to an ISO. Parameters: efi - Whether this ISO should be setup for EFI boot. mac - Whether this ISO should be made bootable for the Macintosh. part_entry - The partition entry for the hybridization. mbr_id - The mbr_id to use for the hybridization. part_offset - The partition offset to use for the hybridization. geometry_sectors - The number of sectors to use for the hybridization. geometry_heads - The number of heads to use for the hybridization. part_type - The partition type for the hybridization. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is already initialized') if geometry_sectors < 1 or geometry_sectors > 63: raise pycdlibexception.PyCdlibInvalidInput('Geometry sectors can only be between 1 and 63, inclusive') if geometry_heads < 1 or geometry_heads > 256: raise pycdlibexception.PyCdlibInvalidInput('Geometry heads can only be between 1 and 256, inclusive') if mac and part_type != 0: raise pycdlibexception.PyCdlibInvalidInput('When generating for Mac, partition type must be 0') isohybrid_data_hd0 = b'\x33\xed\xfa\x8e\xd5\xbc\x00\x7c\xfb\xfc\x66\x31\xdb\x66\x31\xc9\x66\x53\x66\x51\x06\x57\x8e\xdd\x8e\xc5\x52\xbe\x00\x7c\xbf\x00\x06\xb9\x00\x01\xf3\xa5\xea\x4b\x06\x00\x00\x52\xb4\x41\xbb\xaa\x55\x31\xc9\x30\xf6\xf9\xcd\x13\x72\x16\x81\xfb\x55\xaa\x75\x10\x83\xe1\x01\x74\x0b\x66\xc7\x06\xf1\x06\xb4\x42\xeb\x15\xeb\x00\x5a\x51\xb4\x08\xcd\x13\x83\xe1\x3f\x5b\x51\x0f\xb6\xc6\x40\x50\xf7\xe1\x53\x52\x50\xbb\x00\x7c\xb9\x04\x00\x66\xa1\xb0\x07\xe8\x44\x00\x0f\x82\x80\x00\x66\x40\x80\xc7\x02\xe2\xf2\x66\x81\x3e\x40\x7c\xfb\xc0\x78\x70\x75\x09\xfa\xbc\xec\x7b\xea\x44\x7c\x00\x00\xe8\x83\x00\x69\x73\x6f\x6c\x69\x6e\x75\x78\x2e\x62\x69\x6e\x20\x6d\x69\x73\x73\x69\x6e\x67\x20\x6f\x72\x20\x63\x6f\x72\x72\x75\x70\x74\x2e\x0d\x0a\x66\x60\x66\x31\xd2\x66\x03\x06\xf8\x7b\x66\x13\x16\xfc\x7b\x66\x52\x66\x50\x06\x53\x6a\x01\x6a\x10\x89\xe6\x66\xf7\x36\xe8\x7b\xc0\xe4\x06\x88\xe1\x88\xc5\x92\xf6\x36\xee\x7b\x88\xc6\x08\xe1\x41\xb8\x01\x02\x8a\x16\xf2\x7b\xcd\x13\x8d\x64\x10\x66\x61\xc3\xe8\x1e\x00\x4f\x70\x65\x72\x61\x74\x69\x6e\x67\x20\x73\x79\x73\x74\x65\x6d\x20\x6c\x6f\x61\x64\x20\x65\x72\x72\x6f\x72\x2e\x0d\x0a\x5e\xac\xb4\x0e\x8a\x3e\x62\x04\xb3\x07\xcd\x10\x3c\x0a\x75\xf1\xcd\x18\xf4\xeb\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' self.mbr = isohybrid_data_hd0 self.rba = 0 # This will be set later self.mbr_id = mbr_id if self.mbr_id is None: self.mbr_id = random.getrandbits(32) self.part_entry = part_entry self.bhead = (part_offset // geometry_sectors) % geometry_heads self.bsect = (part_offset % geometry_sectors) + 1 self.bcyle = part_offset // (geometry_heads * geometry_sectors) self.bsect += (self.bcyle & 0x300) >> 2 self.bcyle &= 0xff self.ptype = part_type self.ehead = geometry_heads - 1 self.part_offset = part_offset self.geometry_heads = geometry_heads self.geometry_sectors = geometry_sectors self.mac = mac if self.mac: self.header = self.MAC_AFP self.mac_lba = 0 # this will be set later self.mac_count = 0 # this will be set later else: self.header = self.ORIG_HEADER self.efi = efi if self.efi: self.efi_lba = 0 # this will be set later self.efi_count = 0 # this will be set later self.primary_gpt.new(self.mac) self.secondary_gpt.new(self.mac) self._initialized = True def _calc_cc(self, iso_size): # type: (int) -> Tuple[int, int] ''' Calculate the 'cc' and the 'padding' values for this hybridization. Parameters: iso_size - The size of the ISO, excluding the hybridization. Returns: A tuple containing the cc value and the padding. ''' cylsize = self.geometry_heads * self.geometry_sectors * 512 frac = iso_size % cylsize padding = 0 if frac > 0: padding = cylsize - frac cc = (iso_size + padding) // cylsize if cc > 1024: cc = 1024 return (cc, padding) def record(self, iso_size): # type: (int) -> bytes ''' Generate a string containing the ISO hybridization. Parameters: iso_size - The size of the ISO, excluding the hybridization. Returns: A string containing the ISO hybridization. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') outlist = [struct.pack('<32s400sLLLH', self.header, self.mbr, self.rba, 0, self.mbr_id, 0)] for i in range(1, 5): raw = b'\x00' * 16 if i == self.part_entry: cc = self._calc_cc(iso_size)[0] esect = self.geometry_sectors + (((cc - 1) & 0x300) >> 2) ecyle = (cc - 1) & 0xff psize = cc * self.geometry_heads * self.geometry_sectors - self.part_offset raw = struct.pack(' bytes ''' Record padding for the ISO hybridization. Parameters: iso_size - The size of the ISO, excluding the hybridization. Returns: A string of zeros the right size to pad the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') padlen = self._calc_cc(iso_size)[1] return b'\x00' * padlen def update_rba(self, current_extent): # type: (int) -> None ''' Update the current rba for the ISO hybridization. Parameters: current_extent - The new extent to set the RBA to. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') self.rba = current_extent def update_efi(self, current_extent, sector_count, iso_size): # type: (int, int, int) -> None ''' Update the current EFI lba for the ISO hybridization. Parameters: current_extent - The new extent to set the RBA to. sector_count - The number of sectors for the EFI entry. iso_size - The full size of the ISO. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') if not self.efi: raise pycdlibexception.PyCdlibInternalError('Attempted to set EFI lba on a non-EFI ISO') self.efi_lba = current_extent self.efi_count = sector_count padlen = self._calc_cc(iso_size)[1] size_and_padlen = iso_size + padlen secondary_lba = (size_and_padlen - 512) // 512 self.primary_gpt.header.set_lbas(1, secondary_lba) self.primary_gpt.header.set_last_usable_lba(size_and_padlen) self.primary_gpt.parts[0].last_lba = (iso_size // 512) - 1 self.primary_gpt.parts[1].first_lba = current_extent * 4 self.primary_gpt.parts[1].last_lba = (current_extent * 4) + sector_count - 1 self.secondary_gpt.header.partition_entries_lba = secondary_lba - (128 // 4) self.secondary_gpt.header.set_lbas(secondary_lba, 1) self.secondary_gpt.header.set_last_usable_lba(size_and_padlen) self.secondary_gpt.parts[0].last_lba = (iso_size // 512) - 1 self.secondary_gpt.parts[1].first_lba = current_extent * 4 self.secondary_gpt.parts[1].last_lba = (current_extent * 4) + sector_count - 1 def update_mac(self, current_extent, sector_count): # type: (int, int) -> None ''' Update the current MAC lba for the ISO hybridization. Parameters: current_extent - The new extent to set the RBA to. sector_count - The number of sectors for the MAC entry. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This IsoHybrid object is not initialized') if not self.mac: raise pycdlibexception.PyCdlibInternalError('Attempted to set Mac lba on a non-Mac ISO') self.mac_lba = current_extent self.mac_count = sector_count self.primary_gpt.parts[2].first_lba = current_extent * 4 self.primary_gpt.parts[2].last_lba = (current_extent * 4) + sector_count - 1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600304860.1256611 pycdlib-1.11.0/pycdlib/path_table_record.py0000664000175000017500000001714600000000000023455 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Class to support ISO9660 Path Table Records. ''' from __future__ import absolute_import import struct from pycdlib import pycdlibexception from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import Type # NOQA pylint: disable=unused-import class PathTableRecord(object): ''' A class that represents a single ISO9660 Path Table Record. ''' __slots__ = ('_initialized', 'len_di', 'xattr_length', 'extent_location', 'parent_directory_num', 'directory_identifier', 'dirrecord') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse an ISO9660 Path Table Record out of a string. Parameters: data - The string to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record already initialized') (self.len_di, self.xattr_length, self.extent_location, self.parent_directory_num) = struct.unpack_from(self.FMT, data[:8], 0) if self.len_di % 2 != 0: self.directory_identifier = data[8:-1] else: self.directory_identifier = data[8:] self.dirrecord = None self._initialized = True def _record(self, ext_loc, parent_dir_num): # type: (int, int) -> bytes ''' An internal method to generate a string representing this Path Table Record. Parameters: ext_loc - The extent location to place in this Path Table Record. parent_dir_num - The parent directory number to place in this Path Table Record. Returns: A string representing this Path Table Record. ''' return struct.pack(self.FMT, self.len_di, self.xattr_length, ext_loc, parent_dir_num) + self.directory_identifier + b'\x00' * (self.len_di % 2) def record_little_endian(self): # type: () -> bytes ''' Generate a string representing the little endian version of this Path Table Record. Parameters: None. Returns: A string representing the little endian version of this Path Table Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record not initialized') return self._record(self.extent_location, self.parent_directory_num) def record_big_endian(self): # type: () -> bytes ''' Generate a string representing the big endian version of this Path Table Record. Parameters: None. Returns: A string representing the big endian version of this Path Table Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record not initialized') return self._record(utils.swab_32bit(self.extent_location), utils.swab_16bit(self.parent_directory_num)) @classmethod def record_length(cls, len_di): # type: (Type[PathTableRecord], int) -> int ''' A class method to calculate the length of a Path Table Record. Parameters: len_di - The length of the name for this Path Directory Record. Returns: The total length that a Path Directory Record with this name would occupy. ''' return struct.calcsize(cls.FMT) + len_di + (len_di % 2) def _new(self, name, parent_dir_num): # type: (bytes, int) -> None ''' An internal method to create a new Path Table Record. Parameters: name - The name for this Path Table Record. parent_dir_num - The directory number of the parent of this Path Table Record. Returns: Nothing. ''' self.len_di = len(name) self.xattr_length = 0 # FIXME: we don't support xattr for now self.parent_directory_num = parent_dir_num self.directory_identifier = name self._initialized = True def new_root(self): # type: () -> None ''' Create a new root Path Table Record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record already initialized') self._new(b'\x00', 1) def new_dir(self, name): # type: (bytes) -> None ''' Create a new Path Table Record. Parameters: name - The name for this Path Table Record. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record already initialized') # Zero for the parent dir num is bogus, but that will get fixed later. self._new(name, 0) def update_extent_location(self, extent_loc): # type: (int) -> None ''' Update the extent location for this Path Table Record. Parameters: extent_loc - The new extent location. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record not initialized') self.extent_location = extent_loc def update_parent_directory_number(self, parent_dir_num): # type: (int) -> None ''' Update the parent directory number for this Path Table Record. Parameters: parent_dir_num - The new parent directory number to assign to this PTR. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record not initialized') self.parent_directory_num = parent_dir_num def equal_to_be(self, be_record): # type: (PathTableRecord) -> bool ''' Compare a little-endian path table record to its big-endian counterpart. This is used to ensure that the ISO is sane. Parameters: be_record - The big-endian object to compare with the little-endian object. Returns: True if this record is equal to the big-endian record passed in, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Path Table Record not initialized') if be_record.len_di != self.len_di or \ be_record.xattr_length != self.xattr_length or \ utils.swab_32bit(be_record.extent_location) != self.extent_location or \ utils.swab_16bit(be_record.parent_directory_num) != self.parent_directory_num or \ be_record.directory_identifier != self.directory_identifier: return False return True ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4931056 pycdlib-1.11.0/pycdlib/pycdlib.py0000664000175000017500000106172000000000000021440 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Main PyCdlib class and support classes and utilities. ''' from __future__ import absolute_import import bisect import collections import inspect import io import os import struct import sys import warnings try: from functools import lru_cache except ImportError: from pycdlib.backport_functools import lru_cache # type: ignore try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO # pylint: disable=ungrouped-imports from pycdlib import dr from pycdlib import eltorito from pycdlib import facade from pycdlib import headervd from pycdlib import inode from pycdlib import isohybrid from pycdlib import path_table_record from pycdlib import pycdlibexception from pycdlib import pycdlibio from pycdlib import udf as udfmod from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import Any, BinaryIO, Callable, Deque, Dict, Generator, IO, List, Optional, Tuple, Union # NOQA pylint: disable=unused-import # There are a number of specific ways that numerical data is stored in the # ISO9660/Ecma-119 standard. In the text these are reference by the section # number they are stored in. A brief synopsis: # # 7.1.1 - 8-bit number # 7.2.3 - 16-bit number, stored first as little-endian then as big-endian (4 bytes total) # 7.3.1 - 32-bit number, stored as little-endian # 7.3.2 - 32-bit number ,stored as big-endian # 7.3.3 - 32-bit number, stored first as little-endian then as big-endian (8 bytes total) # We allow A-Z, 0-9, and _ as "d1" characters. The below is the fastest way to # build that list as integers. _allowed_d1_characters = set(tuple(range(65, 91)) + tuple(range(48, 58)) + tuple((ord(b'_'),))) def _check_d1_characters(name): # type: (bytes) -> None ''' A function to check that a name only uses d1 characters as defined by ISO9660. Parameters: name - The name to check. Returns: Nothing. ''' bytename = bytearray(name) for char in bytename: if char not in _allowed_d1_characters: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 filenames must consist of characters A-Z, 0-9, and _') def _split_iso9660_filename(fullname): # type: (bytes) -> Tuple[bytes, bytes, bytes] ''' A function to split an ISO 9660 filename into its constituent parts. This is the name, the extension, and the version number. Parameters: fullname - The name to split. Returns: A tuple containing the name, extension, and version. ''' namesplit = fullname.split(b';') version = b'' if len(namesplit) > 1: version = namesplit.pop() rest = b';'.join(namesplit) dotsplit = rest.split(b'.') if len(dotsplit) == 1: name = dotsplit[0] extension = b'' else: name = b'.'.join(dotsplit[:-1]) extension = dotsplit[-1] return (name, extension, version) def _check_iso9660_filename(fullname, interchange_level): # type: (bytes, int) -> None ''' A function to check that a file identifier conforms to the ISO9660 rules for a particular interchange level. Parameters: fullname - The name to check. interchange_level - The interchange level to check against. Returns: Nothing. ''' # Check to ensure the name is a valid filename for the ISO according to # Ecma-119 7.5. (name, extension, version) = _split_iso9660_filename(fullname) # Ecma-119 says that filenames must end with a semicolon-number, but I have # found CDs (Ubuntu 14.04 Desktop i386, for instance) that do not follow # this. Thus we allow for names both with and without the semi+version. # Ecma-119 says that filenames must have a version number, but I have # found CDs (FreeBSD 10.1 amd64) that do not have any version number. # Allow for this. if version != b'' and (int(version) < 1 or int(version) > 32767): raise pycdlibexception.PyCdlibInvalidInput('ISO9660 filenames must have a version between 1 and 32767') # Ecma-119 section 7.5.1 specifies that filenames must have at least one # character in either the name or the extension. if not name and not extension: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 filenames must have a non-empty name or extension') if b';' in name or b';' in extension: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 filenames must contain exactly one semicolon') if interchange_level == 1: # According to Ecma-119, section 10.1, at level 1 the filename can # only be up to 8 d-characters or d1-characters, and the extension can # only be up to 3 d-characters or 3 d1-characters. if len(name) > 8 or len(extension) > 3: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 filenames at interchange level 1 cannot have more than 8 characters or 3 characters in the extension') else: # For all other interchange levels, the maximum filename length is # specified in Ecma-119 7.5.2. However, I have found CDs (Ubuntu 14.04 # Desktop i386, for instance) that don't conform to this. Skip the # check until we know how long is allowed. pass # Ecma-119 section 7.5.1 says that the file name and extension each contain # zero or more d-characters or d1-characters. While the definition of # d-characters and d1-characters is not specified in Ecma-119, # http://wiki.osdev.org/ISO_9660 suggests that this consists of A-Z, 0-9, _ # which seems to correlate with empirical evidence. if interchange_level < 4: _check_d1_characters(name) _check_d1_characters(extension) def _check_iso9660_directory(fullname, interchange_level): # type: (bytes, int) -> None ''' A function to check that an directory identifier conforms to the ISO9660 rules for a particular interchange level. Parameters: fullname - The name to check. interchange_level - The interchange level to check against. Returns: Nothing. ''' # Check to ensure the directory name is valid for the ISO according to # Ecma-119 7.6. # Ecma-119 section 7.6.1 says that a directory identifier needs at least one # character if not fullname: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 directory names must be at least 1 character long') maxlen = float('inf') if interchange_level == 1: # Ecma-119 section 10.1 says that directory identifiers lengths cannot # exceed 8 at interchange level 1. maxlen = 8 elif interchange_level in (2, 3): # Ecma-119 section 7.6.3 says that directory identifiers lengths cannot # exceed 207. maxlen = 207 # for interchange_level 4, we allow any length if len(fullname) > maxlen: raise pycdlibexception.PyCdlibInvalidInput('ISO9660 directory names at interchange level %d cannot exceed %d characters' % (interchange_level, maxlen)) # Ecma-119 section 7.6.1 says that directory names consist of one or more # d-characters or d1-characters. While the definition of d-characters and # d1-characters is not specified in Ecma-119, # http://wiki.osdev.org/ISO_9660 suggests that this consists of A-Z, 0-9, _ # which seems to correlate with empirical evidence. Thus we check for that # here. if interchange_level < 4: _check_d1_characters(fullname) def _interchange_level_from_filename(fullname): # type: (bytes) -> int ''' A function to determine the ISO interchange level from the filename. In theory, there are 3 levels, but in practice we only deal with level 1 and level 3. Parameters: name - The name to use to determine the interchange level. Returns: The interchange level determined from this filename. ''' (name, extension, version) = _split_iso9660_filename(fullname) interchange_level = 1 if version != b'' and (int(version) < 1 or int(version) > 32767): interchange_level = 3 if b';' in name or b';' in extension: interchange_level = 3 if len(name) > 8 or len(extension) > 3: interchange_level = 3 try: _check_d1_characters(name) _check_d1_characters(extension) except pycdlibexception.PyCdlibInvalidInput: interchange_level = 3 return interchange_level def _interchange_level_from_directory(name): # type: (bytes) -> int ''' A function to determine the ISO interchange level from the directory name. In theory, there are 3 levels, but in practice we only deal with level 1 and level 3. Parameters: name - The name to use to determine the interchange level. Returns: The interchange level determined from this filename. ''' interchange_level = 1 if len(name) > 8: interchange_level = 3 try: _check_d1_characters(name) except pycdlibexception.PyCdlibInvalidInput: interchange_level = 3 return interchange_level def _reassign_vd_dirrecord_extents(vd, current_extent): # type: (headervd.PrimaryOrSupplementaryVD, int) -> Tuple[int, List[inode.Inode]] ''' An internal helper method for reassign_extents that assigns extents to directory records for the passed in Volume Descriptor. The current extent is passed in, and this function returns the extent after the last one it assigned. Parameters: vd - The volume descriptor on which to operate. current_extent - The current extent before assigning extents to the volume descriptor directory records. Returns: The current extent after assigning extents to the volume descriptor directory records. ''' log_block_size = vd.logical_block_size() root_dir_record = vd.root_directory_record() root_dir_record.set_data_location(current_extent, 0) current_extent += utils.ceiling_div(root_dir_record.data_length, log_block_size) # Walk through the list, assigning extents to all of the directories. child_link_recs = [] # type: List[dr.DirectoryRecord] parent_link_recs = [] # type: List[dr.DirectoryRecord] file_list = [] ptr_index = 1 dirs = collections.deque([root_dir_record]) while dirs: dir_record = dirs.popleft() if dir_record.is_root: # The root directory record doesn't need an extent assigned, # so just add its children to the list and continue on for child in dir_record.children: if child.ptr is not None: child.ptr.update_parent_directory_number(ptr_index) ptr_index += 1 dirs.extend(dir_record.children) continue dir_record_parent = dir_record.parent if dir_record_parent is None: raise pycdlibexception.PyCdlibInternalError('Parent of record is empty, this should never happen') if dir_record.is_dot(): dir_record.set_data_location(dir_record_parent.extent_location(), 0) continue dir_record_rock_ridge = dir_record.rock_ridge if dir_record.is_dotdot(): if dir_record_parent.is_root: # Special case of the root directory record. In this case, we # set the dotdot extent location to the same as the root. dir_record.set_data_location(dir_record_parent.extent_location(), 0) continue if dir_record_parent.parent is None: raise pycdlibexception.PyCdlibInternalError('Grandparent of record is empty, this should never happen') dir_record.set_data_location(dir_record_parent.parent.extent_location(), 0) # Now that we've set the data location, move around the Rock Ridge # links if necessary. if dir_record_rock_ridge is not None: if dir_record_rock_ridge.parent_link is not None: parent_link_recs.append(dir_record) if dir_record_parent.rock_ridge is not None: if dir_record_parent.parent is not None: if dir_record_parent.parent.is_root: source_dr = dir_record_parent.parent.children[0] else: source_dr = dir_record_parent.parent if source_dr is None or source_dr.rock_ridge is None: raise pycdlibexception.PyCdlibInternalError('Expected directory record to have Rock Ridge') dir_record_rock_ridge.copy_file_links(source_dr.rock_ridge) continue if dir_record.is_dir(): dir_record.set_data_location(current_extent, current_extent) for child in dir_record.children: if child.ptr is not None: child.ptr.update_parent_directory_number(ptr_index) ptr_index += 1 if dir_record_rock_ridge is None or not dir_record_rock_ridge.child_link_record_exists(): current_extent += utils.ceiling_div(dir_record.data_length, log_block_size) dirs.extend(dir_record.children) else: if dir_record.data_length == 0 or (dir_record_rock_ridge is not None and (dir_record_rock_ridge.child_link_record_exists() or dir_record_rock_ridge.is_symlink())): # If this is a child link record, the extent location really # doesn't matter, since it is fake. We set it to zero. dir_record.set_data_location(0, 0) else: if dir_record.inode is not None: file_list.append(dir_record.inode) if dir_record_rock_ridge is not None: if dir_record_rock_ridge.dr_entries.ce_record is not None and dir_record_rock_ridge.ce_block is not None: if dir_record_rock_ridge.ce_block.extent_location() < 0: dir_record_rock_ridge.ce_block.set_extent_location(current_extent) current_extent += 1 dir_record_rock_ridge.dr_entries.ce_record.update_extent(dir_record_rock_ridge.ce_block.extent_location()) if dir_record_rock_ridge.cl_to_moved_dr is not None: child_link_recs.append(dir_record) # After we have reshuffled the extents, update the rock ridge links. for ch in child_link_recs: if ch.rock_ridge is not None: ch.rock_ridge.child_link_update_from_dirrecord() for p in parent_link_recs: if p.rock_ridge is not None: p.rock_ridge.parent_link_update_from_dirrecord() return current_extent, file_list def _check_path_depth(iso_path): # type: (bytes) -> None ''' An internal method to take a fully-qualified iso path and check whether it meets the path depth requirements of ISO9660/Ecma-119. Parameters: iso_path - The path to check. Returns: Nothing. ''' if len(utils.split_path(iso_path)) > 7: # Ecma-119 Section 6.8.2.1 says that the number of levels in the # hierarchy shall not exceed eight. Since the root directory must be # at level 1 by itself, the effective maximum hierarchy depth is 7. raise pycdlibexception.PyCdlibInvalidInput('Directory levels too deep (maximum is 7)') def _yield_children(rec): # type: (dr.DirectoryRecord) -> Generator ''' An internal function to gather and yield all of the children of a Directory Record. Parameters: rec - The Directory Record to get all of the children from (must be a directory) Yields: Children of this Directory Record. Returns: Nothing. ''' if not rec.is_dir(): raise pycdlibexception.PyCdlibInvalidInput('Record is not a directory!') last = b'' for child in rec.children: # If the filename of this child is the same as the last one, skip the # child. This can happen if there is a very large file with more than # one directory entry. fi = child.file_identifier() if fi == last: continue last = fi if child.rock_ridge is not None and child.rock_ridge.child_link_record_exists() and child.rock_ridge.cl_to_moved_dr is not None and child.rock_ridge.cl_to_moved_dr.parent is not None: # This is a relocated entry. We want to find the entry this was # relocated to; we do that by following the child_link, then going # up to the parent and finding the entry that links to the same one # as this one. cl_parent = child.rock_ridge.cl_to_moved_dr.parent for cl_child in cl_parent.children: if cl_child.rock_ridge is not None and cl_child.rock_ridge.name() == child.rock_ridge.name(): child = cl_child break # If we didn't find the relocated entry in the parent of the moved # entry, weird; just yield the one we would have anyway. yield child def _assign_udf_desc_extents(descs, start_extent): # type: (PyCdlib._UDFDescriptorSequence, int) -> None ''' An internal function to assign a consecutive sequence of extents for the given set of UDF Descriptors, starting at the given extent. Parameters: descs - The PyCdlib._UDFDescriptorSequence object to assign extents for. start_extent - The starting extent to assign from. Returns: Nothing. ''' current_extent = start_extent for pvd in descs.pvds: pvd.set_extent_location(current_extent) current_extent += 1 if descs.desc_pointer.initialized: descs.desc_pointer.set_extent_location(current_extent) current_extent += 1 for impl_use in descs.impl_use: impl_use.set_extent_location(current_extent) current_extent += 1 for partition in descs.partitions: partition.set_extent_location(current_extent) current_extent += 1 for logical_volume in descs.logical_volumes: logical_volume.set_extent_location(current_extent) current_extent += 1 for unallocated_space in descs.unallocated_space: unallocated_space.set_extent_location(current_extent) current_extent += 1 if descs.terminator.initialized: descs.terminator.set_extent_location(current_extent) current_extent += 1 def _find_dr_record_by_name(vd, path, encoding): # type: (headervd.PrimaryOrSupplementaryVD, bytes, str) -> dr.DirectoryRecord ''' An internal function to find an directory record on the ISO given an ISO or Joliet path. If the entry is found, it returns the directory record object corresponding to that entry. If the entry could not be found, a pycdlibexception.PyCdlibInvalidInput exception is raised. Parameters: vd - The Volume Descriptor to look in for the Directory Record. path - The ISO or Joliet entry to find the Directory Record for. encoding - The string encoding used for the path. Returns: The directory record entry representing the entry on the ISO. ''' root_dir_record = vd.root_directory_record() # If the path is just the slash, we want to return the root directory. if path == b'/': return root_dir_record splitpath = utils.split_path(path) currpath = splitpath.pop(0).decode('utf-8').encode(encoding) entry = root_dir_record tmpdr = dr.DirectoryRecord() while True: child = None thelist = entry.children lo = 2 hi = len(thelist) while lo < hi: mid = (lo + hi) // 2 tmpdr.file_ident = currpath if thelist[mid] < tmpdr: lo = mid + 1 else: hi = mid index = lo if index != len(thelist) and thelist[index].file_ident == currpath: child = thelist[index] if child is None: # We failed to find this component of the path, so break out of the # loop and fail. break if child.rock_ridge is not None and child.rock_ridge.child_link_record_exists(): # The rock ridge extension has a child link, so follow it. child = child.rock_ridge.cl_to_moved_dr if child is None: break # We found the child, and it is the last one we are looking for; # return it. if not splitpath: return child if not child.is_dir(): break entry = child currpath = splitpath.pop(0).decode('utf-8').encode(encoding) raise pycdlibexception.PyCdlibInvalidInput('Could not find path') class PyCdlib(object): ''' The main class for manipulating ISOs. ''' __slots__ = ('_initialized', '_cdfp', 'pvds', 'svds', 'vdsts', 'brs', 'pvd', 'rock_ridge', '_always_consistent', '_has_udf', 'eltorito_boot_catalog', 'isohybrid_mbr', 'xa', '_managing_fp', '_needs_reshuffle', '_rr_moved_record', '_rr_moved_name', '_rr_moved_rr_name', 'enhanced_vd', 'joliet_vd', 'version_vd', 'interchange_level', '_write_check_list', '_track_writes', 'udf_beas', 'udf_nsr', 'udf_teas', 'udf_anchors', 'udf_main_descs', 'udf_reserve_descs', 'udf_logical_volume_integrity', 'udf_boots', 'udf_logical_volume_integrity_terminator', 'udf_root', 'udf_file_set', 'udf_file_set_terminator', 'inodes', 'logical_block_size') class _UDFDescriptorSequence(object): ''' A class to represent a UDF Descriptor Sequence. ''' __slots__ = ('pvds', 'impl_use', 'partitions', 'logical_volumes', 'unallocated_space', 'terminator', 'desc_pointer') def __init__(self): # type: () -> None self.pvds = [] # type: List[udfmod.UDFPrimaryVolumeDescriptor] self.impl_use = [] # type: List[udfmod.UDFImplementationUseVolumeDescriptor] self.partitions = [] # type: List[udfmod.UDFPartitionVolumeDescriptor] self.logical_volumes = [] # type: List[udfmod.UDFLogicalVolumeDescriptor] self.unallocated_space = [] # type: List[udfmod.UDFUnallocatedSpaceDescriptor] self.terminator = udfmod.UDFTerminatingDescriptor() self.desc_pointer = udfmod.UDFVolumeDescriptorPointer() def append_to_list(self, which, desc): # type: (str, Union[udfmod.UDFPrimaryVolumeDescriptor, udfmod.UDFImplementationUseVolumeDescriptor, udfmod.UDFPartitionVolumeDescriptor, udfmod.UDFLogicalVolumeDescriptor, udfmod.UDFUnallocatedSpaceDescriptor]) -> None ''' Append a descriptor to the list of descriptors, checking that there are no duplicates. Parameters: which - Which list to append to. desc - The descriptor to check and append. Returns: Nothing. ''' # ECMA-167, Part 3, 8.4.2 says that all Volume Descriptors # with the same sequence numbers should have the same contents. # Check that here. vols = getattr(self, which) for vol in vols: if vol.vol_desc_seqnum == desc.vol_desc_seqnum: if vol != desc: raise pycdlibexception.PyCdlibInvalidISO('Descriptors with same sequence number do not have the same contents') vols.append(desc) def _initialize(self): # type: () -> None ''' An internal method to re-initialize the object. Called from both __init__ and close. Parameters: None. Returns: Nothing. ''' self._cdfp = BytesIO() self.svds = [] # type: List[headervd.PrimaryOrSupplementaryVD] self.brs = [] # type: List[headervd.BootRecord] self.vdsts = [] # type: List[headervd.VolumeDescriptorSetTerminator] self.eltorito_boot_catalog = None # type: Optional[eltorito.EltoritoBootCatalog] self._initialized = False self.rock_ridge = '' self.isohybrid_mbr = None # type: Optional[isohybrid.IsoHybrid] self.xa = False self._managing_fp = False self.pvds = [] # type: List[headervd.PrimaryOrSupplementaryVD] self._has_udf = False self.udf_beas = [] # type: List[udfmod.BEAVolumeStructure] self.udf_boots = [] # type: List[udfmod.UDFBootDescriptor] self.udf_nsr = udfmod.NSRVolumeStructure() self.udf_teas = [] # type: List[udfmod.TEAVolumeStructure] self.udf_anchors = [] # type: List[udfmod.UDFAnchorVolumeStructure] self.udf_main_descs = self._UDFDescriptorSequence() self.udf_reserve_descs = self._UDFDescriptorSequence() self.udf_logical_volume_integrity = None # type: Optional[udfmod.UDFLogicalVolumeIntegrityDescriptor] self.udf_logical_volume_integrity_terminator = None # type: Optional[udfmod.UDFTerminatingDescriptor] self.udf_root = None # type: Optional[udfmod.UDFFileEntry] self.udf_file_set = udfmod.UDFFileSetDescriptor() self.udf_file_set_terminator = None # type: Optional[udfmod.UDFTerminatingDescriptor] self._needs_reshuffle = False self._rr_moved_record = dr.DirectoryRecord() self._rr_moved_name = None # type: Optional[bytes] self._rr_moved_rr_name = None # type: Optional[bytes] self.enhanced_vd = None # type: Optional[headervd.PrimaryOrSupplementaryVD] self.joliet_vd = None # type: Optional[headervd.PrimaryOrSupplementaryVD] self._find_iso_record.cache_clear() # pylint: disable=no-member self._find_rr_record.cache_clear() # pylint: disable=no-member self._find_joliet_record.cache_clear() # pylint: disable=no-member self._find_udf_record.cache_clear() # pylint: disable=no-member self._write_check_list = [] # type: List[PyCdlib._WriteRange] self.version_vd = None # type: Optional[headervd.VersionVolumeDescriptor] self.inodes = [] # type: List[inode.Inode] # We default to a logical block size of 2048; this will be overridden # by the block size from the PVD or the detected block size during an # open. self.logical_block_size = 2048 self.interchange_level = 1 # type: int def _parse_volume_descriptors(self): # type: () -> None ''' An internal method to parse the volume descriptors on an ISO. Parameters: None. Returns: Nothing. ''' # Ecma-119, 6.2.1 says that the Volume Space is divided into a System # Area and a Data Area, where the System Area is in logical sectors 0 # to 15, and whose contents is not specified by the standard. Logical # sectors are 2048 bytes in length, so we start at offset 16 * 2048. self._cdfp.seek(16 * 2048) while True: # All volume descriptors are exactly 2048 bytes long curr_extent = self._cdfp.tell() // 2048 vd = self._cdfp.read(2048) if len(vd) != 2048: raise pycdlibexception.PyCdlibInvalidISO('Failed to read entire volume descriptor') (desc_type, ident) = struct.unpack_from('=B5s', vd, 0) if desc_type not in (headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY, headervd.VOLUME_DESCRIPTOR_TYPE_SET_TERMINATOR, headervd.VOLUME_DESCRIPTOR_TYPE_BOOT_RECORD, headervd.VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY) or ident not in (b'CD001', b'CDW02', b'BEA01', b'NSR02', b'NSR03', b'TEA01', b'BOOT2'): # We read the next extent, and it wasn't a descriptor. Abort # the loop, remembering to back up the input file descriptor. self._cdfp.seek(-2048, os.SEEK_CUR) break if desc_type == headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY: pvd = headervd.PrimaryOrSupplementaryVD(headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY) pvd.parse(vd, curr_extent) self.pvds.append(pvd) elif desc_type == headervd.VOLUME_DESCRIPTOR_TYPE_SET_TERMINATOR: vdst = headervd.VolumeDescriptorSetTerminator() vdst.parse(vd, curr_extent) self.vdsts.append(vdst) elif desc_type == headervd.VOLUME_DESCRIPTOR_TYPE_BOOT_RECORD: # Both an Ecma-119 Boot Record and a Ecma-TR 071 UDF-Bridge # Beginning Extended Area Descriptor have the first byte as 0, # so we can't tell which it is until we look at the next 5 # bytes (Boot Record will have 'CD001', BEAD will have 'BEA01'). if ident == b'CD001': br = headervd.BootRecord() br.parse(vd, curr_extent) self.brs.append(br) elif ident == b'BEA01': self._has_udf = True udf_bea = udfmod.BEAVolumeStructure() udf_bea.parse(vd, curr_extent) self.udf_beas.append(udf_bea) elif ident == b'NSR02': self.udf_nsr.parse(vd, curr_extent) elif ident == b'TEA01': udf_tea = udfmod.TEAVolumeStructure() udf_tea.parse(vd, curr_extent) self.udf_teas.append(udf_tea) elif ident == b'BOOT2': udf_boot = udfmod.UDFBootDescriptor() udf_boot.parse(vd, curr_extent) self.udf_boots.append(udf_boot) else: # This isn't really possible, since we would have aborted # the loop above. raise pycdlibexception.PyCdlibInvalidISO('Invalid volume identification type') elif desc_type == headervd.VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY: svd = headervd.PrimaryOrSupplementaryVD(headervd.VOLUME_DESCRIPTOR_TYPE_SUPPLEMENTARY) svd.parse(vd, curr_extent) self.svds.append(svd) # Since we checked for the valid descriptors above, it is impossible # to see an invalid desc_type here, so no check necessary. # The language in Ecma-119, p.8, Section 6.7.1 says: # # The sequence shall contain one Primary Volume Descriptor (see 8.4) recorded at least once. # # The important bit there is "at least one", which means that we have # to accept ISOs with more than one PVD. if not self.pvds: raise pycdlibexception.PyCdlibInvalidISO('Valid ISO9660 filesystems must have at least one PVD') self.pvd = self.pvds[0] # Make sure any other PVDs agree with the first one. for pvd in self.pvds[1:]: if pvd != self.pvd: raise pycdlibexception.PyCdlibInvalidISO('Multiple occurrences of PVD did not agree!') pvd.root_dir_record = self.pvd.root_dir_record if not self.vdsts: raise pycdlibexception.PyCdlibInvalidISO('Valid ISO9660 filesystems must have at least one Volume Descriptor Set Terminator') def _seek_to_extent(self, extent): # type: (int) -> None ''' An internal method to seek to a particular extent on the input ISO. Parameters: extent - The extent to seek to. Returns: Nothing. ''' self._cdfp.seek(extent * self.logical_block_size) @lru_cache(maxsize=256) def _find_iso_record(self, iso_path): # type: (bytes) -> dr.DirectoryRecord ''' An internal method to find an directory record on the ISO given an ISO path. If the entry is found, it returns the directory record object corresponding to that entry. If the entry could not be found, a pycdlibexception.PyCdlibInvalidInput is raised. Parameters: iso_path - The ISO9660 path to lookup. Returns: The directory record entry representing the entry on the ISO. ''' return _find_dr_record_by_name(self.pvd, iso_path, 'utf-8') @lru_cache(maxsize=256) def _find_rr_record(self, rr_path): # type: (bytes) -> dr.DirectoryRecord ''' An internal method to find an directory record on the ISO given a Rock Ridge path. If the entry is found, it returns the directory record object corresponding to that entry. If the entry could not be found, a pycdlibexception.PyCdlibInvalidInput is raised. Parameters: rr_path - The Rock Ridge path to lookup. Returns: The directory record entry representing the entry on the ISO. ''' root_dir_record = self.pvd.root_directory_record() # If the path is just the slash, return the root directory. if rr_path == b'/': return root_dir_record splitpath = utils.split_path(rr_path) currpath = splitpath.pop(0).decode('utf-8').encode('utf-8') entry = root_dir_record while True: child = None thelist = entry.rr_children # The list could be empty because we don't store dot or dotdot # entries in Rock Ridge. If that is the case, just get out and # fail since we definitely didn't find what we were looking for. if not thelist: break lo = 0 hi = len(thelist) while lo < hi: mid = (lo + hi) // 2 tmpchild = thelist[mid] if tmpchild.rock_ridge is None: raise pycdlibexception.PyCdlibInvalidInput('Record without Rock Ridge entry on Rock Ridge ISO') if tmpchild.rock_ridge.name() < currpath: lo = mid + 1 else: hi = mid index = lo tmpchild = thelist[index] if index != len(thelist) and tmpchild.rock_ridge is not None and tmpchild.rock_ridge.name() == currpath: child = thelist[index] if child is None: # We failed to find this component of the path, so break out of the # loop and fail break if child.rock_ridge is not None and child.rock_ridge.child_link_record_exists(): # Here, the rock ridge extension has a child link, so we # need to follow it. child = child.rock_ridge.cl_to_moved_dr if child is None: break # We found the child, and it is the last one we are looking for; # return it. if not splitpath: return child if not child.is_dir(): break entry = child currpath = splitpath.pop(0).decode('utf-8').encode('utf-8') raise pycdlibexception.PyCdlibInvalidInput('Could not find path') @lru_cache(maxsize=256) def _find_joliet_record(self, joliet_path): # type: (bytes) -> dr.DirectoryRecord ''' An internal method to find an directory record on the ISO given a Joliet path. If the entry is found, it returns the directory record object corresponding to that entry. If the entry could not be found, a pycdlibexception.PyCdlibInvalidInput is raised. Parameters: joliet_path - The Joliet path to lookup. Returns: The directory record entry representing the entry on the ISO. ''' if self.joliet_vd is None: raise pycdlibexception.PyCdlibInternalError('Joliet path requested on non-Joliet ISO') return _find_dr_record_by_name(self.joliet_vd, joliet_path, 'utf-16_be') @lru_cache(maxsize=256) def _find_udf_record(self, udf_path): # type: (bytes) -> Tuple[Optional[udfmod.UDFFileIdentifierDescriptor], udfmod.UDFFileEntry] ''' An internal method to find an directory record on the ISO given a UDF path. If the entry is found, it returns the directory record object corresponding to that entry. If the entry could not be found, a pycdlibexception.PyCdlibInvalidInput is raised. Parameters: udf_path - The UDF path to lookup. Returns: The UDF File Entry representing the entry on the ISO. ''' # If the path is just the slash, return the root directory. if udf_path == b'/': return None, self.udf_root # type: ignore splitpath = utils.split_path(udf_path) currpath = splitpath.pop(0) entry = self.udf_root while entry is not None: child = entry.find_file_ident_desc_by_name(currpath) # We found the child, and it is the last one we are looking for; # return it. if not splitpath: return child, child.file_entry # type: ignore if not child.is_dir(): break entry = child.file_entry currpath = splitpath.pop(0) raise pycdlibexception.PyCdlibInvalidInput('Could not find path') def _iso_name_and_parent_from_path(self, iso_path): # type: (bytes) -> Tuple[bytes, dr.DirectoryRecord] ''' An internal method to find the parent directory record and name given an ISO path. If the parent is found, return a tuple containing the basename of the path and the parent directory record object. Parameters: iso_path - The absolute ISO path to the entry on the ISO. Returns: A tuple containing just the name of the entry and a Directory Record object representing the parent of the entry. ''' splitpath = utils.split_path(iso_path) name = splitpath.pop() parent = self._find_iso_record(b'/' + b'/'.join(splitpath)) return (name.decode('utf-8').encode('utf-8'), parent) def _joliet_name_and_parent_from_path(self, joliet_path): # type: (bytes) -> Tuple[bytes, dr.DirectoryRecord] ''' An internal method to find the parent directory record and name given a Joliet path. If the parent is found, return a tuple containing the basename of the path and the parent directory record object. Parameters: joliet_path - The absolute Joliet path to the entry on the ISO. Returns: A tuple containing just the name of the entry and a Directory Record object representing the parent of the entry. ''' splitpath = utils.split_path(joliet_path) name = splitpath.pop() if len(name) > 64: raise pycdlibexception.PyCdlibInvalidInput('Joliet names can be a maximum of 64 characters') parent = self._find_joliet_record(b'/' + b'/'.join(splitpath)) return (name.decode('utf-8').encode('utf-16_be'), parent) def _udf_name_and_parent_from_path(self, udf_path): # type: (bytes) -> Tuple[bytes, udfmod.UDFFileEntry] ''' An internal method to find the parent directory record and name given a UDF path. If the parent is found, return a tuple containing the basename of the path and the parent UDF File Entry object. Parameters: udf_path - The absolute UDF path to the entry on the ISO. Returns: A tuple containing just the name of the entry and a UDF File Entry object representing the parent of the entry. ''' splitpath = utils.split_path(udf_path) name = splitpath.pop() (parent_ident_unused, parent) = self._find_udf_record(b'/' + b'/'.join(splitpath)) return (name.decode('utf-8').encode('utf-8'), parent) def _set_rock_ridge(self, rr): # type: (str) -> None ''' An internal method to set the Rock Ridge version of the ISO given the Rock Ridge version of the previous entry. Parameters: rr - The version of rr from the last directory record. Returns: Nothing. ''' # We don't allow mixed Rock Ridge versions on the ISO, so apply some # checking. If the current overall Rock Ridge version on the ISO is # None, we upgrade it to whatever version we were given. Once we have # seen a particular version, we only allow records of that version or # None (to account for dotdot records which have no Rock Ridge). if not self.rock_ridge: self.rock_ridge = rr else: for ver in ('1.09', '1.10', '1.12'): if self.rock_ridge == ver: if rr and rr != ver: raise pycdlibexception.PyCdlibInvalidISO('Inconsistent Rock Ridge versions on the ISO!') def _walk_directories(self, vd, extent_to_ptr, extent_to_inode, path_table_records): # type: (headervd.PrimaryOrSupplementaryVD, Dict[int, path_table_record.PathTableRecord], Dict[int, inode.Inode], List[path_table_record.PathTableRecord]) -> Tuple[int, int] ''' An internal method to walk the directory records in a volume descriptor, starting with the root. For each child in the directory record, we create a new dr.DirectoryRecord object and append it to the parent. Parameters: vd - The volume descriptor to walk. extent_to_ptr - A dictionary mapping extents to PTRs. extent_to_inode - A dictionary mapping extents to Inodes. path_table_records - The list of path table records. Returns: The interchange level that this ISO conforms to. ''' cdfp = self._cdfp old_loc = cdfp.tell() cdfp.seek(0, os.SEEK_END) iso_file_length = cdfp.tell() cdfp.seek(old_loc) all_extent_to_dr = {} # type: Dict[int, dr.DirectoryRecord] is_pvd = vd.is_pvd() root_dir_record = vd.root_directory_record() root_dir_record.set_ptr(path_table_records[0]) interchange_level = 1 parent_links = [] child_links = [] lastbyte = 0 dirs = collections.deque([root_dir_record]) while dirs: dir_record = dirs.popleft() self._seek_to_extent(dir_record.extent_location()) length = dir_record.get_data_length() offset = 0 last_record = None # type: Optional[dr.DirectoryRecord] data = cdfp.read(length) while offset < length: if offset > (len(data) - 1): # The data we read off of the ISO was shorter than what we # expected. The ISO is corrupt, throw an error. raise pycdlibexception.PyCdlibInvalidISO('Invalid directory record') lenbyte = bytearray([data[offset]])[0] if lenbyte == 0: # If we saw a zero length, this is probably the padding for # the end of this extent. Move the offset to the start of # the next extent. padsize = self.logical_block_size - (offset % self.logical_block_size) if data[offset:offset + padsize] != b'\x00' * padsize: # For now we are pedantic, and throw an exception if the # padding bytes are not all zero. We may have to loosen # this check depending on what we see in the wild. raise pycdlibexception.PyCdlibInvalidISO('Invalid padding on ISO') offset = offset + padsize continue new_record = dr.DirectoryRecord() rr = new_record.parse(vd, data[offset:offset + lenbyte], dir_record) offset += lenbyte self._set_rock_ridge(rr) # Cache some properties of this record for later use. is_symlink = new_record.is_symlink() dots = new_record.is_dot() or new_record.is_dotdot() rr_cl = new_record.rock_ridge is not None and new_record.rock_ridge.child_link_record_exists() is_dir = new_record.is_dir() data_length = new_record.get_data_length() new_extent_loc = new_record.extent_location() if is_pvd and not dots and not rr_cl and not is_symlink and new_extent_loc not in all_extent_to_dr: all_extent_to_dr[new_extent_loc] = new_record # Some ISOs use random extent locations for zero-length files. # Thus, it is not valid for us to link zero-length files to # other files, as the linkage will be essentially random. # Ignore zero-length files (including symlinks) for linkage. # We don't do the lastbyte calculation on zero-length files for # the same reason. if not is_dir: len_to_use = data_length extent_to_use = new_extent_loc # An important side-effect of this is that zero-length files # or symlinks get an inode, but it is always set to length 0 # and location 0 and not actually written out. This is so # that we can 'link' everything through the Inode. if len_to_use == 0 or is_symlink: len_to_use = 0 extent_to_use = 0 # Directory Records that point to the El Torito Boot Catalog # do not get Inodes since all of that is handled in-memory. if self.eltorito_boot_catalog is not None and extent_to_use == self.eltorito_boot_catalog.extent_location(): self.eltorito_boot_catalog.add_dirrecord(new_record) else: # For real files, create an inode that points to the # location on disk. if extent_to_use in extent_to_inode: ino = extent_to_inode[extent_to_use] else: ino = inode.Inode() ino.parse(extent_to_use, len_to_use, cdfp, self.logical_block_size) extent_to_inode[extent_to_use] = ino self.inodes.append(ino) ino.linked_records.append((new_record, vd == self.pvd)) new_record.inode = ino new_end = extent_to_use * self.logical_block_size + len_to_use if new_end > iso_file_length: # The end of the file is beyond the size of the ISO. # Since this can't be true, truncate the file size. if new_record.inode is not None: new_record.inode.data_length = iso_file_length - extent_to_use * self.logical_block_size for rec, is_pvd in new_record.inode.linked_records: rec.set_data_length(new_end) else: # The new end is still within the file size, but the PVD # size is wrong. Set the lastbyte appropriately, which # will eventually be used to fix the PVD size. lastbyte = max(lastbyte, new_end) if new_record.rock_ridge is not None and new_record.rock_ridge.dr_entries.ce_record is not None: ce_record = new_record.rock_ridge.dr_entries.ce_record orig_pos = cdfp.tell() self._seek_to_extent(ce_record.bl_cont_area) cdfp.seek(ce_record.offset_cont_area, os.SEEK_CUR) con_block = cdfp.read(ce_record.len_cont_area) new_record.rock_ridge.parse(con_block, False, new_record.rock_ridge.bytes_to_skip, True) cdfp.seek(orig_pos) block = self.pvd.track_rr_ce_entry(ce_record.bl_cont_area, ce_record.offset_cont_area, ce_record.len_cont_area) new_record.rock_ridge.update_ce_block(block) if rr_cl: child_links.append(new_record) if is_dir: if new_record.rock_ridge is not None and new_record.rock_ridge.relocated_record(): self._rr_moved_record = new_record if new_record.is_dotdot() and new_record.rock_ridge is not None and new_record.rock_ridge.parent_link_record_exists(): # Make sure to mark a dotdot record with a parent link # record in the parent_links list for later linking. parent_links.append(new_record) if not dots and not rr_cl: dirs.append(new_record) new_record.set_ptr(extent_to_ptr[new_extent_loc]) if new_record.parent is None: raise pycdlibexception.PyCdlibInternalError('Trying to track child with no parent') try_long_entry = False try: new_record.parent.track_child(new_record, self.logical_block_size) except pycdlibexception.PyCdlibInvalidInput: # dir_record.track_child() may throw a PyCdlibInvalidInput if it # saw a duplicate child. However, we allow duplicate children # iff this record is a file and the last child has the same name; # this means we have a very long entry. If that is not the case, # re-raise the error, otherwise pass through to try with the # allow_duplicates flag set to True. if new_record.is_dir() or last_record is None or last_record.file_identifier() != new_record.file_identifier(): raise try_long_entry = True if try_long_entry: new_record.parent.track_child(new_record, self.logical_block_size, True) if is_pvd: if new_record.is_dir(): new_level = _interchange_level_from_directory(new_record.file_identifier()) else: new_level = _interchange_level_from_filename(new_record.file_identifier()) interchange_level = max(interchange_level, new_level) last_record = new_record for pl in parent_links: if pl.rock_ridge is not None: pl.rock_ridge.parent_link = all_extent_to_dr[pl.rock_ridge.parent_link_extent()] for cl in child_links: if cl.rock_ridge is not None: cl.rock_ridge.cl_to_moved_dr = all_extent_to_dr[cl.rock_ridge.child_link_extent()] if cl.rock_ridge.cl_to_moved_dr.rock_ridge is not None: cl.rock_ridge.cl_to_moved_dr.rock_ridge.moved_to_cl_dr = cl return interchange_level, lastbyte def _parse_path_table(self, ptr_size, extent): # type: (int, int) -> Tuple[List[path_table_record.PathTableRecord], Dict[int, path_table_record.PathTableRecord]] ''' An internal method to parse a path table on an ISO. For each path table entry found, a Path Table Record object is created, and the callback is called. Parameters: vd - The volume descriptor that these path table records correspond to. extent - The extent at which this path table record starts. callback - The callback to call for each path table record. Returns: A tuple consisting of the list of path table record entries and a dictionary of the extent locations to the path table record entries. ''' self._seek_to_extent(extent) data = self._cdfp.read(ptr_size) offset = 0 out = [] extent_to_ptr = {} while offset < ptr_size: ptr = path_table_record.PathTableRecord() len_di_byte = bytearray([data[offset]])[0] read_len = path_table_record.PathTableRecord.record_length(len_di_byte) ptr.parse(data[offset:offset + read_len]) out.append(ptr) extent_to_ptr[ptr.extent_location] = ptr offset += read_len return out, extent_to_ptr def _check_and_parse_eltorito(self, br): # type: (headervd.BootRecord) -> None ''' An internal method to examine a Boot Record and see if it is an El Torito Boot Record. If it is, parse the El Torito Boot Catalog, verification entry, initial entry, and any additional section entries. Parameters: br - The boot record to examine for an El Torito signature. Returns: Nothing. ''' if br.boot_system_identifier != b'EL TORITO SPECIFICATION'.ljust(32, b'\x00'): return if self.eltorito_boot_catalog is not None: raise pycdlibexception.PyCdlibInvalidISO('Only one El Torito boot record is allowed') # According to the El Torito specification, section 2.0, the El # Torito boot record must be at extent 17. if br.extent_location() != 17: raise pycdlibexception.PyCdlibInvalidISO('El Torito Boot Record must be at extent 17') # Once we have verified that the BootRecord is an El Torito one and that # it is sane, we parse the El Torito Boot Catalog. self.eltorito_boot_catalog = eltorito.EltoritoBootCatalog(br) # While it is not entirely clear in the El Torito spec, empirical # evidence suggests that the boot catalog extent is always # little-endian, even on big-endian systems eltorito_boot_catalog_extent, = struct.unpack_from(' None ''' An internal method that is one of the keys of PyCdlib's ability to keep the in-memory metadata consistent at all times. After making any changes to the ISO, most API calls end up calling this method. This method will run through the entire ISO, assigning extents to each of the pieces of the ISO that exist. This includes the Primary Volume Descriptor (which is fixed at extent 16), the Boot Records (including El Torito), the Supplementary Volume Descriptors (including Joliet), the Volume Descriptor Terminators, the Version Descriptor, the Primary Volume Descriptor Path Table Records (little and big endian), the Supplementary Volume Descriptor Path Table Records (little and big endian), the Primary Volume Descriptor directory records, the Supplementary Volume Descriptor directory records, the Rock Ridge ER sector, the El Torito Boot Catalog, the El Torito Initial Entry, and finally the data for the files. Parameters: None. Returns: Nothing. ''' current_extent = 16 for pvd in self.pvds: pvd.set_extent_location(current_extent) current_extent += 1 for br in self.brs: br.set_extent_location(current_extent) current_extent += 1 for svd in self.svds: svd.set_extent_location(current_extent) current_extent += 1 for vdst in self.vdsts: vdst.set_extent_location(current_extent) current_extent += 1 if self._has_udf: for bea in self.udf_beas: bea.set_extent_location(current_extent) current_extent += 1 for boot in self.udf_boots: boot.set_extent_location(current_extent) current_extent += 1 self.udf_nsr.set_extent_location(current_extent) current_extent += 1 for tea in self.udf_teas: tea.set_extent_location(current_extent) current_extent += 1 if self.version_vd is not None: # Save off an extent for the version descriptor self.version_vd.set_extent_location(current_extent) current_extent += 1 part_start = 0 udf_files = [] # type: List[inode.Inode] linked_inodes = {} # type: Dict[int, bool] if self._has_udf: if current_extent > 32: # There is no *requirement* in the UDF specification that the # UDF Volume Descriptor Sequence starts at extent 32. It can # start anywhere between extents 16 and 256, as long as the # ISO9660 volume descriptors, the UDF Bridge Volume Recognition # Sequence, Main Volume Descriptor Sequence, Reserve Volume # Descriptor Sequence, and Logical Volume Integrity Sequence all # fit, in that order. The only way that all of these volume # descriptors would not fit between extents 16 and 32 is in the # case of many duplicate PVDs, many VDSTs, or similar. Since # that is unlikely, for now we maintain compatibility with # genisoimage and force the UDF Main Descriptor Sequence to # start at 32. We can change this later if needed. raise pycdlibexception.PyCdlibInternalError('Too many ISO9660 volume descriptors to fit UDF') current_extent = 32 _assign_udf_desc_extents(self.udf_main_descs, current_extent) # ECMA TR-071 2.6 says that the volume sequence will be exactly 16 # extents long, and we know we started at 32, so make it exactly 48. current_extent = 48 _assign_udf_desc_extents(self.udf_reserve_descs, current_extent) # ECMA TR-071 2.6 says that the volume sequence will be exactly 16 # extents long, and we know we started at 48, so make it exactly 64. current_extent = 64 if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.set_extent_location(current_extent) self.udf_main_descs.logical_volumes[0].set_integrity_location(current_extent) self.udf_reserve_descs.logical_volumes[0].set_integrity_location(current_extent) current_extent += 1 if self.udf_logical_volume_integrity_terminator is not None: self.udf_logical_volume_integrity_terminator.set_extent_location(current_extent) current_extent += 1 # Now assign the first UDF anchor at 256. if len(self.udf_anchors) != 2: raise pycdlibexception.PyCdlibInternalError('Expected 2 UDF anchors') # The first UDF anchor is hard-coded at extent 256. We assign the # other anchor later, since it needs to be the last extent. current_extent = 256 self.udf_anchors[0].set_extent_location(current_extent, self.udf_main_descs.pvds[0].extent_location(), self.udf_reserve_descs.pvds[0].extent_location()) current_extent += 1 # Now assign the UDF File Set Descriptor to the beginning of the partition. part_start = current_extent self.udf_file_set.set_extent_location(part_start) self.udf_main_descs.partitions[0].set_start_location(part_start) self.udf_reserve_descs.partitions[0].set_start_location(part_start) current_extent += 1 if self.udf_file_set_terminator is not None: self.udf_file_set_terminator.set_extent_location(current_extent, current_extent - part_start) current_extent += 1 # Assignment of extents to UDF is somewhat complicated. UDF # filesystems are laid out by having one extent containing a # File Entry that describes a directory or a file, followed by # an extent that contains the entries in the case of a directory. # All File Entries and entries containing File Identifier # descriptors are laid out ahead of File Entries for files. The # implementation below alternates assignment to File Entries and # File Descriptors for all directories, and then assigns to all # files. Note that data for files is assigned in the 'normal' # file assignment below. # First assign directories. if self.udf_root is None: raise pycdlibexception.PyCdlibInternalError('ISO has UDF but no UDF root; this should never happen') udf_file_assign_list = [] udf_file_entries = collections.deque([(self.udf_root, None)]) # type: Deque[Tuple[udfmod.UDFFileEntry, Optional[udfmod.UDFFileIdentifierDescriptor]]] while udf_file_entries: udf_file_entry, fi_desc = udf_file_entries.popleft() # In theory we should check for and skip the work for files and # symlinks, but they will never be added to 'udf_file_entries' # to begin with so we can safely ignore them. # Set the location that the File Entry lives at, and update # the File Identifier Descriptor that points to it (for all # but the root). udf_file_entry.set_extent_location(current_extent, current_extent - part_start) if fi_desc is not None: fi_desc.set_icb(current_extent, current_extent - part_start) current_extent += 1 # Now assign where the File Entry points to; for files this # is overwritten later, but for directories this tells us where # to find the extent containing the list of File Identifier # Descriptors that are in this directory. udf_file_entry.set_data_location(current_extent, current_extent - part_start) offset = 0 for d in udf_file_entry.fi_descs: if offset >= self.logical_block_size: # The offset has spilled over into a new extent. # Increase the current extent by one, and update the # offset. Note that the offset does not go to 0, since # UDF allows File Identifier Descs to span extents. # Instead, it is the current offset minus the size of a # block (say 2050 - 2048, leaving us at offset 2). current_extent += 1 offset = offset - self.logical_block_size d.set_extent_location(current_extent, current_extent - part_start) if not d.is_parent() and d.file_entry is not None: if d.is_dir(): udf_file_entries.append((d.file_entry, d)) else: udf_file_assign_list.append((d.file_entry, d)) offset += udfmod.UDFFileIdentifierDescriptor.length(len(d.fi)) if offset > self.logical_block_size: current_extent += 1 current_extent += 1 # Now assign files (this includes symlinks). udf_file_entry_inodes_assigned = {} # type: Dict[int, bool] for udf_file_assign_entry, fi_desc in udf_file_assign_list: if udf_file_assign_entry is None or fi_desc is None: continue if udf_file_assign_entry.inode is not None and id(udf_file_assign_entry.inode) in udf_file_entry_inodes_assigned: continue udf_file_assign_entry.set_extent_location(current_extent, current_extent - part_start) fi_desc.set_icb(current_extent, current_extent - part_start) if udf_file_assign_entry.inode is not None: # The data location for files will be set later. if udf_file_assign_entry.inode.get_data_length() > 0: udf_files.append(udf_file_assign_entry.inode) for rec, pvd_unused in udf_file_assign_entry.inode.linked_records: if isinstance(rec, udfmod.UDFFileEntry): rec.set_extent_location(current_extent, current_extent - part_start) if rec.file_ident is not None: rec.file_ident.set_icb(current_extent, current_extent - part_start) udf_file_entry_inodes_assigned[id(udf_file_assign_entry.inode)] = True current_extent += 1 if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_contents_use.unique_id = current_extent # Next up, put the path table records in the right place. for pvd in self.pvds: pvd.path_table_location_le = current_extent current_extent += self.pvd.path_table_num_extents for pvd in self.pvds: pvd.path_table_location_be = current_extent current_extent += self.pvd.path_table_num_extents if self.enhanced_vd is not None: self.enhanced_vd.path_table_location_le = self.pvd.path_table_location_le self.enhanced_vd.path_table_location_be = self.pvd.path_table_location_be if self.joliet_vd is not None: self.joliet_vd.path_table_location_le = current_extent current_extent += self.joliet_vd.path_table_num_extents self.joliet_vd.path_table_location_be = current_extent current_extent += self.joliet_vd.path_table_num_extents self.pvd.clear_rr_ce_entries() current_extent, pvd_files = _reassign_vd_dirrecord_extents(self.pvd, current_extent) joliet_files = [] # type: List[inode.Inode] if self.joliet_vd is not None: current_extent, joliet_files = _reassign_vd_dirrecord_extents(self.joliet_vd, current_extent) # The rock ridge 'ER' sector must be after all of the directory # entries but before the file contents. rr = self.pvd.root_directory_record().children[0].rock_ridge if rr is not None and rr.dr_entries.ce_record is not None: rr.dr_entries.ce_record.update_extent(current_extent) current_extent += 1 if len(self.udf_anchors) > 2: self.udf_anchors[1].set_extent_location(self.pvd.space_size - 256, self.udf_main_descs.pvds[0].extent_location(), self.udf_reserve_descs.pvds[0].extent_location()) def _set_inode(ino, current_extent, part_start): # type: (inode.Inode, int, int) -> int ''' Internal function to set the location of an inode and update the metadata of all records attached to it. Parameters: ino - The inode to update. current_extent - The extent to set the inode to. part_start - The start of the partition that the inode is on. Returns: The new extent location. ''' if len(self.udf_anchors) > 2 and current_extent == self.pvd.space_size - 256: current_extent += 1 ino.set_extent_location(current_extent) for rec, pvd_unused in ino.linked_records: rec.set_data_location(current_extent, current_extent - part_start) current_extent += utils.ceiling_div(ino.get_data_length(), self.logical_block_size) return current_extent if self.eltorito_boot_catalog is not None: self.eltorito_boot_catalog.update_catalog_extent(current_extent) for rec in self.eltorito_boot_catalog.dirrecords: rec.set_data_location(current_extent, current_extent - part_start) current_extent += utils.ceiling_div(self.eltorito_boot_catalog.dirrecords[0].get_data_length(), self.logical_block_size) class _EltoritoEncapsulation(object): ''' Internal class to encapsulate an El Torito Entry object with additional necessary metadata for sorting. ''' def __init__(self, entry, platform_id, name): self.entry = entry self.platform_id = platform_id self.name = name def __lt__(self, other): return self.name < other.name def _add_entry_to_enc_list(enc_to_update, entry, platform_id): # type: (List[_EltoritoEncapsulation], eltorito.EltoritoEntry, int) -> None added_enc = False for rec, is_pvd in entry.inode.linked_records: if not is_pvd: continue if not isinstance(rec, udfmod.UDFFileEntry) and not isinstance(rec, dr.DirectoryRecord): continue enc = _EltoritoEncapsulation(entry, platform_id, rec.file_identifier()) bisect.insort_right(enc_to_update, enc) added_enc = True if not added_enc: # In this case, the entry wasn't linked into the PVD # filesystem at all. Just add an entry with a dummy name # that is guaranteed to sort first. enc = _EltoritoEncapsulation(entry, platform_id, b'AAAAAAAA.;1') bisect.insort_right(enc_to_update, enc) enc_to_update = [] # type: List[_EltoritoEncapsulation] _add_entry_to_enc_list(enc_to_update, self.eltorito_boot_catalog.initial_entry, self.eltorito_boot_catalog.validation_entry.platform_id) for sec in self.eltorito_boot_catalog.sections: for entry in sec.section_entries: _add_entry_to_enc_list(enc_to_update, entry, sec.platform_id) for entry in self.eltorito_boot_catalog.standalone_entries: _add_entry_to_enc_list(enc_to_update, entry, self.eltorito_boot_catalog.validation_entry.platform_id) num_seen_efi = 0 for enc in enc_to_update: if id(enc.entry.inode) in linked_inodes: continue enc.entry.set_data_location(current_extent, current_extent - part_start) if self.isohybrid_mbr is not None: if enc.platform_id == 0xef: if num_seen_efi == 0: self.isohybrid_mbr.update_efi(current_extent, entry.sector_count, self.pvd.space_size * self.logical_block_size) elif num_seen_efi == 1: self.isohybrid_mbr.update_mac(current_extent, entry.sector_count) else: raise pycdlibexception.PyCdlibInternalError('Only expected two EFI sections') num_seen_efi += 1 elif enc.platform_id == 0: self.isohybrid_mbr.update_rba(current_extent) current_extent = _set_inode(enc.entry.inode, current_extent, part_start) linked_inodes[id(enc.entry.inode)] = True for ino in pvd_files + joliet_files + udf_files: if id(ino) in linked_inodes: # We've already assigned an extent because it was linked to an # earlier entry. continue current_extent = _set_inode(ino, current_extent, part_start) linked_inodes[id(ino)] = True if self.enhanced_vd is not None: loc = self.pvd.root_directory_record().extent_location() self.enhanced_vd.root_directory_record().set_data_location(loc, loc) if self.udf_anchors: self.udf_anchors[-1].set_extent_location(current_extent, self.udf_main_descs.pvds[0].extent_location(), self.udf_reserve_descs.pvds[0].extent_location()) if current_extent > self.pvd.space_size: raise pycdlibexception.PyCdlibInternalError('Assigned an extent beyond the ISO (%d > %d)' % (current_extent, self.pvd.space_size)) self._needs_reshuffle = False def _add_child_to_dr(self, child): # type: (dr.DirectoryRecord) -> int ''' An internal method to add a child to a directory record, expanding the space in the Volume Descriptor(s) if necessary. Parameters: child - The new child. Returns: The number of bytes to add for this directory record (this may be zero). ''' if child.parent is None: raise pycdlibexception.PyCdlibInternalError('Trying to add child without a parent') try_long_entry = False try: ret = child.parent.add_child(child, self.logical_block_size) except pycdlibexception.PyCdlibInvalidInput: # dir_record.add_child() may throw a PyCdlibInvalidInput if # it saw a duplicate child. However, we allow duplicate # children iff the last child is the same; this means that # we have a very long entry. If that is the case, try again # with the allow_duplicates flag set to True. if not child.is_dir(): try_long_entry = True else: raise if try_long_entry: ret = child.parent.add_child(child, self.logical_block_size, True) # The add_child() method returns True if the parent needs another extent # in order to fit the directory record for this child. if ret: return self.logical_block_size return 0 def _remove_child_from_dr(self, child, index): # type: (dr.DirectoryRecord, int) -> int ''' An internal method to remove a child from a directory record, shrinking the space in the Volume Descriptor if necessary. Parameters: child - The child to remove. index - The index of the child into the parent's child array. Returns: The number of bytes to remove for this directory record (this may be zero). ''' if child.parent is None: raise pycdlibexception.PyCdlibInternalError('Trying to remove child from non-existent parent') self._find_iso_record.cache_clear() # pylint: disable=no-member self._find_rr_record.cache_clear() # pylint: disable=no-member self._find_joliet_record.cache_clear() # pylint: disable=no-member # The remove_child() method returns True if the parent no longer needs # the extent that the directory record for this child was on. if child.parent.remove_child(child, index, self.logical_block_size): return self.logical_block_size return 0 def _add_to_ptr_size(self, ptr): # type: (path_table_record.PathTableRecord) -> int ''' An internal method to add a PTR to a VD, adding space to the VD if necessary. Parameters: ptr - The PTR to add to the vd. Returns: The number of additional bytes that are needed to fit the new PTR (this may be zero). ''' num_bytes_to_add = 0 for pvd in self.pvds: # The add_to_ptr_size() method returns True if the PVD needs # additional space in the PTR to store this directory. We always # add 4 additional extents for that (2 for LE, 2 for BE). if pvd.add_to_ptr_size(path_table_record.PathTableRecord.record_length(ptr.len_di)): num_bytes_to_add += 4 * self.logical_block_size return num_bytes_to_add def _remove_from_ptr_size(self, ptr): # type: (path_table_record.PathTableRecord) -> int ''' An internal method to remove a PTR from a VD, removing space from the VD if necessary. Parameters: ptr - The PTR to remove from the VD. Returns: The number of bytes to remove from the VDs (this may be zero). ''' num_bytes_to_remove = 0 for pvd in self.pvds: # The remove_from_ptr_size() returns True if the PVD no longer # needs the extra extents in the PTR that stored this directory. # We always remove 4 additional extents for that. if pvd.remove_from_ptr_size(path_table_record.PathTableRecord.record_length(ptr.len_di)): num_bytes_to_remove += 4 * self.logical_block_size return num_bytes_to_remove def _find_or_create_rr_moved(self): # type: () -> int ''' An internal method to find the /RR_MOVED directory on the ISO. If it already exists, the directory record to it is returned. If it doesn't yet exist, it is created and the directory record to it is returned. Parameters: None. Returns: The number of additional bytes needed for the rr_moved directory (this may be zero). ''' if self._rr_moved_record.initialized: return 0 if self._rr_moved_name is None: self._rr_moved_name = b'RR_MOVED' if self._rr_moved_rr_name is None: self._rr_moved_rr_name = b'rr_moved' # No rr_moved found, so we have to create it. rec = dr.DirectoryRecord() rec.new_dir(self.pvd, self._rr_moved_name, self.pvd.root_directory_record(), self.pvd.sequence_number(), self.rock_ridge, self._rr_moved_rr_name, self.logical_block_size, False, False, self.xa, 0o040555) num_bytes_to_add = self._add_child_to_dr(rec) self._create_dot(self.pvd, rec, self.rock_ridge, self.xa, 0o040555) self._create_dotdot(self.pvd, rec, self.rock_ridge, False, self.xa, 0o040555) # We always need to add an entry to the path table record. ptr = path_table_record.PathTableRecord() ptr.new_dir(self._rr_moved_name) num_bytes_to_add += self.logical_block_size + self._add_to_ptr_size(ptr) rec.set_ptr(ptr) self._rr_moved_record = rec return num_bytes_to_add def _calculate_eltorito_boot_info_table_csum(self, data_fp, data_len): # type: (BinaryIO, int) -> int ''' An internal method to calculate the checksum for an El Torito Boot Info Table. This checksum is a simple 32-bit checksum over all of the data in the boot file, starting right after the Boot Info Table itself. Parameters: data_fp - The file object to read the input data from. data_len - The length of the input file. Returns: An integer representing the 32-bit checksum for the boot info table. ''' # Read the boot file so we can calculate the checksum over it. num_sectors = utils.ceiling_div(data_len, self.logical_block_size) csum = 0 curr_sector = 0 while curr_sector < num_sectors: block = data_fp.read(self.logical_block_size) block = block.ljust(2048, b'\x00') i = 0 if curr_sector == 0: # The first 64 bytes are not included in the checksum. i = 64 while i < len(block): tmp, = struct.unpack_from(' None ''' An internal method to check a boot directory record to see if it has an El Torito Boot Info Table embedded inside of it. Parameters: ino - The Inode to check for a Boot Info Table. Returns: Nothing. ''' orig = self._cdfp.tell() with inode.InodeOpenData(ino, self.logical_block_size) as (data_fp, data_len): data_fp.seek(8, os.SEEK_CUR) bi_table = eltorito.EltoritoBootInfoTable() if bi_table.parse(self.pvd, data_fp.read(eltorito.EltoritoBootInfoTable.header_length()), ino): data_fp.seek(-24, os.SEEK_CUR) # OK, the rest of the stuff checks out; do a final # check to make sure the checksum is reasonable. csum = self._calculate_eltorito_boot_info_table_csum(data_fp, data_len) if csum == bi_table.csum: ino.add_boot_info_table(bi_table) self._cdfp.seek(orig) def _check_rr_name(self, rr_name): # type: (Optional[str]) -> bytes ''' An internal method to check whether this ISO requires or does not require a Rock Ridge path. Parameters: rr_name - The Rock Ridge name. Returns: The Rock Ridge name in bytes if this is a Rock Ridge ISO, None otherwise. ''' if self.rock_ridge: if not rr_name: raise pycdlibexception.PyCdlibInvalidInput('A rock ridge name must be passed for a rock-ridge ISO') if rr_name.count('/') != 0: raise pycdlibexception.PyCdlibInvalidInput('A rock ridge name must be relative') return rr_name.encode('utf-8') if rr_name: raise pycdlibexception.PyCdlibInvalidInput('A rock ridge name can only be specified for a rock-ridge ISO') return b'' def _normalize_joliet_path(self, joliet_path): # type: (str) -> bytes ''' An internal method to check whether this ISO does or does not require a Joliet path. If a Joliet path is required, the path is normalized and returned. Parameters: joliet_path - The joliet_path to normalize (if necessary). Returns: The normalized joliet_path if this ISO has Joliet, None otherwise. ''' tmp_path = b'' if self.joliet_vd is not None: if not joliet_path: raise pycdlibexception.PyCdlibInvalidInput('A Joliet path must be passed for a Joliet ISO') tmp_path = utils.normpath(joliet_path) else: if joliet_path: raise pycdlibexception.PyCdlibInvalidInput('A Joliet path can only be specified for a Joliet ISO') return tmp_path def _link_eltorito(self, extent_to_inode): # type: (Dict[int, inode.Inode]) -> None ''' An internal method to link the El Torito entries into their corresponding Directory Records, creating new ones if they are 'hidden'. Should only be called on an El Torito ISO. Parameters: extent_to_inode - The map that maps extents to Inodes. Returns: Nothing. ''' if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInternalError('Trying to link El Torito entries on a non-El Torito ISO') entries_to_assign = [self.eltorito_boot_catalog.initial_entry] for sec in self.eltorito_boot_catalog.sections: for entry in sec.section_entries: entries_to_assign.append(entry) for entry in entries_to_assign: entry_extent = entry.get_rba() if entry_extent in extent_to_inode: ino = extent_to_inode[entry_extent] else: ino = inode.Inode() ino.parse(entry_extent, entry.length(), self._cdfp, self.logical_block_size) extent_to_inode[entry_extent] = ino self.inodes.append(ino) ino.linked_records.append((entry, False)) entry.set_inode(ino) def _parse_udf_vol_descs(self, extent, length, descs): # type: (int, int, PyCdlib._UDFDescriptorSequence) -> None ''' An internal method to parse a set of UDF Volume Descriptors. Parameters: extent - The extent at which to start parsing. length - The number of bytes to read from the incoming ISO. descs - The _UDFDescriptorSequence object to store parsed objects into. Returns: Nothing. ''' # Read in the Volume Descriptor Sequence. self._seek_to_extent(extent) vd_data = self._cdfp.read(length) # And parse it. Since the sequence doesn't have to be in any set order, # and since some of the entries may be missing, we parse the Descriptor # Tag (the first 16 bytes) to find out what kind of descriptor it is, # then construct the correct type based on that. We keep going until we # see a Terminating Descriptor. offset = 0 current_extent = extent done = False while not done: desc_tag = udfmod.UDFTag() desc_tag.parse(vd_data[offset:], current_extent) if desc_tag.tag_ident == 1: pvd = udfmod.UDFPrimaryVolumeDescriptor() pvd.parse(vd_data[offset:offset + 512], current_extent, desc_tag) descs.append_to_list('pvds', pvd) elif desc_tag.tag_ident == 3: descs.desc_pointer.parse(vd_data[offset:offset + 512], current_extent, desc_tag) done = True elif desc_tag.tag_ident == 4: impl_use = udfmod.UDFImplementationUseVolumeDescriptor() impl_use.parse(vd_data[offset:offset + 512], current_extent, desc_tag) descs.append_to_list('impl_use', impl_use) elif desc_tag.tag_ident == 5: partition = udfmod.UDFPartitionVolumeDescriptor() partition.parse(vd_data[offset:offset + 512], current_extent, desc_tag) descs.append_to_list('partitions', partition) elif desc_tag.tag_ident == 6: logical_volume = udfmod.UDFLogicalVolumeDescriptor() logical_volume.parse(vd_data[offset:offset + 512], current_extent, desc_tag) descs.append_to_list('logical_volumes', logical_volume) elif desc_tag.tag_ident == 7: unallocated_space = udfmod.UDFUnallocatedSpaceDescriptor() unallocated_space.parse(vd_data[offset:offset + 512], current_extent, desc_tag) descs.append_to_list('unallocated_space', unallocated_space) elif desc_tag.tag_ident == 8: descs.terminator.parse(current_extent, desc_tag) done = True elif desc_tag.tag_ident == 0: # This would be an unrecorded sector, so we are done. done = True else: raise pycdlibexception.PyCdlibInvalidISO('UDF Tag identifier not %d' % (desc_tag.tag_ident)) offset += self.logical_block_size current_extent += 1 if not descs.pvds: raise pycdlibexception.PyCdlibInvalidISO('At least one UDF Primary Volume Descriptor required') saw_zero_desc_num = False for pvd in descs.pvds: if pvd.desc_num == 0: if saw_zero_desc_num: raise pycdlibexception.PyCdlibInvalidISO('Only one UDF Primary Volume Descriptor can have a descriptor number 0') saw_zero_desc_num = True def _parse_udf_descriptors(self): # type: () -> None ''' An internal method to parse the UDF descriptors on the ISO. This should only be called if it the ISO has a valid UDF Volume Recognition Sequence at the beginning of the ISO. Parameters: None. Returns: Nothing. ''' # Parse the anchors. According to ECMA-167, Part 3, 8.4.2.1, there # must be anchors recorded in at least two of the three extent locations # 256, N-256, and N, where N is the total number of extents on the disc. # We'll preserve all 3 if they exist, with a minimum of two for a valid # disc. self._cdfp.seek(0, os.SEEK_END) last_extent = self.pvd.space_size - 1 anchor_locations = [256, last_extent - 256, last_extent] for loc in anchor_locations: self._seek_to_extent(loc) extent = self._cdfp.tell() // self.logical_block_size anchor_data = self._cdfp.read(self.logical_block_size) anchor_tag = udfmod.UDFTag() try: anchor_tag.parse(anchor_data, extent) except pycdlibexception.PyCdlibInvalidISO: continue if anchor_tag.tag_ident != 2: continue anchor = udfmod.UDFAnchorVolumeStructure() anchor.parse(anchor_data, extent, anchor_tag) self.udf_anchors.append(anchor) if len(self.udf_anchors) < 2: raise pycdlibexception.PyCdlibInvalidISO('Expected at least 2 UDF Anchors') for anchor in self.udf_anchors[1:]: if self.udf_anchors[0] != anchor: raise pycdlibexception.PyCdlibInvalidISO('Anchor points do not match') # ECMA-167, Part 3, 8.4.2 says that the anchors identify the main # volume descriptor sequence, so look for it here. # Parse the Main Volume Descriptor Sequence. self._parse_udf_vol_descs(self.udf_anchors[0].main_vd.extent_location, self.udf_anchors[0].main_vd.extent_length, self.udf_main_descs) # ECMA-167, Part 3, 8.4.2 and 8.4.2.2 says that the anchors *may* # identify a reserve volume descriptor sequence. 10.2.3 says that # a reserve volume sequence is identified if the length is > 0. if self.udf_anchors[0].reserve_vd.extent_length > 0: # Parse the Reserve Volume Descriptor Sequence. self._parse_udf_vol_descs(self.udf_anchors[0].reserve_vd.extent_location, self.udf_anchors[0].reserve_vd.extent_length, self.udf_reserve_descs) # ECMA-167, Part 3, 10.6.12 says that the integrity sequence extent # only exists if the length is > 0. if self.udf_main_descs.logical_volumes[0].integrity_sequence.extent_length > 0: # Parse the Logical Volume Integrity Sequence. self._seek_to_extent(self.udf_main_descs.logical_volumes[0].integrity_sequence.extent_location) integrity_data = self._cdfp.read(self.udf_main_descs.logical_volumes[0].integrity_sequence.extent_length) offset = 0 current_extent = self.udf_main_descs.logical_volumes[0].integrity_sequence.extent_location desc_tag = udfmod.UDFTag() desc_tag.parse(integrity_data[offset:], current_extent) if desc_tag.tag_ident != 9: raise pycdlibexception.PyCdlibInvalidISO('UDF Volume Integrity Tag identifier not 9') self.udf_logical_volume_integrity = udfmod.UDFLogicalVolumeIntegrityDescriptor() self.udf_logical_volume_integrity.parse(integrity_data[offset:offset + 512], current_extent, desc_tag) offset += self.logical_block_size if len(integrity_data) >= (offset + self.logical_block_size): current_extent += 1 desc_tag = udfmod.UDFTag() desc_tag.parse(integrity_data[offset:], current_extent) if desc_tag.tag_ident != 8: raise pycdlibexception.PyCdlibInvalidISO('UDF Logical Volume Integrity Terminator Tag identifier not 8') self.udf_logical_volume_integrity_terminator = udfmod.UDFTerminatingDescriptor() self.udf_logical_volume_integrity_terminator.parse(current_extent, desc_tag) # Now look for the File Set Descriptor. current_extent = self.udf_main_descs.partitions[0].part_start_location self._seek_to_extent(current_extent) # Read the data for the File Set and File Terminator together file_set_and_term_data = self._cdfp.read(2 * self.logical_block_size) desc_tag = udfmod.UDFTag() desc_tag.parse(file_set_and_term_data[:self.logical_block_size], 0) if desc_tag.tag_ident != 256: raise pycdlibexception.PyCdlibInvalidISO('UDF File Set Tag identifier not 256') self.udf_file_set.parse(file_set_and_term_data[:self.logical_block_size], current_extent, desc_tag) current_extent += 1 desc_tag = udfmod.UDFTag() desc_tag.parse(file_set_and_term_data[self.logical_block_size:], current_extent - self.udf_main_descs.partitions[0].part_start_location) if desc_tag.tag_ident != 8: raise pycdlibexception.PyCdlibInvalidISO('UDF File Set Terminator Tag identifier not 8') self.udf_file_set_terminator = udfmod.UDFTerminatingDescriptor() self.udf_file_set_terminator.parse(current_extent, desc_tag) def _parse_udf_file_entry(self, abs_file_entry_extent, icb, parent): # type: (int, udfmod.UDFLongAD, Optional[udfmod.UDFFileEntry]) -> Optional[udfmod.UDFFileEntry] ''' An internal method to parse a single UDF File Entry and return the corresponding object. Parameters: abs_file_entry_extent - The extent number the file entry starts at. icb - The ICB object for the data. parent - The parent of the UDF File Entry. Returns: A UDF File Entry object corresponding to the on-disk File Entry. ''' self._seek_to_extent(abs_file_entry_extent) icbdata = self._cdfp.read(icb.extent_length) if all(v == 0 for v in bytearray(icbdata)): # We have seen ISOs in the wild (Windows 2008 Datacenter Enterprise # Standard SP2 x86 DVD) where the UDF File Identifier points to a # UDF File Entry of all zeros. In those cases, we just keep the # File Identifier, and keep the UDF File Entry blank. return None desc_tag = udfmod.UDFTag() desc_tag.parse(icbdata, icb.log_block_num) if desc_tag.tag_ident != 261: raise pycdlibexception.PyCdlibInvalidISO('UDF File Entry Tag identifier not 261') file_entry = udfmod.UDFFileEntry() file_entry.parse(icbdata, abs_file_entry_extent, parent, desc_tag) return file_entry def _walk_udf_directories(self, extent_to_inode): # type: (Dict[int, inode.Inode]) -> None ''' An internal method to walk a UDF filesystem and add all the metadata to this object. Parameters: extent_to_inode - A map from extent numbers to Inodes. Returns: Nothing. ''' part_start = self.udf_main_descs.partitions[0].part_start_location self.udf_root = self._parse_udf_file_entry(part_start + self.udf_file_set.root_dir_icb.log_block_num, self.udf_file_set.root_dir_icb, None) udf_file_entries = collections.deque([self.udf_root]) while udf_file_entries: udf_file_entry = udf_file_entries.popleft() if udf_file_entry is None: continue for desc in udf_file_entry.alloc_descs: abs_file_ident_extent = part_start + desc.log_block_num self._seek_to_extent(abs_file_ident_extent) self._cdfp.seek(desc.offset, 1) data = self._cdfp.read(desc.extent_length) offset = 0 while offset < len(data): current_extent = (abs_file_ident_extent * self.logical_block_size + offset) // self.logical_block_size desc_tag = udfmod.UDFTag() desc_tag.parse(data[offset:], current_extent - part_start) if desc_tag.tag_ident != 257: raise pycdlibexception.PyCdlibInvalidISO('UDF File Identifier Tag identifier not 257') file_ident = udfmod.UDFFileIdentifierDescriptor() offset += file_ident.parse(data[offset:], current_extent, desc_tag, udf_file_entry) if file_ident.is_parent(): # For a parent, no further work to do. udf_file_entry.track_file_ident_desc(file_ident) continue abs_file_entry_extent = part_start + file_ident.icb.log_block_num next_entry = self._parse_udf_file_entry(abs_file_entry_extent, file_ident.icb, udf_file_entry) # For a non-parent, we delay adding this to the list of # fi_descs until after we check whether this is a valid # entry or not. udf_file_entry.track_file_ident_desc(file_ident) if next_entry is None: if file_ident.is_dir(): raise pycdlibexception.PyCdlibInvalidISO('Empty UDF File Entry for directories are not allowed') # If the next_entry is None, then we just skip the # rest of the code dealing with the entry and the # Inode. continue file_ident.file_entry = next_entry next_entry.file_ident = file_ident if file_ident.is_dir(): udf_file_entries.append(next_entry) else: if next_entry.get_data_length() > 0: abs_file_data_extent = part_start + next_entry.alloc_descs[0].log_block_num else: abs_file_data_extent = 0 if self.eltorito_boot_catalog is not None and abs_file_data_extent == self.eltorito_boot_catalog.extent_location(): self.eltorito_boot_catalog.add_dirrecord(next_entry) else: if abs_file_data_extent in extent_to_inode: ino = extent_to_inode[abs_file_data_extent] else: ino = inode.Inode() ino.parse(abs_file_data_extent, next_entry.get_data_length(), self._cdfp, self.logical_block_size) extent_to_inode[abs_file_data_extent] = ino self.inodes.append(ino) ino.linked_records.append((next_entry, False)) next_entry.inode = ino def _open_fp(self, fp): # type: (IO) -> None ''' An internal method to open an existing ISO for inspection and modification. Note that the file object passed in here must stay open for the lifetime of this object, as the PyCdlib class uses it internally to do writing and reading operations. Parameters: fp - The file object containing the ISO to open up. Returns: Nothing. ''' if hasattr(fp, 'mode') and 'b' not in fp.mode: raise pycdlibexception.PyCdlibInvalidInput("The file to open must be in binary mode (add 'b' to the open flags)") self._cdfp = fp # Get the Primary Volume Descriptor (pvd), the set of Supplementary # Volume Descriptors (svds), the set of Volume Partition # Descriptors (vpds), the set of Boot Records (brs), and the set of # Volume Descriptor Set Terminators (vdsts) self._parse_volume_descriptors() self.logical_block_size = self.pvd.logical_block_size() old = self._cdfp.tell() self._cdfp.seek(0) tmp_isohybrid = isohybrid.IsoHybrid() if tmp_isohybrid.parse(self._cdfp.read(16 * 2048)): if tmp_isohybrid.efi: # If we have an EFI partition, we now need to go find the backup # LBA and parse that. The backup_lba attribute tells us the # location of the GPT Header, which is *after* the backup # partition information, so we first parse that, then go find # out how many more partitions we need to parse. self._cdfp.seek(tmp_isohybrid.primary_gpt.header.backup_lba * 512) tmp_isohybrid.parse_secondary_gpt_header(self._cdfp.read(512)) self._cdfp.seek((tmp_isohybrid.secondary_gpt.header.current_lba * 512) - (tmp_isohybrid.secondary_gpt.header.num_parts * 128)) tmp_isohybrid.parse_secondary_gpt_partitions(self._cdfp.read(tmp_isohybrid.secondary_gpt.header.num_parts * 128)) # We only save the object if it turns out to be a valid IsoHybrid. self.isohybrid_mbr = tmp_isohybrid self._cdfp.seek(old) if self.pvd.application_use[141:149] == b'CD-XA001': self.xa = True for br in self.brs: self._check_and_parse_eltorito(br) # Now that we have the PVD, parse the Path Tables according to Ecma-119 # section 9.4. We want to ensure that the big endian versions agree # with the little endian ones (to make sure it is a valid ISO). # Little Endian first. le_ptrs, extent_to_ptr = self._parse_path_table(self.pvd.path_table_size(), self.pvd.path_table_location_le) # Big Endian next. tmp_be_ptrs, e_unused = self._parse_path_table(self.pvd.path_table_size(), self.pvd.path_table_location_be) for index, ptr in enumerate(le_ptrs): if not ptr.equal_to_be(tmp_be_ptrs[index]): raise pycdlibexception.PyCdlibInvalidISO('Little-endian and big-endian path table records do not agree') self.interchange_level = 1 for svd in self.svds: if svd.version == 2 and svd.file_structure_version == 2: self.interchange_level = 4 break extent_to_inode = {} # type: Dict[int, inode.Inode] # Parse all of the files starting from the PVD root directory record. ic_level, lastbyte = self._walk_directories(self.pvd, extent_to_ptr, extent_to_inode, le_ptrs) if self.eltorito_boot_catalog is not None: if not self.eltorito_boot_catalog.dirrecords: # We expect the boot catalog to have at *least* one directory # record attached. If we run across an ISO that doesn't have # that, we attach a "fake" one so that later steps do the right # thing. Note that this will never be written out since we # don't add it to the main PVD directory structure. new_record = dr.DirectoryRecord() new_record.new_file(self.pvd, self.logical_block_size, b'FAKEELT.;1', self.pvd.root_directory_record(), 0, '', b'', False, 0) self.eltorito_boot_catalog.add_dirrecord(new_record) self.interchange_level = max(self.interchange_level, ic_level) # After we have walked the directories we look to see if all of the # El Torito entries have corresponding directory records. If not, the # El Torito records may be 'hidden' or 'unlinked', meaning they have no # corresponding directory record in the ISO filesystem. In order to # accommodate the rest of the system which expects them to have # directory records, we use fake directory records that don't get # written out. # # Note that we specifically do *not* add these to any sort of parent; # that way, we don't run afoul of any checks that adding a child to a # parent might have. This means that if we do ever want to unhide this # entry, we'll have to do some additional work to give it a real name # and link it to the appropriate parent. if self.eltorito_boot_catalog is not None: self._link_eltorito(extent_to_inode) # Now that everything has a dirrecord, see if we have a boot # info table. self._check_for_eltorito_boot_info_table(self.eltorito_boot_catalog.initial_entry.inode) for sec in self.eltorito_boot_catalog.sections: for entry in sec.section_entries: self._check_for_eltorito_boot_info_table(entry.inode) # The PVD is finished. Now look to see if we need to parse the SVD. for svd in self.svds: if (svd.flags & 0x1) == 0 and svd.escape_sequences[:3] in (b'%/@', b'%/C', b'%/E'): if self.joliet_vd is not None: raise pycdlibexception.PyCdlibInvalidISO('Only a single Joliet SVD is supported') self.joliet_vd = svd le_ptrs, joliet_extent_to_ptr = self._parse_path_table(svd.path_table_size(), svd.path_table_location_le) tmp_be_ptrs, j_unused = self._parse_path_table(svd.path_table_size(), svd.path_table_location_be) for index, ptr in enumerate(le_ptrs): if not ptr.equal_to_be(tmp_be_ptrs[index]): raise pycdlibexception.PyCdlibInvalidISO('Joliet little-endian and big-endian path table records do not agree') self._walk_directories(svd, joliet_extent_to_ptr, extent_to_inode, le_ptrs) elif svd.version == 2 and svd.file_structure_version == 2: if self.enhanced_vd is not None: raise pycdlibexception.PyCdlibInvalidISO('Only a single enhanced VD is supported') self.enhanced_vd = svd # We've seen ISOs in the wild (Office XP) that have a PVD space size # that is smaller than the location of the last directory record # extent + length. If we see this, automatically update the size in the # PVD (and any SVDs) so that subsequent operations will be correct. if lastbyte > self.pvd.space_size * self.logical_block_size: new_pvd_size = utils.ceiling_div(lastbyte, self.logical_block_size) for pvd in self.pvds: pvd.space_size = new_pvd_size if self.joliet_vd is not None: self.joliet_vd.space_size = new_pvd_size if self.enhanced_vd is not None: self.enhanced_vd.space_size = new_pvd_size # Look to see if this is a UDF volume. It is one if we have a UDF BEA, # UDF NSR, and UDF TEA, in which case we parse the UDF descriptors and # walk the filesystem. if self._has_udf: self._parse_udf_descriptors() self._walk_udf_directories(extent_to_inode) # Now we look for the 'version' volume descriptor, common on ISOs made # with genisoimage or mkisofs. This volume descriptor doesn't have any # specification, but from code inspection, it is either a completely # zero extent, or starts with 'MKI'. Further, it starts directly after # the VDST, or directly after the UDF recognition sequence (if this is # a UDF ISO). Thus, we go looking for it at those places, and add it # if we find it there. version_vd_extent = self.vdsts[0].extent_location() + 1 if self._has_udf: version_vd_extent = self.udf_teas[0].extent_location() + 1 version_vd = headervd.VersionVolumeDescriptor() self._cdfp.seek(version_vd_extent * self.logical_block_size) if version_vd.parse(self._cdfp.read(self.logical_block_size), version_vd_extent): self.version_vd = version_vd self._initialized = True def _get_and_write_fp(self, iso_path, outfp, blocksize): # type: (bytes, BinaryIO, int) -> None ''' An internal method to fetch a single file from the ISO and write it out to the file object. Parameters: iso_path - The absolute path to the file to get data from. outfp - The file object to write data to. blocksize - The blocksize to use when copying data. Returns: Nothing. ''' if self.joliet_vd is not None: try: return self._get_file_from_iso_fp(outfp, blocksize, None, None, iso_path) except pycdlibexception.PyCdlibInvalidInput as err: if str(err) == 'Could not find path': pass else: raise saved_exception = None try: return self._get_file_from_iso_fp(outfp, blocksize, iso_path, None, None) except pycdlibexception.PyCdlibException as err: if str(err) == 'Could not find path': saved_exception = err else: raise if self.rock_ridge != '': return self._get_file_from_iso_fp(outfp, blocksize, None, iso_path, None) if saved_exception is not None: raise saved_exception def _udf_get_file_from_iso_fp(self, outfp, blocksize, udf_path): # type: (BinaryIO, int, bytes) -> None ''' An internal method to fetch a single UDF file from the ISO and write it out to the file object. Parameters: outfp - The file object to write data to. blocksize - The number of bytes in each transfer. udf_path - The absolute UDF path to lookup on the ISO. Returns: Nothing. ''' if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a udf_path from a non-UDF ISO') (ident_unused, found_file_entry) = self._find_udf_record(udf_path) if found_file_entry is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot get the contents of an empty UDF File Entry') if not found_file_entry.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Can only write out a file') if found_file_entry.inode is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot write out an entry without data') if found_file_entry.get_data_length() > 0: with inode.InodeOpenData(found_file_entry.inode, self.logical_block_size) as (data_fp, data_len): utils.copy_data(data_len, blocksize, data_fp, outfp) def _get_file_from_iso_fp(self, outfp, blocksize, iso_path, rr_path, joliet_path): # type: (BinaryIO, int, Optional[bytes], Optional[bytes], Optional[bytes]) -> None ''' An internal method to fetch a single file from the ISO and write it out to the file object. Parameters: outfp - The file object to write data to. blocksize - The number of bytes in each transfer. iso_path - The absolute ISO9660 path to lookup on the ISO (exclusive with rr_path and joliet_path). rr_path - The absolute Rock Ridge path to lookup on the ISO (exclusive with iso_path and joliet_path). joliet_path - The absolute Joliet path to lookup on the ISO (exclusive with iso_path and rr_path). Returns: Nothing. ''' if joliet_path is not None: if self.joliet_vd is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a joliet_path from a non-Joliet ISO') found_record = self._find_joliet_record(joliet_path) elif rr_path is not None: if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a rr_path from a non-Rock Ridge ISO') found_record = self._find_rr_record(rr_path) elif iso_path is not None: found_record = self._find_iso_record(iso_path) else: raise pycdlibexception.PyCdlibInternalError('Invalid path passed to get_file_from_iso_fp') if found_record.is_dir(): raise pycdlibexception.PyCdlibInvalidInput('Cannot write out a directory') if rr_path is not None or iso_path is not None: if found_record.is_symlink(): # If this Rock Ridge record is a symlink, it has no data # associated with it, so it makes no sense to try and get the # data. In theory, we could follow the symlink to the # appropriate place and get the data of the thing it points to. # However, Rock Ridge symlinks are allowed to point *outside* # of this ISO, so it is really not clear that this is something # we want to do. For now we make the user follow the symlink # themselves if they want to get the data. We can revisit this # decision in the future if we need to. raise pycdlibexception.PyCdlibInvalidInput('Symlinks have no data associated with them') if self.eltorito_boot_catalog is not None: for rec in self.eltorito_boot_catalog.dirrecords: if isinstance(rec, udfmod.UDFFileEntry): continue if rec.file_ident == found_record.file_ident and rec.parent == found_record.parent: recdata = self.eltorito_boot_catalog.record() outfp.write(recdata) utils.zero_pad(outfp, len(recdata), self.logical_block_size) return if found_record.inode is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot write out a file without data') while found_record.get_data_length() > 0: with inode.InodeOpenData(found_record.inode, self.logical_block_size) as (data_fp, data_len): # Copy the data into the output file descriptor. If a boot info # table is present, overlay the table over bytes 8-64 of the # file. Note that we never return more bytes than the length # of the file, so the boot info table may get truncated. if found_record.inode.boot_info_table is not None: header_len = min(data_len, 8) outfp.write(data_fp.read(header_len)) data_len -= header_len if data_len > 0: bi_rec = found_record.inode.boot_info_table.record() table_len = min(data_len, len(bi_rec)) outfp.write(bi_rec[:table_len]) data_len -= table_len if data_len > 0: data_fp.seek(len(bi_rec), os.SEEK_CUR) utils.copy_data(data_len, blocksize, data_fp, outfp) else: utils.copy_data(data_len, blocksize, data_fp, outfp) if found_record.data_continuation is not None: found_record = found_record.data_continuation else: break class _WriteRange(object): ''' A class to store the offset and length of a written section of data. A sorted list of these is used to determine whether we are unintentionally spending time rewriting data that we have already written. ''' __slots__ = ('offset', 'length') def __init__(self, start, end): # type: (int, int) -> None self.offset = start self.length = start + (end - start) def __lt__(self, other): # When we go to insert this into the list, we determine if this one # overlaps with the one we are currently looking at. if range(max(other.offset, self.offset), min(other.length, self.length) + 1): raise pycdlibexception.PyCdlibInternalError('Overlapping write %s, %s' % (repr(self), repr(other))) return self.offset < other.offset def __repr__(self): return 'WriteRange: %s %s' % (self.offset, self.length) def _outfp_write_with_check(self, outfp, data, enable_overwrite_check=True): # type: (BinaryIO, bytes, bool) -> None ''' Internal method to write data out to the output file descriptor, ensuring that it doesn't go beyond the bounds of the ISO. Parameters: outfp - The file object to write to. data - The actual data to write. enable_overwrite_check - Whether to do overwrite checking if it is enabled. Some pieces of code explicitly want to overwrite data, so this allows them to disable the checking. Returns: Nothing. ''' start = outfp.tell() outfp.write(data) if self._track_writes: # After the write, double check that we didn't write beyond the # boundary of the PVD, and raise a PyCdlibException if we do. end = outfp.tell() if end > self.pvd.space_size * self.logical_block_size: raise pycdlibexception.PyCdlibInternalError('Wrote past the end of the ISO! (%d > %d)' % (end, self.pvd.space_size * self.logical_block_size)) if enable_overwrite_check: bisect.insort_left(self._write_check_list, self._WriteRange(start, end - 1)) def _output_file_data(self, outfp, blocksize, ino): # type: (BinaryIO, int, inode.Inode) -> int ''' Internal method to write a directory record entry out. Parameters: outfp - The file object to write the data to. blocksize - The blocksize to use when writing the data out. ino - The Inode to write. Returns: The total number of bytes written out. ''' outfp.seek(ino.extent_location() * self.logical_block_size) tmp_start = outfp.tell() with inode.InodeOpenData(ino, self.logical_block_size) as (data_fp, data_len): utils.copy_data(data_len, blocksize, data_fp, outfp) utils.zero_pad(outfp, data_len, self.logical_block_size) if self._track_writes: end = outfp.tell() bisect.insort_left(self._write_check_list, self._WriteRange(tmp_start, end - 1)) # If this file is being used as a bootfile, and a boot info table is # present, patch the boot info table into offset 8 here. if ino.boot_info_table is not None: old = outfp.tell() outfp.seek(tmp_start + 8) self._outfp_write_with_check(outfp, ino.boot_info_table.record(), enable_overwrite_check=False) outfp.seek(old) return outfp.tell() - tmp_start class _Progress(object): ''' An inner class to deal with progress. ''' __slots__ = ('done', 'total', 'progress_cb', 'progress_opaque') def __init__(self, total, progress_cb, progress_opaque): # type: (int, Optional[Callable[[int, int, Any], None]], Optional[Any]) -> None self.done = 0 self.total = total self.progress_cb = progress_cb self.progress_opaque = progress_opaque def call(self, length): # type: (int) -> None ''' Add the length to done, then call progress_cb if it is not None. ''' self.done += length if self.done > self.total: self.done = self.total if self.progress_cb is not None: with warnings.catch_warnings(): warnings.simplefilter('ignore') if len(inspect.getargspec(self.progress_cb).args) == 2: # pylint: disable=W1505 self.progress_cb(self.done, self.total) # type: ignore else: self.progress_cb(self.done, self.total, self.progress_opaque) def finish(self): # type: () -> None ''' If the progress_cb is not None, call progress_cb with the final total. ''' # In almost all cases, this will cause self.done to wildly # overflow the total size. However, with the hard cap in # call, this works just fine. self.call(self.total) def _write_directory_records(self, vd, outfp, progress): # type: (headervd.PrimaryOrSupplementaryVD, BinaryIO, PyCdlib._Progress) -> None ''' An internal method to write out the directory records from a particular Volume Descriptor. Parameters: vd - The Volume Descriptor to write the Directory Records from. outfp - The file object to write data to. progress - The _Progress object to use for outputting progress. Returns: Nothing. ''' le_ptr_offset = 0 be_ptr_offset = 0 dirs = collections.deque([vd.root_directory_record()]) while dirs: curr = dirs.popleft() curr_dirrecord_offset = 0 if curr.is_dir(): if curr.ptr is None: raise pycdlibexception.PyCdlibInternalError('Directory has no Path Table Record') # Little Endian PTR outfp.seek(vd.path_table_location_le * self.logical_block_size + le_ptr_offset) ret = curr.ptr.record_little_endian() self._outfp_write_with_check(outfp, ret) le_ptr_offset += len(ret) # Big Endian PTR outfp.seek(vd.path_table_location_be * self.logical_block_size + be_ptr_offset) ret = curr.ptr.record_big_endian() self._outfp_write_with_check(outfp, ret) be_ptr_offset += len(ret) progress.call(curr.get_data_length()) dir_extent = curr.extent_location() for child in curr.children: # First write out the directory record entry for all children. recstr = child.record() if (curr_dirrecord_offset + len(recstr)) > self.logical_block_size: dir_extent += 1 curr_dirrecord_offset = 0 outfp.seek(dir_extent * self.logical_block_size + curr_dirrecord_offset) # Now write out the child. self._outfp_write_with_check(outfp, recstr) curr_dirrecord_offset += len(recstr) if child.rock_ridge is not None: if child.rock_ridge.dr_entries.ce_record is not None: # The child has a continue block, so write it out here. ce_rec = child.rock_ridge.dr_entries.ce_record outfp.seek(ce_rec.bl_cont_area * self.logical_block_size + ce_rec.offset_cont_area) rec = child.rock_ridge.record_ce_entries() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if child.rock_ridge.child_link_record_exists(): continue if child.is_dir(): # If the child is a directory, and is not dot or dotdot, # descend into it to look at the children. if not child.is_dot() and not child.is_dotdot(): dirs.append(child) def _write_udf_descs(self, descs, outfp, progress): # type: (PyCdlib._UDFDescriptorSequence, BinaryIO, PyCdlib._Progress) -> None ''' An internal method to write out a UDF Descriptor sequence. Parameters: descs - The UDF Descriptors object to write out. outfp - The output file descriptor to use for writing. progress - The _Progress object to use for updating progress. Returns: Nothing. ''' for pvd in descs.pvds: outfp.seek(pvd.extent_location() * self.logical_block_size) rec = pvd.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if descs.desc_pointer.initialized: outfp.seek(descs.desc_pointer.extent_location() * self.logical_block_size) rec = descs.desc_pointer.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for impl_use in descs.impl_use: outfp.seek(impl_use.extent_location() * self.logical_block_size) rec = impl_use.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for partition in descs.partitions: outfp.seek(partition.extent_location() * self.logical_block_size) rec = partition.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for logical_volume in descs.logical_volumes: outfp.seek(logical_volume.extent_location() * self.logical_block_size) rec = logical_volume.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for unallocated_space in descs.unallocated_space: outfp.seek(unallocated_space.extent_location() * self.logical_block_size) rec = unallocated_space.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if descs.terminator.initialized: outfp.seek(descs.terminator.extent_location() * self.logical_block_size) rec = descs.terminator.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) def _write_fp(self, outfp, blocksize, progress_cb, progress_opaque): # type: (BinaryIO, int, Optional[Callable[[int, int, Any], None]], Optional[Any]) -> None ''' Write a properly formatted ISO out to the file object passed in. This also goes by the name of 'mastering'. Parameters: outfp - The file object to write the data to. blocksize - The blocksize to use when copying data. progress_cb - If not None, a function to call as the write call does its work. The callback function must have a signature of: def func(done, total). progress_opaque - User data to be passed to the progress callback. Returns: Nothing. ''' if hasattr(outfp, 'mode') and 'b' not in outfp.mode: raise pycdlibexception.PyCdlibInvalidInput("The file to write out must be in binary mode (add 'b' to the open flags)") if self._needs_reshuffle: self._reshuffle_extents() self._write_check_list = [] outfp.seek(0) progress = self._Progress(self.pvd.space_size * self.logical_block_size, progress_cb, progress_opaque) progress.call(0) if self.isohybrid_mbr is not None: self._outfp_write_with_check(outfp, self.isohybrid_mbr.record(self.pvd.space_size * self.logical_block_size)) outfp.seek(self.pvd.extent_location() * self.logical_block_size) # First write out the PVDs. for pvd in self.pvds: rec = pvd.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Next write out the boot records. for br in self.brs: outfp.seek(br.extent_location() * self.logical_block_size) rec = br.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Next we write out the SVDs. for svd in self.svds: outfp.seek(svd.extent_location() * self.logical_block_size) rec = svd.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Next we write out the Volume Descriptor Terminators. for vdst in self.vdsts: outfp.seek(vdst.extent_location() * self.logical_block_size) rec = vdst.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Next we write out the UDF Volume Recognition sequence (if this is a # UDF ISO). if self._has_udf: for bea in self.udf_beas: outfp.seek(bea.extent_location() * self.logical_block_size) rec = bea.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for boot in self.udf_boots: outfp.seek(boot.extent_location() * self.logical_block_size) rec = boot.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) outfp.seek(self.udf_nsr.extent_location() * self.logical_block_size) rec = self.udf_nsr.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) for tea in self.udf_teas: outfp.seek(tea.extent_location() * self.logical_block_size) rec = tea.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Next we write out the version block if it exists. if self.version_vd is not None: outfp.seek(self.version_vd.extent_location() * self.logical_block_size) rec = self.version_vd.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if self._has_udf: # Now the UDF Main and Reserved Volume Descriptor Sequence. self._write_udf_descs(self.udf_main_descs, outfp, progress) self._write_udf_descs(self.udf_reserve_descs, outfp, progress) # Now the UDF Logical Volume Integrity Sequence (if there is one). if self.udf_logical_volume_integrity is not None: outfp.seek(self.udf_logical_volume_integrity.extent_location() * self.logical_block_size) rec = self.udf_logical_volume_integrity.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if self.udf_logical_volume_integrity_terminator is not None: outfp.seek(self.udf_logical_volume_integrity_terminator.extent_location() * self.logical_block_size) rec = self.udf_logical_volume_integrity_terminator.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Now the UDF Anchor Points (if there are any). for anchor in self.udf_anchors: outfp.seek(anchor.extent_location() * self.logical_block_size) rec = anchor.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # In theory, the Path Table Records (for both the PVD and SVD) get # written out next. Since we store them along with the Directory # Records, however, we will write them out along with the directory # records instead. # Now write out the El Torito Boot Catalog if it exists. if self.eltorito_boot_catalog is not None: outfp.seek(self.eltorito_boot_catalog.extent_location() * self.logical_block_size) rec = self.eltorito_boot_catalog.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) # Now write out the ISO9660 directory records. self._write_directory_records(self.pvd, outfp, progress) # Now write out the Joliet directory records, if they exist. if self.joliet_vd is not None: self._write_directory_records(self.joliet_vd, outfp, progress) # Now write out the UDF directory records, if they exist. if self.udf_root is not None: # Write out the UDF File Sets. outfp.seek(self.udf_file_set.extent_location() * self.logical_block_size) rec = self.udf_file_set.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if self.udf_file_set_terminator is not None: outfp.seek(self.udf_file_set_terminator.extent_location() * self.logical_block_size) rec = self.udf_file_set_terminator.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) written_file_entry_inodes = {} # type: Dict[int, bool] udf_file_entries = collections.deque([(self.udf_root, True)]) # type: Deque[Tuple[Optional[udfmod.UDFFileEntry], bool]] while udf_file_entries: udf_file_entry, isdir = udf_file_entries.popleft() if udf_file_entry is None: continue if udf_file_entry.inode is None or not id(udf_file_entry.inode) in written_file_entry_inodes: outfp.seek(udf_file_entry.extent_location() * self.logical_block_size) rec = udf_file_entry.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) written_file_entry_inodes[id(udf_file_entry.inode)] = True if isdir: outfp.seek(udf_file_entry.fi_descs[0].extent_location() * self.logical_block_size) # FIXME: for larger directories, we'll actually need to # iterate over the alloc_descs and write them for fi_desc in udf_file_entry.fi_descs: rec = fi_desc.record() self._outfp_write_with_check(outfp, rec) progress.call(len(rec)) if not fi_desc.is_parent(): udf_file_entries.append((fi_desc.file_entry, fi_desc.is_dir())) # Now we need to write out the actual files. Note that in many cases, # we haven't yet read the file out of the original, so we need to do # that here. for ino in self.inodes: if ino.get_data_length() > 0: progress.call(self._output_file_data(outfp, blocksize, ino)) # We need to pad out to the total size of the disk, in the case that # the last thing we wrote is shorter than a full block size. It turns # out that not all file-like objects allow you to use truncate() to # grow the file, so we do it the old-fashioned way by seeking to the # end - 1 and writing a padding '\x00' byte. outfp.seek(0, os.SEEK_END) total_size = self.pvd.space_size * self.logical_block_size if outfp.tell() != total_size: outfp.seek(total_size - 1) outfp.write(b'\x00') if self.isohybrid_mbr is not None: outfp.seek(0, 2) outfp.write(self.isohybrid_mbr.record_padding(self.pvd.space_size * self.logical_block_size)) if self.isohybrid_mbr.efi: outfp.seek((self.isohybrid_mbr.secondary_gpt.header.current_lba * 512) - (self.isohybrid_mbr.secondary_gpt.header.num_parts * 128)) outfp.write(self.isohybrid_mbr.secondary_gpt.record()) progress.finish() def _update_rr_ce_entry(self, rec): # type: (dr.DirectoryRecord) -> int ''' An internal method to update the Rock Ridge CE entry for the given record. Parameters: rec - The record to update the Rock Ridge CE entry for (if it exists). Returns: The number of additional bytes needed for this Rock Ridge CE entry. ''' if rec.rock_ridge is not None and rec.rock_ridge.dr_entries.ce_record is not None: celen = rec.rock_ridge.dr_entries.ce_record.len_cont_area added_block, block, offset = self.pvd.add_rr_ce_entry(celen) rec.rock_ridge.update_ce_block(block) rec.rock_ridge.dr_entries.ce_record.update_offset(offset) if added_block: return self.logical_block_size return 0 def _finish_add(self, num_bytes_to_add, num_partition_bytes_to_add): # type: (int, int) -> None ''' An internal method to do all of the accounting needed whenever something is added to the ISO. This method should only be called by public API implementations. Parameters: num_bytes_to_add - The number of additional bytes to add to all descriptors. num_partition_bytes_to_add - The number of additional bytes to add to the partition if this is a UDF file. Returns: Nothing. ''' for pvd in self.pvds: pvd.add_to_space_size(num_bytes_to_add + num_partition_bytes_to_add) if self.joliet_vd is not None: self.joliet_vd.add_to_space_size(num_bytes_to_add + num_partition_bytes_to_add) if self.enhanced_vd is not None: self.enhanced_vd.copy_sizes(self.pvd) if self.udf_root is not None: num_extents_to_add = utils.ceiling_div(num_partition_bytes_to_add, self.logical_block_size) self.udf_main_descs.partitions[0].part_length += num_extents_to_add self.udf_reserve_descs.partitions[0].part_length += num_extents_to_add if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.size_tables[0] += num_extents_to_add if self._always_consistent: self._reshuffle_extents() else: self._needs_reshuffle = True def _finish_remove(self, num_bytes_to_remove, is_partition): # type: (int, bool) -> None ''' An internal method to do all of the accounting needed whenever something is removed from the ISO. This method should only be called by public API implementations. Parameters: num_bytes_to_remove - The number of additional bytes to remove from the descriptors. is_partition - Whether these bytes are part of a UDF partition. Returns: Nothing. ''' for pvd in self.pvds: pvd.remove_from_space_size(num_bytes_to_remove) if self.joliet_vd is not None: self.joliet_vd.remove_from_space_size(num_bytes_to_remove) if self.enhanced_vd is not None: self.enhanced_vd.copy_sizes(self.pvd) if self.udf_root is not None and is_partition: num_extents_to_remove = utils.ceiling_div(num_bytes_to_remove, self.logical_block_size) self.udf_main_descs.partitions[0].part_length -= num_extents_to_remove self.udf_reserve_descs.partitions[0].part_length -= num_extents_to_remove if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.size_tables[0] -= num_extents_to_remove if self._always_consistent: self._reshuffle_extents() else: self._needs_reshuffle = True def _add_hard_link_to_inode(self, data_ino, length, file_mode, boot_catalog_old, **kwargs): # type: (Optional[inode.Inode], int, int, bool, Any) -> int ''' Add a hard link to the ISO. Hard links are alternate names for the same file contents that don't take up any additional space on the ISO. This API can be used to create hard links between two files on the ISO9660 filesystem, between two files on the Joliet filesystem, or between a file on the ISO9660 filesystem and the Joliet filesystem. In all cases, exactly one old path must be specified, and exactly one new path must be specified. Parameters: data_ino - The inode of the old record to link against. length - The length of the old record to link against. file_mode - The file mode of the old record to link against. boot_catalog_old - Whether this is a link to an old boot catalog. iso_new_path - The new path on the ISO9660 filesystem to link to. joliet_new_path - The new path on the Joliet filesystem to link to. rr_name - The Rock Ridge name to use for the new file if this is a Rock Ridge ISO and the new path is on the ISO9660 filesystem. udf_new_path - The new path on the UDF filesystem to link to. Returns: The number of bytes to add to the descriptors. ''' num_new = 0 iso_new_path = None joliet_new_path = None rr_name = b'' udf_new_path = None new_rec = None # type: Optional[Union[dr.DirectoryRecord, udfmod.UDFFileEntry]] for key in kwargs: if key == 'iso_new_path': if kwargs[key] is not None: num_new += 1 iso_new_path = utils.normpath(kwargs[key]) if not self.rock_ridge and self.interchange_level < 4: _check_path_depth(iso_new_path) elif key == 'joliet_new_path': if kwargs[key] is not None: num_new += 1 joliet_new_path = self._normalize_joliet_path(kwargs[key]) elif key == 'rr_name': if kwargs[key] is not None: rr_name = self._check_rr_name(kwargs[key]) elif key == 'udf_new_path': if kwargs[key] is not None: num_new += 1 udf_new_path = utils.normpath(kwargs[key]) else: raise pycdlibexception.PyCdlibInvalidInput('Unknown keyword %s' % (key)) if num_new != 1: raise pycdlibexception.PyCdlibInvalidInput('Exactly one new path must be specified') if self.rock_ridge and iso_new_path is not None and not rr_name: raise pycdlibexception.PyCdlibInvalidInput('Rock Ridge name must be supplied for a Rock Ridge new path') num_bytes_to_add = 0 if udf_new_path is None: if iso_new_path is not None: # ... to another file on the ISO9660 filesystem. (new_name, new_parent) = self._iso_name_and_parent_from_path(iso_new_path) _check_iso9660_filename(new_name, self.interchange_level) vd = self.pvd rr = self.rock_ridge xa = self.xa elif joliet_new_path is not None: if self.joliet_vd is None: raise pycdlibexception.PyCdlibInternalError('Tried to link to Joliet record on non-Joliet ISO') # ... to a file on the Joliet filesystem. (new_name, new_parent) = self._joliet_name_and_parent_from_path(joliet_new_path) vd = self.joliet_vd rr = '' xa = False # Above we checked to make sure we got at least one new path, so we # don't need to worry about the else situation here. new_rec = dr.DirectoryRecord() new_rec.new_file(vd, length, new_name, new_parent, vd.sequence_number(), rr, rr_name, xa, file_mode) num_bytes_to_add += self._add_child_to_dr(new_rec) num_bytes_to_add += self._update_rr_ce_entry(new_rec) else: if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') # UDF new path. (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_new_path) file_ident = udfmod.UDFFileIdentifierDescriptor() file_ident.new(False, False, udf_name, udf_parent) num_new_extents = udf_parent.add_file_ident_desc(file_ident, self.logical_block_size) num_bytes_to_add += num_new_extents * self.logical_block_size file_entry = udfmod.UDFFileEntry() file_entry.new(length, 'file', udf_parent, self.logical_block_size) file_ident.file_entry = file_entry file_entry.file_ident = file_ident if data_ino is None or data_ino.num_udf == 0: num_bytes_to_add += self.logical_block_size if data_ino is not None: data_ino.num_udf += 1 new_rec = file_entry if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_impl_use.num_files += 1 if data_ino is not None and new_rec is not None: data_ino.linked_records.append((new_rec, iso_new_path is not None)) new_rec.inode = data_ino if boot_catalog_old and new_rec is not None: if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInternalError('Tried to link to El Torito on non-El Torito ISO') self.eltorito_boot_catalog.add_dirrecord(new_rec) return num_bytes_to_add def _add_fp(self, fp, length, manage_fp, iso_path, rr_name, joliet_path, udf_path, file_mode, eltorito_catalog): # type: (Optional[Union[BinaryIO, str]], int, bool, Optional[str], Optional[str], Optional[str], Optional[str], Optional[int], bool) -> int ''' An internal method to add a file to the ISO. If the ISO contains Rock Ridge, then a Rock Ridge name must be provided. If the ISO contains Joliet, then a Joliet path is not required but is highly recommended. Note that the caller must ensure that the file remains open for the lifetime of the ISO object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. manage_fp - Whether or not pycdlib should internally manage the file pointer. It is faster to manage the file pointer externally, but it is more convenient to have pycdlib do it internally. iso_path - The ISO9660 absolute path to the file destination on the ISO. rr_name - The Rock Ridge name of the file destination on the ISO. joliet_path - The Joliet absolute path to the file destination on the ISO. udf_path - The UDF absolute path to the file destination on the ISO. file_mode - The POSIX file_mode to apply to this file. This only applies if this is a Rock Ridge ISO. If this is None (the default), the permissions from the original file are used. eltorito_catalog - Whether this entry represents an El Torito Boot Catalog. Returns: The number of bytes to add to the descriptors. ''' if iso_path is None and joliet_path is None and udf_path is None: raise pycdlibexception.PyCdlibInvalidInput("At least one of 'iso_path', 'joliet_path', or 'udf_path' must be provided") fmode = 0 if file_mode is not None: if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a file mode for Rock Ridge ISOs') fmode = file_mode else: if self.rock_ridge: if fp is not None and not isinstance(fp, str): # Python 3 implements the fileno method for all file-like # objects, so we can't just use the existence of the method # to tell whether it is available. Instead, we try to # assign it, and if that fails, then we assume it is not # available. try: fileno = fp.fileno() fmode = os.fstat(fileno).st_mode except (AttributeError, io.UnsupportedOperation): # We couldn't get the actual file mode of the file, so # assume a conservative 444 fmode = 0o0100444 else: fmode = 0o0100444 left = length offset = 0 done = False num_bytes_to_add = 0 while not done: # The maximum length we allow in one directory record is 0xfffff800 # (this is taken from xorriso, though I don't really know why). thislen = min(left, 0xfffff800) ino = None if fp is not None: ino = inode.Inode() ino.new(thislen, fp, manage_fp, offset) num_bytes_to_add += thislen if iso_path: num_bytes_to_add += self._add_hard_link_to_inode(ino, thislen, fmode, eltorito_catalog, iso_new_path=iso_path, rr_name=rr_name) if joliet_path: # If this is a Joliet ISO, then we can re-use add_hard_link to do # most of the work. num_bytes_to_add += self._add_hard_link_to_inode(ino, thislen, fmode, eltorito_catalog, joliet_new_path=joliet_path) if udf_path: num_bytes_to_add += self._add_hard_link_to_inode(ino, thislen, fmode, eltorito_catalog, udf_new_path=udf_path) # This goes after the hard link so we only track the new Inode if # everything above succeeds if ino is not None: self.inodes.append(ino) left -= thislen offset += thislen if left == 0: done = True return num_bytes_to_add def _rm_dr_link(self, rec): # type: (dr.DirectoryRecord) -> int ''' An internal method to remove a Directory Record link given the record. Parameters: rec - The Directory Record to remove. Returns: The number of bytes to remove from the ISO. ''' if not rec.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Cannot remove a directory with rm_hard_link (try rm_directory instead)') num_bytes_to_remove = 0 done = False while not done: num_bytes_to_remove += self._remove_child_from_dr(rec, rec.index_in_parent) if rec.inode is not None: found_index = None for index, reclink in enumerate(rec.inode.linked_records): link = reclink[0] if id(link) == id(rec): found_index = index break else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Could not find inode corresponding to record') del rec.inode.linked_records[found_index] # We only remove the size of the child from the ISO if there are # no other references to this file on the ISO. if not rec.inode.linked_records: found_index = None for index, ino in enumerate(self.inodes): if id(ino) == id(rec.inode): found_index = index break else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Could not find inode corresponding to record') del self.inodes[found_index] num_bytes_to_remove += rec.get_data_length() if rec.data_continuation is not None: rec = rec.data_continuation else: done = True return num_bytes_to_remove def _rm_udf_file_ident(self, parent, fi): # type: (udfmod.UDFFileEntry, bytes) -> int ''' An internal method to remove a UDF File Identifier from the parent and remove any space from the Logical Volume as necessary. Parameters: parent - The parent entry to remove the UDF File Identifier from. fi - The file identifier to remove. Returns: The number of bytes to remove from the ISO. ''' num_extents_to_remove = parent.remove_file_ident_desc_by_name(fi, self.logical_block_size) if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_impl_use.num_files -= 1 self._find_udf_record.cache_clear() # pylint: disable=no-member return num_extents_to_remove * self.logical_block_size def _rm_udf_link(self, rec): # type: (udfmod.UDFFileEntry) -> int ''' An internal method to remove a UDF File Entry link. Parameters: rec - The UDF File Entry to remove. Returns: The number of bytes to remove from the ISO. ''' if not rec.is_file() and not rec.is_symlink(): raise pycdlibexception.PyCdlibInvalidInput('Cannot remove a directory with rm_hard_link (try rm_directory instead)') # To remove something from UDF, we have to: # 1. Remove it from the list of linked_records on the Inode. # 2. If the number of links to the Inode is now 0, remove the Inode. # 3. If the number of links to the UDF File Entry this uses is 0, # remove the UDF File Entry. # 4. Remove the UDF File Identifier from the parent. num_bytes_to_remove = 0 if rec.inode is not None: # Step 1. found_index = None for index, reclink in enumerate(rec.inode.linked_records): link = reclink[0] if id(link) == id(rec): found_index = index break else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Could not find inode corresponding to record') del rec.inode.linked_records[found_index] rec.inode.num_udf -= 1 # Step 2. if not rec.inode.linked_records: found_index = None for index, ino in enumerate(self.inodes): if id(ino) == id(rec.inode): found_index = index break else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Could not find inode corresponding to record') del self.inodes[found_index] num_bytes_to_remove += rec.get_data_length() # Step 3. if rec.inode.num_udf == 0: num_bytes_to_remove += self.logical_block_size else: # If rec.inode is None, just remove space for the UDF File Entry. num_bytes_to_remove += self.logical_block_size # Step 4. if rec.parent is None: raise pycdlibexception.PyCdlibInternalError('Cannot remove a UDF record with no parent') if rec.file_ident is None: raise pycdlibexception.PyCdlibInternalError('Cannot remove a UDF record with no file identifier') return num_bytes_to_remove + self._rm_udf_file_ident(rec.parent, rec.file_ident.fi) def _add_joliet_dir(self, joliet_path): # type: (bytes) -> int ''' An internal method to add a joliet directory to the ISO. Parameters: joliet_path - The path to add to the Joliet portion of the ISO. Returns: The number of additional bytes needed on the ISO to fit this directory. ''' if self.joliet_vd is None: raise pycdlibexception.PyCdlibInternalError('Tried to add Joliet dir to non-Joliet ISO') (joliet_name, joliet_parent) = self._joliet_name_and_parent_from_path(joliet_path) rec = dr.DirectoryRecord() rec.new_dir(self.joliet_vd, joliet_name, joliet_parent, self.joliet_vd.sequence_number(), '', b'', self.logical_block_size, False, False, False, -1) num_bytes_to_add = self._add_child_to_dr(rec) self._create_dot(self.joliet_vd, rec, '', False, -1) self._create_dotdot(self.joliet_vd, rec, '', False, False, -1) num_bytes_to_add += self.logical_block_size if self.joliet_vd.add_to_ptr_size(path_table_record.PathTableRecord.record_length(len(joliet_name))): num_bytes_to_add += 4 * self.logical_block_size ptr = path_table_record.PathTableRecord() ptr.new_dir(joliet_name) rec.set_ptr(ptr) return num_bytes_to_add def _rm_joliet_dir(self, joliet_path): # type: (bytes) -> int ''' An internal method to remove a directory from the Joliet portion of the ISO. Parameters: joliet_path - The Joliet directory to remove. Returns: The number of bytes to remove from the ISO for this Joliet directory. ''' if self.joliet_vd is None: raise pycdlibexception.PyCdlibInternalError('Tried to remove joliet dir from non-Joliet ISO') joliet_child = self._find_joliet_record(joliet_path) num_bytes_to_remove = joliet_child.get_data_length() num_bytes_to_remove += self._remove_child_from_dr(joliet_child, joliet_child.index_in_parent) if joliet_child.ptr is None: raise pycdlibexception.PyCdlibInternalError('Joliet directory has no path table record; this should not be') if self.joliet_vd.remove_from_ptr_size(path_table_record.PathTableRecord.record_length(joliet_child.ptr.len_di)): num_bytes_to_remove += 4 * self.logical_block_size return num_bytes_to_remove def _get_entry(self, iso_path, rr_path, joliet_path): # type: (Optional[bytes], Optional[bytes], Optional[bytes]) -> dr.DirectoryRecord ''' Internal method to get the directory record for a particular path. Parameters: iso_path - The path on the ISO filesystem to look up the record for. rr_path - The Rock Ridge path on the ISO filesystem to look up the record for. joliet_path - The path on the Joliet filesystem to look up the record for. Returns: A dr.DirectoryRecord object representing the path. ''' if self._needs_reshuffle: self._reshuffle_extents() rec = None if joliet_path is not None: rec = self._find_joliet_record(joliet_path) elif rr_path is not None: rec = self._find_rr_record(rr_path) elif iso_path is not None: rec = self._find_iso_record(iso_path) else: raise pycdlibexception.PyCdlibInternalError('get_entry called without legal argument') return rec def _get_udf_entry(self, udf_path): # type: (str) -> udfmod.UDFFileEntry ''' Internal method to get the UDF File Entry for a particular path. Parameters: udf_path - The path on the UDF filesystem to look up the record for. Returns: A udfmod.UDFFileEntry object representing the path. ''' if self._needs_reshuffle: self._reshuffle_extents() (ident_unused, rec) = self._find_udf_record(utils.normpath(udf_path)) if rec is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot get entry for empty UDF File Entry') return rec def _check_inode_against_eltorito(self, ino): # type: (inode.Inode) -> None ''' Internal method to check if an Inode is referenced by El Torito at all. Parameters: ino - The Inode to look for in the El Torito part of the ISO. Returns: Nothing. ''' if self.eltorito_boot_catalog is not None: eltorito_entries = {} eltorito_entries[id(self.eltorito_boot_catalog.initial_entry.inode)] = True for sec in self.eltorito_boot_catalog.sections: for entry in sec.section_entries: eltorito_entries[id(entry.inode)] = True if id(ino) in eltorito_entries: raise pycdlibexception.PyCdlibInvalidInput("Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") def _rm_file_inodes(self, child): # type: (dr.DirectoryRecord) -> int ''' Internal method to remove all of the Directory Records and UDF File Entries linked to this child's Inode. Parameters: child - The DirectoryRecord to remove. Returns: The number of bytes to remove from the ISO. ''' if not child.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Cannot remove a directory with rm_file (try rm_directory instead)') # We also want to check to see if this Directory Record is currently # being used as an El Torito Boot Catalog, Initial Entry, or Section # Entry. If it is, we throw an exception; we don't know if the user # meant to remove El Torito from this ISO, or if they meant to 'hide' # the entry, but we need them to call the correct API to let us know. if self.eltorito_boot_catalog is not None: if any([id(child) == id(rec) for rec in self.eltorito_boot_catalog.dirrecords]): raise pycdlibexception.PyCdlibInvalidInput("Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") num_bytes_to_remove = 0 if child.inode is None: num_bytes_to_remove += self._remove_child_from_dr(child, child.index_in_parent) else: self._check_inode_against_eltorito(child.inode) while child.inode.linked_records: rec = child.inode.linked_records[0][0] if isinstance(rec, dr.DirectoryRecord): num_bytes_to_remove += self._rm_dr_link(rec) elif isinstance(rec, udfmod.UDFFileEntry): num_bytes_to_remove += self._rm_udf_link(rec) else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Saw a linked record that was neither ISO or UDF') return num_bytes_to_remove def _rm_file_via_iso_path(self, iso_path): # type: (str) -> int ''' Internal method to completely remove a file given an ISO path. Parameters: iso_path - The path to the file in the ISO9660 context to remove. Returns: The number of bytes to remove from the ISO. ''' iso_path_bytes = utils.normpath(iso_path) return self._rm_file_inodes(self._find_iso_record(iso_path_bytes)) def _rm_file_via_joliet_path(self, joliet_path): # type: (str) -> int ''' Internal method to completely remove a file given a Joliet path. Parameters: joliet_path - The path to the file in the Joliet context to remove. Returns: The number of bytes to remove from the ISO. ''' joliet_path_bytes = self._normalize_joliet_path(joliet_path) return self._rm_file_inodes(self._find_joliet_record(joliet_path_bytes)) def _rm_file_via_udf_path(self, udf_path): # type: (str) -> int ''' Internal method to completely remove a file given a UDF path. Parameters: udf_path - The path to the file in the UDF context to remove. Returns: The number of bytes to remove from the ISO. ''' if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') udf_path_bytes = utils.normpath(udf_path) (udf_file_ident, udf_file_entry) = self._find_udf_record(udf_path_bytes) num_bytes_to_remove = 0 if udf_file_entry is None: if udf_file_ident is not None and udf_file_ident.parent is not None: # If the udf_path was specified, go looking for the UDF File Ident # that corresponds to this record. If the UDF File Ident exists, # and the File Entry is None, this means that it is an "zeroed" # UDF File Entry and we have to remove it by hand. self._rm_udf_file_ident(udf_file_ident.parent, udf_file_ident.fi) # We also have to remove the "zero" UDF File Entry, since nothing # else will. num_bytes_to_remove += self.logical_block_size else: if not udf_file_entry.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Cannot remove a directory with rm_file (try rm_directory instead)') # We also want to check to see if this Directory Record is currently # being used as an El Torito Boot Catalog, Initial Entry, or Section # Entry. If it is, we throw an exception; we don't know if the user # meant to remove El Torito from this ISO, or if they meant to 'hide' # the entry, but we need them to call the correct API to let us know. if self.eltorito_boot_catalog is not None: if any([id(udf_file_entry) == id(rec) for rec in self.eltorito_boot_catalog.dirrecords]): raise pycdlibexception.PyCdlibInvalidInput("Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") if udf_file_entry.inode is not None: self._check_inode_against_eltorito(udf_file_entry.inode) while udf_file_entry.inode.linked_records: rec = udf_file_entry.inode.linked_records[0][0] if isinstance(rec, dr.DirectoryRecord): num_bytes_to_remove += self._rm_dr_link(rec) elif isinstance(rec, udfmod.UDFFileEntry): num_bytes_to_remove += self._rm_udf_link(rec) else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Saw a linked record that was neither ISO or UDF') return num_bytes_to_remove def _create_dot(self, vd, parent, rock_ridge, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, dr.DirectoryRecord, str, bool, int) -> None ''' An internal method to create a new 'dot' Directory Record. Parameters: vd - The volume descriptor to attach the 'dot' Directory Record to. parent - The parent Directory Record for new Directory Record. rock_ridge - The Rock Ridge version to use for this entry (if any). xa - Whether this Directory Record should have extended attributes. file_mode - The mode to assign to the dot directory (only applies to Rock Ridge). Returns: Nothing. ''' dot = dr.DirectoryRecord() dot.new_dot(vd, parent, vd.sequence_number(), rock_ridge, vd.logical_block_size(), xa, file_mode) self._add_child_to_dr(dot) def _create_dotdot(self, vd, parent, rock_ridge, relocated, xa, file_mode): # type: (headervd.PrimaryOrSupplementaryVD, dr.DirectoryRecord, str, bool, bool, int) -> dr.DirectoryRecord ''' An internal method to create a new 'dotdot' Directory Record. Parameters: vd - The volume descriptor to attach the 'dotdot' Directory Record to. parent - The parent Directory Record for new Directory Record. rock_ridge - The Rock Ridge version to use for this entry (if any). relocated - Whether this Directory Record is a Rock Ridge relocated entry. xa - Whether this Directory Record should have extended attributes. file_mode - The mode to assign to the dot directory (only applies to Rock Ridge). Returns: Nothing. ''' dotdot = dr.DirectoryRecord() dotdot.new_dotdot(vd, parent, vd.sequence_number(), rock_ridge, vd.logical_block_size(), relocated, xa, file_mode) self._add_child_to_dr(dotdot) return dotdot ########################### PUBLIC API ##################################### def __init__(self, always_consistent=False): # type: (bool) -> None self._always_consistent = always_consistent track_writes = os.getenv('PYCDLIB_TRACK_WRITES') self._track_writes = False if track_writes is not None: self._track_writes = True self._initialize() def new(self, interchange_level=1, sys_ident='', vol_ident='', set_size=1, seqnum=1, log_block_size=2048, vol_set_ident=' ', pub_ident_str='', preparer_ident_str='', app_ident_str='', copyright_file='', abstract_file='', bibli_file='', vol_expire_date=None, app_use='', joliet=None, rock_ridge=None, xa=False, udf=None): # type: (int, str, str, int, int, int, str, str, str, str, str, str, str, Optional[float], str, Optional[int], Optional[str], bool, Optional[str]) -> None ''' Create a new ISO from scratch. Parameters: interchange_level - The ISO9660 interchange level to use; this dictates the rules on the names of files. Levels 1, 2, 3, and 4 are supported. Level 1 is the most conservative, and is the default, but level 3 is recommended. sys_ident - The system identification string to use on the new ISO. vol_ident - The volume identification string to use on the new ISO. set_size - The size of the set of ISOs this ISO is a part of. seqnum - The sequence number of the set of this ISO. log_block_size - The logical block size to use for the ISO. While ISO9660 technically supports sizes other than 2048 (the default), this almost certainly doesn't work. vol_set_ident - The volume set identification string to use on the new ISO. pub_ident_str - The publisher identification string to use on the new ISO. preparer_ident_str - The preparer identification string to use on the new ISO. app_ident_str - The application identification string to use on the new ISO. copyright_file - The name of a file at the root of the ISO to use as the copyright file. abstract_file - The name of a file at the root of the ISO to use as the abstract file. bibli_file - The name of a file at the root of the ISO to use as the bibliographic file. vol_expire_date - The date that this ISO will expire at. app_use - Arbitrary data that the application can stuff into the primary volume descriptor of this ISO. joliet - A integer that can have the value 1, 2, or 3 for Joliet levels 1, 2, or 3 (3 is by far the most common), or None for no Joliet support (the default). For legacy reasons, this parameter also accepts a boolean, where the value of 'False' means no Joliet and a value of 'True' means level 3. rock_ridge - Whether to make this ISO have the Rock Ridge extensions or not. The default value of None does not add Rock Ridge extensions. A string value of '1.09', '1.10', or '1.12' adds the specified Rock Ridge version to the ISO. If unsure, pass '1.09' to ensure maximum compatibility. xa - Whether to add the ISO9660 Extended Attribute extensions to this ISO. The default is False. udf - Whether to add UDF support to this ISO. If it is None (the default), no UDF support is added. If it is "2.60", version 2.60 of the UDF spec is used. All other values are disallowed. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object already has an ISO; either close it or create a new object') if interchange_level < 1 or interchange_level > 4: raise pycdlibexception.PyCdlibInvalidInput('Invalid interchange level (must be between 1 and 4)') if rock_ridge and rock_ridge not in ('1.09', '1.10', '1.12'): raise pycdlibexception.PyCdlibInvalidInput('Rock Ridge value must be None (no Rock Ridge), 1.09, 1.10, or 1.12') if udf and udf != '2.60': raise pycdlibexception.PyCdlibInvalidInput('UDF value must be empty (no UDF), or 2.60') if not app_ident_str: app_ident_str = 'PyCdlib (C) 2015-2020 Chris Lalancette' self.interchange_level = interchange_level self.xa = xa if isinstance(joliet, bool): if joliet: joliet = 3 else: joliet = None if rock_ridge: self.rock_ridge = rock_ridge sys_ident_bytes = sys_ident.encode('utf-8') vol_ident_bytes = vol_ident.encode('utf-8') vol_set_ident_bytes = vol_set_ident.encode('utf-8') pub_ident_bytes = pub_ident_str.encode('utf-8') preparer_ident_bytes = preparer_ident_str.encode('utf-8') app_ident_bytes = app_ident_str.encode('utf-8') copyright_file_bytes = copyright_file.encode('utf-8') abstract_file_bytes = abstract_file.encode('utf-8') bibli_file_bytes = bibli_file.encode('utf-8') app_use_bytes = app_use.encode('utf-8') if vol_expire_date is None: real_vol_expire_date = 0.0 else: real_vol_expire_date = vol_expire_date # Now start creating the ISO. self.pvd = headervd.pvd_factory(sys_ident_bytes, vol_ident_bytes, set_size, seqnum, log_block_size, vol_set_ident_bytes, pub_ident_bytes, preparer_ident_bytes, app_ident_bytes, copyright_file_bytes, abstract_file_bytes, bibli_file_bytes, real_vol_expire_date, app_use_bytes, xa) self.pvds.append(self.pvd) self.logical_block_size = self.pvd.logical_block_size() num_bytes_to_add = 0 if self.interchange_level == 4: self.enhanced_vd = headervd.enhanced_vd_factory(sys_ident_bytes, vol_ident_bytes, set_size, seqnum, log_block_size, vol_set_ident_bytes, pub_ident_bytes, preparer_ident_bytes, app_ident_bytes, copyright_file_bytes, abstract_file_bytes, bibli_file_bytes, real_vol_expire_date, app_use_bytes, xa) self.svds.append(self.enhanced_vd) num_bytes_to_add += self.enhanced_vd.logical_block_size() if joliet is not None: self.joliet_vd = headervd.joliet_vd_factory(joliet, sys_ident_bytes, vol_ident_bytes, set_size, seqnum, log_block_size, vol_set_ident_bytes, pub_ident_bytes, preparer_ident_bytes, app_ident_bytes, copyright_file_bytes, abstract_file_bytes, bibli_file_bytes, real_vol_expire_date, app_use_bytes, xa) self.svds.append(self.joliet_vd) # Now that we have added joliet, we need to add the new space to the # PVD for the VD itself. num_bytes_to_add += self.joliet_vd.logical_block_size() self.vdsts.append(headervd.vdst_factory()) num_bytes_to_add += self.logical_block_size if udf: self._has_udf = True # Create the UDF Bridge Recognition Volume Sequence. udf_bea = udfmod.BEAVolumeStructure() udf_bea.new() self.udf_beas.append(udf_bea) self.udf_nsr.new(2) udf_tea = udfmod.TEAVolumeStructure() udf_tea.new() self.udf_teas.append(udf_tea) num_bytes_to_add += 3 * self.logical_block_size # We always create an empty version volume descriptor. self.version_vd = headervd.version_vd_factory(self.logical_block_size) num_bytes_to_add += self.logical_block_size if udf: # We need to pad out to extent 32. The padding should be the # distance between the current PVD space size and 32. additional_extents = 32 - (self.pvd.space_size + (num_bytes_to_add // self.logical_block_size)) num_bytes_to_add += additional_extents * self.logical_block_size # Create the Main Volume Descriptor Sequence. pvd = udfmod.UDFPrimaryVolumeDescriptor() pvd.new() self.udf_main_descs.pvds.append(pvd) impl_use = udfmod.UDFImplementationUseVolumeDescriptor() impl_use.new() self.udf_main_descs.impl_use.append(impl_use) partition = udfmod.UDFPartitionVolumeDescriptor() partition.new(2) # FIXME: we should let the user set this self.udf_main_descs.partitions.append(partition) logical_volume = udfmod.UDFLogicalVolumeDescriptor() logical_volume.new() logical_volume.add_partition_map(1) self.udf_main_descs.logical_volumes.append(logical_volume) unallocated_space = udfmod.UDFUnallocatedSpaceDescriptor() unallocated_space.new() self.udf_main_descs.unallocated_space.append(unallocated_space) self.udf_main_descs.terminator.new() num_bytes_to_add += 16 * self.logical_block_size # Create the Reserve Volume Descriptor Sequence. reserve_pvd = udfmod.UDFPrimaryVolumeDescriptor() reserve_pvd.new() self.udf_reserve_descs.pvds.append(reserve_pvd) reserve_impl_use = udfmod.UDFImplementationUseVolumeDescriptor() reserve_impl_use.new() self.udf_reserve_descs.impl_use.append(reserve_impl_use) reserve_partition = udfmod.UDFPartitionVolumeDescriptor() reserve_partition.new(2) self.udf_reserve_descs.partitions.append(reserve_partition) reserve_logical_volume = udfmod.UDFLogicalVolumeDescriptor() reserve_logical_volume.new() reserve_logical_volume.add_partition_map(1) self.udf_reserve_descs.logical_volumes.append(reserve_logical_volume) reserve_unallocated_space = udfmod.UDFUnallocatedSpaceDescriptor() reserve_unallocated_space.new() self.udf_reserve_descs.unallocated_space.append(reserve_unallocated_space) self.udf_reserve_descs.terminator.new() num_bytes_to_add += 16 * self.logical_block_size # Create the Logical Volume Integrity Sequence. self.udf_logical_volume_integrity = udfmod.UDFLogicalVolumeIntegrityDescriptor() self.udf_logical_volume_integrity.new() self.udf_logical_volume_integrity_terminator = udfmod.UDFTerminatingDescriptor() self.udf_logical_volume_integrity_terminator.new() num_bytes_to_add += 192 * self.logical_block_size # Create the Anchor. anchor1 = udfmod.UDFAnchorVolumeStructure() anchor1.new() self.udf_anchors.append(anchor1) num_bytes_to_add += self.logical_block_size # Create the File Set self.udf_file_set.new() self.udf_file_set_terminator = udfmod.UDFTerminatingDescriptor() self.udf_file_set_terminator.new() num_bytes_to_add += 2 * self.logical_block_size # Create the root directory, and the 'parent' entry inside. self.udf_root = udfmod.UDFFileEntry() self.udf_root.new(0, 'dir', None, self.logical_block_size) num_bytes_to_add += self.logical_block_size parent = udfmod.UDFFileIdentifierDescriptor() parent.new(True, True, b'', None) num_new_extents = self.udf_root.add_file_ident_desc(parent, self.logical_block_size) num_bytes_to_add += num_new_extents * self.logical_block_size num_partition_bytes_to_add = 0 # Create the PTR, and add the 4 extents that comprise of the LE PTR and # BE PTR to the number of bytes to add. ptr = path_table_record.PathTableRecord() ptr.new_root() self.pvd.root_directory_record().set_ptr(ptr) num_partition_bytes_to_add += 4 * self.logical_block_size # Also add one extent to the size for the root directory record. num_partition_bytes_to_add += self.logical_block_size self._create_dot(self.pvd, self.pvd.root_directory_record(), self.rock_ridge, self.xa, 0o040555) self._create_dotdot(self.pvd, self.pvd.root_directory_record(), self.rock_ridge, False, self.xa, 0o040555) if self.joliet_vd is not None: # Create the PTR, and add the 4 extents that comprise of the LE PTR # and BE PTR to the number of bytes to add. ptr = path_table_record.PathTableRecord() ptr.new_root() self.joliet_vd.root_directory_record().set_ptr(ptr) num_partition_bytes_to_add += 4 * self.logical_block_size # Also add one extent to the size for the root directory record. num_partition_bytes_to_add += self.logical_block_size self._create_dot(self.joliet_vd, self.joliet_vd.root_directory_record(), '', False, -1) self._create_dotdot(self.joliet_vd, self.joliet_vd.root_directory_record(), '', False, False, -1) if self.rock_ridge: num_partition_bytes_to_add += self.logical_block_size if udf: anchor2 = udfmod.UDFAnchorVolumeStructure() anchor2.new() self.udf_anchors.append(anchor2) num_partition_bytes_to_add += self.logical_block_size self._finish_add(num_bytes_to_add, num_partition_bytes_to_add) self._initialized = True def open(self, filename, mode='rb'): # type: (str, str) -> None ''' Open up an existing ISO for inspection and modification. Parameters: filename - The filename containing the ISO to open up. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object already has an ISO; either close it or create a new object') fp = open(filename, mode) self._managing_fp = True try: self._open_fp(fp) except Exception: fp.close() raise def open_fp(self, fp): # type: (BinaryIO) -> None ''' Open up an existing ISO for inspection and modification. Note that the file object passed in here must stay open for the lifetime of this object, as the PyCdlib class uses it internally to do writing and reading operations. To have PyCdlib manage this automatically, use 'open' instead. Parameters: fp - The file object containing the ISO to open up. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object already has an ISO; either close it or create a new object') self._open_fp(fp) def get_file_from_iso(self, local_path, **kwargs): # type: (str, Any) -> None ''' Fetch a single file from the ISO and write it out to a local file. Parameters: local_path - The local file to write to. blocksize - The number of bytes in each transfer. iso_path - The absolute ISO9660 path to lookup on the ISO (exclusive with rr_path, joliet_path, and udf_path). rr_path - The absolute Rock Ridge path to lookup on the ISO (exclusive with iso_path, joliet_path, and udf_path). joliet_path - The absolute Joliet path to lookup on the ISO (exclusive with iso_path, rr_path, and udf_path). udf_path - The absolute UDF path to lookup on the ISO (exclusive with iso_path, rr_path, and joliet_path). Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') blocksize = 8192 joliet_path = None iso_path = None rr_path = None udf_path = None num_paths = 0 for key in kwargs: if key == 'blocksize': blocksize = kwargs[key] elif key == 'iso_path': if kwargs[key] is not None: iso_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'rr_path': if kwargs[key] is not None: rr_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'joliet_path': if kwargs[key] is not None: joliet_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'udf_path': if kwargs[key] is not None: udf_path = utils.normpath(kwargs[key]) num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput('Unknown keyword %s' % (key)) if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Exactly one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path' must be passed") with open(local_path, 'wb') as fp: if udf_path is not None: self._udf_get_file_from_iso_fp(fp, blocksize, udf_path) else: self._get_file_from_iso_fp(fp, blocksize, iso_path, rr_path, joliet_path) def get_file_from_iso_fp(self, outfp, **kwargs): # type: (BinaryIO, Any) -> None ''' Fetch a single file from the ISO and write it out to the file object. Parameters: outfp - The file object to write data to. blocksize - The number of bytes in each transfer. iso_path - The absolute ISO9660 path to lookup on the ISO (exclusive with rr_path, joliet_path, and udf_path). rr_path - The absolute Rock Ridge path to lookup on the ISO (exclusive with iso_path, joliet_path, and udf_path). joliet_path - The absolute Joliet path to lookup on the ISO (exclusive with iso_path, rr_path, and udf_path). udf_path - The absolute UDF path to lookup on the ISO (exclusive with iso_path, rr_path, and joliet_path). Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') blocksize = 8192 joliet_path = None iso_path = None rr_path = None udf_path = None num_paths = 0 for key in kwargs: if key == 'blocksize': blocksize = kwargs[key] elif key == 'iso_path': if kwargs[key] is not None: iso_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'rr_path': if kwargs[key] is not None: rr_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'joliet_path': if kwargs[key] is not None: joliet_path = utils.normpath(kwargs[key]) num_paths += 1 elif key == 'udf_path': if kwargs[key] is not None: udf_path = utils.normpath(kwargs[key]) num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput('Unknown keyword %s' % (key)) if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Exactly one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path' must be passed") if udf_path is not None: self._udf_get_file_from_iso_fp(outfp, blocksize, udf_path) else: self._get_file_from_iso_fp(outfp, blocksize, iso_path, rr_path, joliet_path) def get_and_write(self, iso_path, local_path, blocksize=8192): # type: (str, str, int) -> None ''' (deprecated) Fetch a single file from the ISO and write it out to the specified file. Note that this will overwrite the contents of the local file if it already exists. Also note that 'iso_path' must be an absolute path to the file. Finally, the 'iso_path' can be an ISO9660 path, a Rock Ridge path, or a Joliet path. In the case of ambiguity, the Joliet path is tried first, followed by the ISO9660 path, followed by the Rock Ridge path. It is recommended to use the get_file_from_iso API instead to resolve this ambiguity. Parameters: iso_path - The absolute path to the file to get data from. local_path - The local filename to write the contents to. blocksize - The blocksize to use when copying data; the default is 8192. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') with open(local_path, 'wb') as fp: self._get_and_write_fp(utils.normpath(iso_path), fp, blocksize) def get_and_write_fp(self, iso_path, outfp, blocksize=8192): # type: (str, BinaryIO, int) -> None ''' (deprecated) Fetch a single file from the ISO and write it out to the file object. Note that 'iso_path' must be an absolute path to the file. Also note that the 'iso_path' can be an ISO9660 path, a Rock Ridge path, or a Joliet path. In the case of ambiguity, the Joliet path is tried first, followed by the ISO9660 path, followed by the Rock Ridge path. It is recommend to use the get_file_from_iso_fp API instead to resolve this ambiguity. Parameters: iso_path - The absolute path to the file to get data from. outfp - The file object to write data to. blocksize - The blocksize to use when copying data; the default is 8192. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') self._get_and_write_fp(utils.normpath(iso_path), outfp, blocksize) def write(self, filename, blocksize=32768, progress_cb=None, progress_opaque=None): # type: (str, int, Optional[Callable[[int, int, Any], None]], Optional[Any]) -> None ''' Write a properly formatted ISO out to the filename passed in. This also goes by the name of 'mastering'. Parameters: filename - The filename to write the data to. blocksize - The blocksize to use when copying data; set to 32768 by default. progress_cb - If not None, a function to call as the write call does its work. The callback function must have a signature of: def func(done, total, opaque). progress_opaque - User data to be passed to the progress callback. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') with open(filename, 'wb') as fp: self._write_fp(fp, blocksize, progress_cb, progress_opaque) def write_fp(self, outfp, blocksize=32768, progress_cb=None, progress_opaque=None): # type: (BinaryIO, int, Optional[Callable[[int, int, Any], None]], Optional[Any]) -> None ''' Write a properly formatted ISO out to the file object passed in. This also goes by the name of 'mastering'. Parameters: outfp - The file object to write the data to. blocksize - The blocksize to use when copying data; set to 32768 by default. progress_cb - If not None, a function to call as the write call does its work. The callback function must have a signature of: def func(done, total, opaque). progress_opaque - User data to be passed to the progress callback. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') self._write_fp(outfp, blocksize, progress_cb, progress_opaque) def add_fp(self, fp, length, iso_path=None, rr_name=None, joliet_path=None, file_mode=None, udf_path=None): # type: (BinaryIO, int, Optional[str], Optional[str], Optional[str], Optional[int], Optional[str]) -> None ''' Add a file to the ISO. If the ISO is a Rock Ridge one, then a Rock Ridge name must also be provided. If the ISO is a Joliet one, then a Joliet path may also be provided; while it is optional to do so, it is highly recommended. Note that the caller must ensure that 'fp' remains open for the lifetime of the PyCdlib object, as the PyCdlib class uses the file descriptor internally when writing (mastering) the ISO. To have PyCdlib manage this automatically, use 'add_file' instead. Parameters: fp - The file object to use for the contents of the new file. length - The length of the data for the new file. iso_path - The ISO9660 absolute path to the file destination on the ISO. rr_name - The Rock Ridge name of the file destination on the ISO. joliet_path - The Joliet absolute path to the file destination on the ISO. file_mode - The POSIX file_mode to apply to this file. This only applies if this is a Rock Ridge ISO. If this is None (the default), the permissions from the original file are used. udf_path - The UDF name of the file destination on the ISO. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if not utils.file_object_supports_binary(fp): raise pycdlibexception.PyCdlibInvalidInput('The fp argument must be in binary mode') num_bytes_to_add = self._add_fp(fp, length, False, iso_path, rr_name, joliet_path, udf_path, file_mode, False) self._finish_add(0, num_bytes_to_add) def add_file(self, filename, iso_path=None, rr_name=None, joliet_path=None, file_mode=None, udf_path=None): # type: (str, Optional[str], Optional[str], str, Optional[int], Optional[str]) -> None ''' Add a file to the ISO. If the ISO is a Rock Ridge one, then a Rock Ridge name must also be provided. If the ISO is a Joliet one, then a Joliet path may also be provided; while it is optional to do so, it is highly recommended. Parameters: filename - The filename to use for the data contents for the new file. iso_path - The ISO9660 absolute path to the file destination on the ISO. rr_name - The Rock Ridge name of the file destination on the ISO. joliet_path - The Joliet absolute path to the file destination on the ISO. file_mode - The POSIX file_mode to apply to this file. This only applies if this is a Rock Ridge ISO. If this is None (the default), the permissions from the original file are used. udf_path - The UDF name of the file destination on the ISO. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_bytes_to_add = self._add_fp(filename, os.stat(filename).st_size, True, iso_path, rr_name, joliet_path, udf_path, file_mode, False) self._finish_add(0, num_bytes_to_add) def modify_file_in_place(self, fp, length, iso_path, rr_name=None, # pylint: disable=unused-argument joliet_path=None, udf_path=None): # pylint: disable=unused-argument # type: (BinaryIO, int, str, Optional[str], Optional[str], Optional[str]) -> None ''' An API to modify a file in place on the ISO. This can be extremely fast (much faster than calling the write method), but has many restrictions. 1. The original ISO file pointer must have been opened for reading and writing. 2. Only an existing *file* can be modified; directories cannot be changed. 3. Only an existing file can be *modified*; no new files can be added or removed. 4. The new file contents must use the same number of extents (typically 2048 bytes) as the old file contents. If using this API to shrink a file, this is usually easy since the new contents can be padded out with zeros or newlines to meet the requirement. If using this API to grow a file, the new contents can only grow up to the next extent boundary. Unlike all other APIs in PyCdlib, this API actually modifies the originally opened on-disk file, so use it with caution. Parameters: fp - The file object to use for the contents of the new file. length - The length of the new data for the file. iso_path - The ISO9660 absolute path to the file destination on the ISO. rr_name - The Rock Ridge name of the file destination on the ISO. joliet_path - The Joliet absolute path to the file destination on the ISO. udf_path - The UDF absolute path to the file destination on the ISO. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if hasattr(self._cdfp, 'mode') and not self._cdfp.mode.startswith(('r+', 'w', 'a', 'rb+')): raise pycdlibexception.PyCdlibInvalidInput('To modify a file in place, the original ISO must have been opened in a write mode (r+, w, or a)') child = self._find_iso_record(utils.normpath(iso_path)) old_num_extents = utils.ceiling_div(child.get_data_length(), self.logical_block_size) new_num_extents = utils.ceiling_div(length, self.logical_block_size) if old_num_extents != new_num_extents: raise pycdlibexception.PyCdlibInvalidInput('When modifying a file in-place, the number of extents for a file cannot change!') if not child.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Cannot modify a directory with modify_file_in_place') if child.inode is None: raise pycdlibexception.PyCdlibInternalError('Child file found without inode') child.inode.update_fp(fp, length) # Remove the old size from the PVD size. for pvd in self.pvds: pvd.remove_from_space_size(child.get_data_length()) # And add the new size to the PVD size. for pvd in self.pvds: pvd.add_to_space_size(length) if self.enhanced_vd is not None: self.enhanced_vd.copy_sizes(self.pvd) # If we made it here, we have successfully updated all of the in-memory # metadata. Now we can go and modify the on-disk file. self._seek_to_extent(self.pvd.extent_location()) # First write out the PVD. rec = self.pvd.record() self._cdfp.write(rec) # Write out the joliet VD. if self.joliet_vd is not None: self._seek_to_extent(self.joliet_vd.extent_location()) rec = self.joliet_vd.record() self._cdfp.write(rec) # Write out the enhanced VD. if self.enhanced_vd is not None: self._seek_to_extent(self.enhanced_vd.extent_location()) rec = self.enhanced_vd.record() self._cdfp.write(rec) # We don't have to write anything out for UDF since it only tracks # extents, and we know we aren't changing the number of extents. # Write out the actual file contents. self._seek_to_extent(child.extent_location()) with inode.InodeOpenData(child.inode, self.logical_block_size) as (data_fp, data_len): utils.copy_data(data_len, self.logical_block_size, data_fp, self._cdfp) utils.zero_pad(self._cdfp, data_len, self.logical_block_size) # Finally write out the directory record entry. # This is a little tricky because of what things mean. First of all, # child.extents_to_here represents the total number of extents up to # this child in the parent. Thus, to get the absolute extent offset, # we start with the parent's extent location, add on the number of # extents to here, and remove 1 (since our offset will be zero-based). # Second, child.offset_to_here is the *last* byte that the child uses, # so to get the start of it we subtract off the length of the child. # Then we can multiply the extent location by the logical block size, # add on the offset, and get to the absolute location in the file. first_joliet = True for record, is_pvd in child.inode.linked_records: if isinstance(record, dr.DirectoryRecord): if self.joliet_vd is not None and id(record.vd) == id(self.joliet_vd) and first_joliet: first_joliet = False self.joliet_vd.remove_from_space_size(record.get_data_length()) self.joliet_vd.add_to_space_size(length) if record.parent is None: raise pycdlibexception.PyCdlibInternalError('Modifying file with empty parent') abs_extent_loc = record.parent.extent_location() + record.extents_to_here - 1 offset = record.offset_to_here - record.dr_len abs_offset = abs_extent_loc * self.logical_block_size + offset elif isinstance(record, udfmod.UDFFileEntry): abs_offset = record.extent_location() * self.logical_block_size record.set_data_length(length) self._cdfp.seek(abs_offset) self._cdfp.write(record.record()) def add_hard_link(self, **kwargs): # type: (Any) -> None ''' Add a hard link to the ISO. Hard links are alternate names for the same file contents that don't take up any additional space on the the ISO. This API can be used to create hard links between two files on the ISO9660 filesystem, between two files on the Joliet filesystem, or between a file on the ISO9660 filesystem and the Joliet filesystem. In all cases, exactly one old path must be specified, and exactly one new path must be specified. Note that this is an advanced API, so using it in combination with the higher-level APIs (like rm_file) may result in unexpected behavior. Once this API has been used, this API and rm_hard_link() should be preferred over add_file() and rm_file(), respectively. Parameters: iso_old_path - The old path on the ISO9660 filesystem to link from. iso_new_path - The new path on the ISO9660 filesystem to link to. joliet_old_path - The old path on the Joliet filesystem to link from. joliet_new_path - The new path on the Joliet filesystem to link to. rr_name - The Rock Ridge name to use for the new file if this is a Rock Ridge ISO and the new path is on the ISO9660 filesystem. boot_catalog_old - Use the El Torito boot catalog as the old path. udf_old_path - The old path on the UDF filesystem to link from. udf_new_path - The new path on the UDF filesystem to link to. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_old = 0 iso_old_path = None joliet_old_path = None boot_catalog_old = False udf_old_path = None keys_to_remove = [] for key in kwargs: if key == 'iso_old_path': if kwargs[key] is not None: num_old += 1 iso_old_path = utils.normpath(kwargs[key]) keys_to_remove.append(key) elif key == 'joliet_old_path': if kwargs[key] is not None: num_old += 1 joliet_old_path = self._normalize_joliet_path(kwargs[key]) keys_to_remove.append(key) elif key == 'boot_catalog_old': if kwargs[key] is not None: num_old += 1 boot_catalog_old = True if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInvalidInput('Attempting to make link to non-existent El Torito boot catalog') keys_to_remove.append(key) elif key == 'udf_old_path': if kwargs[key] is not None: num_old += 1 udf_old_path = utils.normpath(kwargs[key]) keys_to_remove.append(key) if num_old != 1: raise pycdlibexception.PyCdlibInvalidInput('Exactly one old path must be specified') # Once we've iterated over the keys we know about, remove them from # the map so that _add_hard_link_to_inode() can parse the rest. for key in keys_to_remove: del kwargs[key] # It would be nice to allow the addition of a link to the El Torito # Initial/Default Entry. Unfortunately, the information we need for # a 'hidden' Initial entry just doesn't exist on the ISO. In # particular, we don't know the real size that the file should be, we # only know the number of emulated sectors (512 bytes) that it will be # loaded into. Since the true length and the number of sectors are not # the same thing, we can't actually add a hard link. old_rec = dr.DirectoryRecord() # type: Union[dr.DirectoryRecord, udfmod.UDFFileEntry] fmode = 0 if iso_old_path is not None: # A link from a file on the ISO9660 filesystem... old_rec = self._find_iso_record(iso_old_path) if old_rec.rock_ridge is not None: fmode = old_rec.rock_ridge.get_file_mode() elif joliet_old_path is not None: # A link from a file on the Joliet filesystem... old_rec = self._find_joliet_record(joliet_old_path) elif boot_catalog_old: # A link from the El Torito boot catalog... if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInvalidInput('Attempting to make link to non-existent El Torito boot catalog') old_rec = self.eltorito_boot_catalog.dirrecords[0] elif udf_old_path is not None: # A link from a file on the UDF filesystem... (old_ident_unused, old_rec) = self._find_udf_record(udf_old_path) if old_rec is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot make hard link to a UDF file with an empty UDF File Entry') # Above we checked to make sure we got at least one old path, so we # don't need to worry about the else situation here. num_bytes_to_add = self._add_hard_link_to_inode(old_rec.inode, old_rec.get_data_length(), fmode, boot_catalog_old, **kwargs) self._finish_add(0, num_bytes_to_add) def rm_hard_link(self, iso_path=None, joliet_path=None, udf_path=None): # type: (Optional[str], Optional[str], Optional[str]) -> None ''' Remove a hard link from the ISO. If the number of links to a piece of data drops to zero, then the contents will be removed from the ISO. Thus, this can be thought of as a lower-level interface to rm_file. Either an ISO9660 path or a Joliet path must be passed to this API, but not both. Thus, this interface can be used to hide files from either the ISO9660 filesystem, the Joliet filesystem, or both (if there is another reference to the data on the ISO, such as in El Torito). Note that this is an advanced API, so using it in combination with the higher-level APIs (like rm_file) may result in unexpected behavior. Once this API has been used, this API and add_hard_link() should be preferred over rm_file() and add_file(), respectively. Parameters: iso_path - The ISO link path to remove. joliet_path - The Joliet link path to remove. udf_path - The UDF link path to remove. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if len([x for x in (iso_path, joliet_path, udf_path) if x]) != 1: raise pycdlibexception.PyCdlibInvalidInput('Must provide exactly one of iso_path, joliet_path, or udf_path') num_bytes_to_remove = 0 rec = None # type: Optional[Union[dr.DirectoryRecord, udfmod.UDFFileEntry]] if iso_path is not None: rec = self._find_iso_record(utils.normpath(iso_path)) num_bytes_to_remove += self._rm_dr_link(rec) elif joliet_path is not None: if self.joliet_vd is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot remove Joliet link from non-Joliet ISO') joliet_path_bytes = self._normalize_joliet_path(joliet_path) rec = self._find_joliet_record(joliet_path_bytes) num_bytes_to_remove += self._rm_dr_link(rec) elif udf_path is not None: # UDF hard link removal if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') (ident, rec) = self._find_udf_record(utils.normpath(udf_path)) if rec is None: # If the rec is None, that means that this pointed to an 'empty' # UDF File Entry. Just remove the UDF File Identifier, which is # as much as we can do. if ident is not None and ident.parent is not None: num_bytes_to_remove += self._rm_udf_file_ident(ident.parent, ident.fi) # We also have to remove the "zero" UDF File Entry, since nothing # else will. num_bytes_to_remove += self.logical_block_size else: num_bytes_to_remove += self._rm_udf_link(rec) else: raise pycdlibexception.PyCdlibInvalidInput("One of 'iso_path', 'joliet_path', or 'udf_path' must be specified") self._finish_remove(num_bytes_to_remove, True) def add_directory(self, iso_path=None, rr_name=None, joliet_path=None, file_mode=None, udf_path=None): # type: (Optional[str], Optional[str], Optional[str], int, Optional[str]) -> None ''' Add a directory to the ISO. At least one of an iso_path, joliet_path, or udf_path must be provided. Providing joliet_path on a non-Joliet ISO, or udf_path on a non-UDF ISO, is an error. If the ISO contains Rock Ridge, then a Rock Ridge name must be provided. Parameters: iso_path - The ISO9660 absolute path to use for the directory. rr_name - The Rock Ridge name to use for the directory. joliet_path - The Joliet absolute path to use for the directory. file_mode - The POSIX file mode to use for the directory. This only applies for Rock Ridge ISOs. udf_path - The UDF absolute path to use for the directory. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if iso_path is None and joliet_path is None and udf_path is None: raise pycdlibexception.PyCdlibInvalidInput('Either iso_path or joliet_path must be passed') if file_mode is not None and not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('A file mode can only be specified for Rock Ridge ISOs') # For backwards-compatibility reasons, if the mode was not specified we # just assume 555. We should probably eventually make file_mode # required for Rock Ridge and remove this assumption. if file_mode is None: file_mode = 0o040555 num_bytes_to_add = 0 if iso_path is not None: iso_path_bytes = utils.normpath(iso_path) new_rr_name = self._check_rr_name(rr_name) depth = len(utils.split_path(iso_path_bytes)) if not self.rock_ridge and self.enhanced_vd is None: _check_path_depth(iso_path_bytes) (name, parent) = self._iso_name_and_parent_from_path(iso_path_bytes) _check_iso9660_directory(name, self.interchange_level) relocated = False fake_dir_rec = None orig_parent = None iso9660_name = name if self.rock_ridge and (depth % 8) == 0 and self.enhanced_vd is None: # If the depth was a multiple of 8, then we are going to have to # make a relocated entry for this record. num_bytes_to_add += self._find_or_create_rr_moved() # With a depth of 8, we have to add the directory both to the # original parent with a CL link, and to the new parent with an # RE link. Here we make the 'fake' record, as a child of the # original place; the real one will be done below. fake_dir_rec = dr.DirectoryRecord() fake_dir_rec.new_dir(self.pvd, name, parent, self.pvd.sequence_number(), self.rock_ridge, new_rr_name, self.logical_block_size, True, False, self.xa, file_mode) num_bytes_to_add += self._add_child_to_dr(fake_dir_rec) # The fake dir record doesn't get an entry in the path table # record. relocated = True orig_parent = parent parent = self._rr_moved_record # Since we are moving the entry underneath the RR_MOVED # directory, there is now the chance of a name collision (this # can't happen without relocation since _add_child_to_dr() below # won't allow duplicate names). Check for that here and # generate a new name. index = 0 while True: for child in self._rr_moved_record.children: if child.file_ident == iso9660_name: # Python 3.4 doesn't support substitution with a byte # array, so we do it as a string and encode to bytes. iso9660_name = name + ('%03d' % (index)).encode() index += 1 break else: break rec = dr.DirectoryRecord() rec.new_dir(self.pvd, iso9660_name, parent, self.pvd.sequence_number(), self.rock_ridge, new_rr_name, self.logical_block_size, False, relocated, self.xa, file_mode) num_bytes_to_add += self._add_child_to_dr(rec) if rec.rock_ridge is not None: if relocated and fake_dir_rec is not None and fake_dir_rec.rock_ridge is not None: fake_dir_rec.rock_ridge.cl_to_moved_dr = rec rec.rock_ridge.moved_to_cl_dr = fake_dir_rec num_bytes_to_add += self._update_rr_ce_entry(rec) self._create_dot(self.pvd, rec, self.rock_ridge, self.xa, file_mode) parent_file_mode = -1 if parent.rock_ridge is not None: parent_file_mode = parent.rock_ridge.get_file_mode() else: if parent.is_root: parent_file_mode = file_mode dotdot = self._create_dotdot(self.pvd, rec, self.rock_ridge, relocated, self.xa, parent_file_mode) if dotdot.rock_ridge is not None and relocated: dotdot.rock_ridge.parent_link = orig_parent # We always need to add an entry to the path table record. ptr = path_table_record.PathTableRecord() ptr.new_dir(iso9660_name) num_bytes_to_add += self._add_to_ptr_size(ptr) + self.logical_block_size rec.set_ptr(ptr) if joliet_path is not None: num_bytes_to_add += self._add_joliet_dir(self._normalize_joliet_path(joliet_path)) if udf_path is not None: if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') udf_path_bytes = utils.normpath(udf_path) (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_path_bytes) file_ident = udfmod.UDFFileIdentifierDescriptor() file_ident.new(True, False, udf_name, udf_parent) num_new_extents = udf_parent.add_file_ident_desc(file_ident, self.logical_block_size) num_bytes_to_add += num_new_extents * self.logical_block_size file_entry = udfmod.UDFFileEntry() file_entry.new(0, 'dir', udf_parent, self.logical_block_size) file_ident.file_entry = file_entry file_entry.file_ident = file_ident num_bytes_to_add += self.logical_block_size udf_dotdot = udfmod.UDFFileIdentifierDescriptor() udf_dotdot.new(True, True, b'', udf_parent) num_new_extents = file_ident.file_entry.add_file_ident_desc(udf_dotdot, self.logical_block_size) num_bytes_to_add += num_new_extents * self.logical_block_size if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_impl_use.num_dirs += 1 self._finish_add(0, num_bytes_to_add) def add_joliet_directory(self, joliet_path): # type: (str) -> None ''' (deprecated) Add a directory to the Joliet portion of the ISO. Since Joliet occupies a completely different context than ISO9660, this method can be invoked to create a completely different directory structure in the Joliet context, though that is generally not advised. It is recommended to use the 'joliet_path' argument of the 'add_directory' instead of this method. Parameters: joliet_path - The Joliet directory to create. Returns: Nothing. ''' self.add_directory(joliet_path=joliet_path) def rm_file(self, iso_path=None, rr_name=None, joliet_path=None, # pylint: disable=unused-argument udf_path=None): # type: (Optional[str], Optional[str], Optional[str], Optional[str]) -> None ''' Remove a file from the ISO. This removes the data and the listing of the file from all contexts, even when only one path is given (to only remove it from a single context, use rm_hard_link() instead). Due to some complexities of the ISO format, removal of zero-byte files from all contexts does not automatically happen, so this method may need to be called more than once for zero-byte files. Parameters: iso_path - The path to the file to remove. rr_name - The Rock Ridge name of the file to remove. joliet_path - The Joliet path to the file to remove. udf_path - The UDF path to the file to remove. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_bytes_to_remove = 0 if iso_path is not None: num_bytes_to_remove += self._rm_file_via_iso_path(iso_path) elif joliet_path is not None: num_bytes_to_remove += self._rm_file_via_joliet_path(joliet_path) elif udf_path is not None: num_bytes_to_remove += self._rm_file_via_udf_path(udf_path) else: raise pycdlibexception.PyCdlibInternalError("At least one of 'iso_path', 'joliet_path', or 'udf_path' must be specified") self._finish_remove(num_bytes_to_remove, True) def rm_directory(self, iso_path=None, rr_name=None, joliet_path=None, # pylint: disable=unused-argument udf_path=None): # type: (Optional[str], Optional[str], Optional[str], Optional[str]) -> None ''' Remove a directory from the ISO. The directory must be empty. Parameters: iso_path - The path to the directory to remove. rr_name - The Rock Ridge name of the directory to remove. joliet_path - The Joliet path to the directory to remove. udf_path - The UDF absolute path to the directory to remove. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if iso_path is None and joliet_path is None and udf_path is None: raise pycdlibexception.PyCdlibInvalidInput('Either iso_path or joliet_path must be passed') num_bytes_to_remove = 0 if iso_path is not None: iso_path_bytes = utils.normpath(iso_path) if iso_path_bytes == b'/': raise pycdlibexception.PyCdlibInvalidInput('Cannot remove base directory') child = self._find_iso_record(iso_path_bytes) if not child.is_dir(): raise pycdlibexception.PyCdlibInvalidInput('Cannot remove a file with rm_directory (try rm_file instead)') if len(child.children) > 2: raise pycdlibexception.PyCdlibInvalidInput('Directory must be empty to use rm_directory') num_bytes_to_remove += self._remove_child_from_dr(child, child.index_in_parent) if child.ptr is not None: num_bytes_to_remove += self._remove_from_ptr_size(child.ptr) # Remove space for the directory itself. num_bytes_to_remove += child.get_data_length() if child.rock_ridge is not None and child.rock_ridge.relocated_record(): # OK, this child was relocated. If the parent of this relocated # record is empty (only . and ..), we can remove it. parent = child.parent if parent is None: raise pycdlibexception.PyCdlibInternalError('Relocated child has empty parent; this should not be') if len(parent.children) == 2: if parent.parent is None: raise pycdlibexception.PyCdlibInternalError('Tried to remove a directory that has no parent; this should not happen') for index, c in enumerate(parent.parent.children): if c.file_ident == parent.file_ident: parent_index = index break else: raise pycdlibexception.PyCdlibInvalidISO('Could not find parent in its own parent!') num_bytes_to_remove += self._remove_child_from_dr(parent, parent_index) num_bytes_to_remove += parent.get_data_length() if parent.ptr is not None: num_bytes_to_remove += self._remove_from_ptr_size(parent.ptr) cl = child.rock_ridge.moved_to_cl_dr if cl is None: raise pycdlibexception.PyCdlibInternalError('Invalid child link record') if cl.parent is None: raise pycdlibexception.PyCdlibInternalError('Invalid parent to child link record; this should not be') for index, c in enumerate(cl.parent.children): if cl.file_ident == c.file_ident: clindex = index break else: raise pycdlibexception.PyCdlibInvalidISO('CL record does not exist') if cl.children: raise pycdlibexception.PyCdlibInvalidISO('Parent link should have no children!') num_bytes_to_remove += self._remove_child_from_dr(cl, clindex) # We do not remove additional space from the PVD for the # child_link record because it is a 'fake' record that has no # size. if child.rock_ridge is not None and child.rock_ridge.dr_entries.ce_record is not None and child.rock_ridge.ce_block is not None: child.rock_ridge.ce_block.remove_entry(child.rock_ridge.dr_entries.ce_record.offset_cont_area, child.rock_ridge.dr_entries.ce_record.len_cont_area) if joliet_path is not None: num_bytes_to_remove += self._rm_joliet_dir(self._normalize_joliet_path(joliet_path)) if udf_path is not None: if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') udf_path_bytes = utils.normpath(udf_path) if udf_path_bytes == b'/': raise pycdlibexception.PyCdlibInvalidInput('Cannot remove base directory') (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_path_bytes) num_extents_to_remove = udf_parent.remove_file_ident_desc_by_name(udf_name, self.logical_block_size) # Remove space (if necessary) in the parent File Identifier # Descriptor area. num_bytes_to_remove += num_extents_to_remove * self.logical_block_size # Remove space for the File Entry. num_bytes_to_remove += self.logical_block_size # Remove space for the list of File Identifier Descriptors. num_bytes_to_remove += self.logical_block_size if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_impl_use.num_dirs -= 1 self._find_udf_record.cache_clear() # pylint: disable=no-member self._finish_remove(num_bytes_to_remove, True) def rm_joliet_directory(self, joliet_path): # type: (str) -> None ''' (deprecated) Remove a Joliet directory from the ISO. It is recommended to use the 'joliet_path' parameter to 'rm_directory' instead. Parameters: joliet_path - The Joliet path to the directory to remove. Returns: Nothing. ''' self.rm_directory(joliet_path=joliet_path) def add_eltorito(self, bootfile_path, bootcatfile=None, rr_bootcatname=None, joliet_bootcatfile=None, boot_load_size=None, platform_id=0, boot_info_table=False, efi=False, media_name='noemul', bootable=True, boot_load_seg=0, udf_bootcatfile=None): # type: (str, Optional[str], Optional[str], Optional[str], int, int, bool, bool, str, bool, int, Optional[str]) -> None ''' Add an El Torito Boot Record, and associated files, to the ISO. The file that will be used as the bootfile must be passed into this function and must already be present on the ISO. Parameters: bootfile_path - The file to use as the boot file; it must already exist on this ISO. bootcatfile - The fake file to use as the boot catalog entry; set to BOOT.CAT;1 by default. rr_bootcatname - The Rock Ridge name for the fake file to use as the boot catalog entry; set to 'boot.cat' by default. joliet_bootcatfile - The Joliet name for the fake file to use as the boot catalog entry; set to 'boot.cat' by default. boot_load_size - The number of sectors to use for the boot entry; if set to None (the default), the number of sectors will be calculated. platform_id - The platform ID to set for the El Torito entry; 0 is for x86, 1 is for Power PC, 2 is for Mac, and 0xef is for UEFI. 0 is the default. boot_info_table - Whether to add a boot info table to the ISO. The default is False. efi - Whether this is an EFI entry for El Torito. The default is False. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. bootable - Whether the boot media is bootable. The default is True. boot_load_seg - The load segment address of the boot image. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') # In order to add an El Torito boot, we need to do the following: # 1. Find the boot file record (which must already exist). # 2. Construct a BootRecord. # 3. Construct a BootCatalog, and add it to the filesystem. # 4. Add the boot record to the ISO. if not bootcatfile: bootcatfile = '/BOOT.CAT;1' bootfile_path_bytes = utils.normpath(bootfile_path) if self.joliet_vd is not None: if not joliet_bootcatfile: joliet_bootcatfile = '/boot.cat' else: if joliet_bootcatfile: raise pycdlibexception.PyCdlibInvalidInput('A joliet path must not be passed when adding El Torito to a non-Joliet ISO') if self.udf_root is not None: if not udf_bootcatfile: udf_bootcatfile = '/boot.cat' else: if udf_bootcatfile: raise pycdlibexception.PyCdlibInvalidInput('A UDF path must not be passed when adding El Torito to a non-UDF ISO') # Step 1. boot_dirrecord = self._find_iso_record(bootfile_path_bytes) if boot_load_size is None: sector_count = utils.ceiling_div(boot_dirrecord.get_data_length(), self.logical_block_size) * self.logical_block_size // 512 else: sector_count = boot_load_size if boot_dirrecord.inode is None: raise pycdlibexception.PyCdlibInternalError('Tried to add an empty boot dirrecord inode to the El Torito boot catalog') if boot_info_table: orig_len = boot_dirrecord.get_data_length() bi_table = eltorito.EltoritoBootInfoTable() with inode.InodeOpenData(boot_dirrecord.inode, self.logical_block_size) as (data_fp, data_len): bi_table.new(self.pvd, boot_dirrecord.inode, orig_len, self._calculate_eltorito_boot_info_table_csum(data_fp, data_len)) boot_dirrecord.inode.add_boot_info_table(bi_table) system_type = 0 if media_name == 'hdemul': with inode.InodeOpenData(boot_dirrecord.inode, self.logical_block_size) as (data_fp, data_len): disk_mbr = data_fp.read(512) if len(disk_mbr) != 512: raise pycdlibexception.PyCdlibInvalidInput('Could not read entire HD MBR, must be at least 512 bytes') system_type = eltorito.hdmbrcheck(disk_mbr, sector_count, bootable) num_bytes_to_add = 0 if self.eltorito_boot_catalog is not None: # An El Torito Boot Catalog already exists; add a new section. self.eltorito_boot_catalog.add_section(boot_dirrecord.inode, sector_count, boot_load_seg, media_name, system_type, efi, bootable) else: # Step 2. br = headervd.BootRecord() br.new(b'EL TORITO SPECIFICATION') self.brs.append(br) # On a UDF ISO, adding a new Boot Record doesn't actually increase # the size, since there are a bunch of gaps at the beginning. if not self._has_udf: num_bytes_to_add += self.logical_block_size # Step 3. self.eltorito_boot_catalog = eltorito.EltoritoBootCatalog(br) self.eltorito_boot_catalog.new(br, boot_dirrecord.inode, sector_count, boot_load_seg, media_name, system_type, platform_id, bootable) # Step 4. rrname = '' if self.rock_ridge: if rr_bootcatname is None: rrname = 'boot.cat' else: rrname = rr_bootcatname num_bytes_to_add += self._add_fp(None, self.logical_block_size, False, bootcatfile, rrname, joliet_bootcatfile, udf_bootcatfile, None, True) self._finish_add(0, num_bytes_to_add) def rm_eltorito(self): # type: () -> None ''' Remove the El Torito boot record (and Boot Catalog) from the ISO. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInvalidInput('This ISO does not have an El Torito Boot Record') for brindex, br in enumerate(self.brs): if br.boot_system_identifier == b'EL TORITO SPECIFICATION'.ljust(32, b'\x00'): eltorito_index = brindex break else: # There was a boot catalog, but no corresponding boot record. This # should never happen. raise pycdlibexception.PyCdlibInternalError('El Torito boot catalog found with no corresponding boot record') del self.brs[eltorito_index] num_bytes_to_remove = 0 # On a UDF ISO, removing the Boot Record doesn't actually decrease # the size, since there are a bunch of gaps at the beginning. if not self._has_udf: num_bytes_to_remove += self.logical_block_size # Remove all of the DirectoryRecord/UDFFileEntries associated with # the Boot Catalog. for rec in self.eltorito_boot_catalog.dirrecords: if isinstance(rec, dr.DirectoryRecord): num_bytes_to_remove += self._rm_dr_link(rec) elif isinstance(rec, udfmod.UDFFileEntry): num_bytes_to_remove += self._rm_udf_link(rec) else: # This should never happen. raise pycdlibexception.PyCdlibInternalError('Saw an El Torito record that was neither ISO nor UDF') # Remove the linkage from the El Torito Entries to the inodes. entries_to_remove = [self.eltorito_boot_catalog.initial_entry] for sec in self.eltorito_boot_catalog.sections: for entry in sec.section_entries: entries_to_remove.append(entry) for entry in entries_to_remove: if entry.inode is not None: new_list = [] for linkrec, is_pvd in entry.inode.linked_records: if id(linkrec) != id(entry): new_list.append((linkrec, is_pvd)) entry.inode.linked_records = new_list num_bytes_to_remove += len(self.eltorito_boot_catalog.record()) self.eltorito_boot_catalog = None self._finish_remove(num_bytes_to_remove, True) def add_symlink(self, symlink_path=None, rr_symlink_name=None, rr_path=None, joliet_path=None, udf_symlink_path=None, udf_target=None): # type: (Optional[str], Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> None ''' Add a symlink from rr_symlink_name to the rr_path. The ISO must have either Rock Ridge or UDF support (or both). Parameters: symlink_path - The ISO9660 path of the symlink itself on the ISO. rr_symlink_name - The Rock Ridge name of the symlink itself on the ISO. rr_path - The path that the symlink points to on the Rock Ridge part of the ISO. joliet_path - The Joliet path of the symlink (if this ISO has Joliet). udf_symlink_path - The UDF path of the symlink itself on the ISO. udf_target - The UDF name of the entry on the ISO that the symlink points to. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') # There are actually quite a few combinations and rules to think about # here. Rules: # # 1. The ISO must have Rock Ridge or UDF (or both). # 2. If the rr_symlink_name or rr_path are supplied, the ISO must be a # Rock Ridge one. # 3. If the rr_symlink_name or rr_path are supplied, both must be # supplied. # 4. If udf_symlink_path or udf_target are supplied, the ISO must be a # UDF one. # 5. If udf_symlink_path or udf_target are supplied, both must be # supplied. # 6. For backwards compatibility reasons, symlink_path is a little # weird. If making a Rock Ridge symlink it is required. If making # a UDF symlink, it is optional. # 7. If a symlink_path is specified, at least one of the pair of # (rr_symlink_name, rr_path) or (udf_symlink_path, udf_target) must # be supplied. # 8. All arguments cannot be None. # 9. joliet_path is the optional path on the Joliet filesystem; if it # is provided, the ISO must be a Joliet one. if not self.rock_ridge and not self._has_udf: # Rule 1 raise pycdlibexception.PyCdlibInvalidInput('Can only add a symlink to a Rock Ridge or UDF ISO') if (rr_symlink_name is not None or rr_path is not None) and not self.rock_ridge: # Rule 2 raise pycdlibexception.PyCdlibInvalidInput('A Rock Ridge symlink can only be created on a Rock Ridge ISO') rr_tuple = (rr_symlink_name, rr_path) rr_vars_provided = len([x for x in rr_tuple if x is not None]) if rr_vars_provided > 0 and rr_vars_provided != 2: # Rule 3 raise pycdlibexception.PyCdlibInvalidInput("Both of 'rr_symlink_name' and 'rr_path' must be provided for a Rock Ridge symlink") if (udf_symlink_path is not None or udf_target is not None) and not self._has_udf: # Rule 4 raise pycdlibexception.PyCdlibInvalidInput('A UDF symlink can only be created on a UDF ISO') udf_tuple = (udf_symlink_path, udf_target) udf_vars_provided = len([x for x in udf_tuple if x is not None]) if udf_vars_provided > 0 and udf_vars_provided != 2: # Rule 5 raise pycdlibexception.PyCdlibInvalidInput("Both of 'udf_symlink_path' and 'udf_target' must be provided for a UDF symlink") if rr_symlink_name is not None and symlink_path is None: # Rule 6 raise pycdlibexception.PyCdlibInvalidInput("When making a Rock Ridge symlink 'symlink_path' is required") if symlink_path is not None and rr_vars_provided == 0 and udf_vars_provided == 0: # Rule 7 raise pycdlibexception.PyCdlibInvalidInput('Either a Rock Ridge or a UDF symlink must be specified') all_vars_provided = len([x for x in ((symlink_path,) + rr_tuple + udf_tuple) if x is not None]) if all_vars_provided == 0: # Rule 8 raise pycdlibexception.PyCdlibInvalidInput('Either a Rock Ridge or a UDF symlink must be specified') if joliet_path is not None and self.joliet_vd is None: # Rule 9 raise pycdlibexception.PyCdlibInvalidInput('A Joliet path can only be specified for a Joliet ISO') # Checks complete, we can go on to make the symlink. num_bytes_to_add = 0 if symlink_path is not None: symlink_path_bytes = utils.normpath(symlink_path) (name, parent) = self._iso_name_and_parent_from_path(symlink_path_bytes) rec = dr.DirectoryRecord() if rr_symlink_name is not None and rr_path is not None: # We specifically do *not* normalize rr_path here, since that # potentially changes the meaning of what the user wanted. rr_symlink_name_bytes = rr_symlink_name.encode('utf-8') rec.new_symlink(self.pvd, name, parent, rr_path.encode('utf-8'), self.pvd.sequence_number(), self.rock_ridge, rr_symlink_name_bytes, self.xa) num_bytes_to_add += self._add_child_to_dr(rec) num_bytes_to_add += self._update_rr_ce_entry(rec) if udf_symlink_path is not None and udf_target is not None: # If we aren't making a Rock Ridge symlink at the same time, we need # to add a new zero-byte file to the ISO. if rr_path is None: tmp_joliet_path = joliet_path if tmp_joliet_path is None: tmp_joliet_path = '' num_bytes_to_add += self._add_fp(None, 0, False, symlink_path, '', tmp_joliet_path, '', None, False) udf_symlink_path_bytes = utils.normpath(udf_symlink_path) # We specifically do *not* normalize udf_target here, since that # potentially changes the meaning of what the user wanted. (udf_name, udf_parent) = self._udf_name_and_parent_from_path(udf_symlink_path_bytes) file_ident = udfmod.UDFFileIdentifierDescriptor() file_ident.new(False, False, udf_name, udf_parent) num_new_extents = udf_parent.add_file_ident_desc(file_ident, self.logical_block_size) num_bytes_to_add += num_new_extents * self.logical_block_size # Generate the bytearry representing the symlink. symlink_bytearray = udfmod.symlink_to_bytes(udf_target) file_entry = udfmod.UDFFileEntry() file_entry.new(len(symlink_bytearray), 'symlink', udf_parent, self.logical_block_size) file_ident.file_entry = file_entry file_entry.file_ident = file_ident num_bytes_to_add += self.logical_block_size num_bytes_to_add += file_entry.info_len # The inode for the symlink array. ino = inode.Inode() ino.new(len(symlink_bytearray), BytesIO(symlink_bytearray), False, 0) ino.linked_records.append((file_entry, False)) ino.num_udf += 1 file_entry.inode = ino self.inodes.append(ino) if self.udf_logical_volume_integrity is not None: self.udf_logical_volume_integrity.logical_volume_impl_use.num_files += 1 # Note that we explicitly do *not* link this record to the ISO9660 # record; that's because there is no way to correlate them during # parse time. Instead, we treat them as individual entries, which # has the knock-on effect of requiring two operations to remove; # rm_file() to remove the ISO9660 record, and rm_hard_link() to # remove the UDF record. if joliet_path is not None: if self.joliet_vd is None: raise pycdlibexception.PyCdlibInternalError('Tried to add a Joliet path to a non-Joliet ISO') joliet_path_bytes = self._normalize_joliet_path(joliet_path) (joliet_name, joliet_parent) = self._joliet_name_and_parent_from_path(joliet_path_bytes) # Add in a "fake" symlink entry for Joliet. joliet_rec = dr.DirectoryRecord() joliet_rec.new_file(self.joliet_vd, 0, joliet_name, joliet_parent, self.joliet_vd.sequence_number(), '', b'', self.xa, -1) num_bytes_to_add += self._add_child_to_dr(joliet_rec) self._finish_add(0, num_bytes_to_add) def list_dir(self, iso_path, joliet=False): # type: (str, bool) -> Generator ''' (deprecated) Generate a list of all of the file/directory objects in the specified location on the ISO. It is recommended to use the 'list_children' API instead. Parameters: iso_path - The path on the ISO to look up information for. joliet - Whether to look for the path in the Joliet portion of the ISO. Yields: Children of this path. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if joliet: rec = self._get_entry(None, None, self._normalize_joliet_path(iso_path)) else: normpath = utils.normpath(iso_path) try_rr = False try: rec = self._get_entry(normpath, None, None) except pycdlibexception.PyCdlibInvalidInput: try_rr = True if try_rr: rec = self._get_entry(None, normpath, None) for c in _yield_children(rec): yield c def list_children(self, **kwargs): # type: (str) -> Generator ''' Generate a list of all of the file/directory objects in the specified location on the ISO. Parameters: iso_path - The absolute path on the ISO to list the children for. rr_path - The absolute Rock Ridge path on the ISO to list the children for. joliet_path - The absolute Joliet path on the ISO to list the children for. udf_path - The absolute UDF path on the ISO to list the children for. Yields: Children of this path. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_paths = 0 for key in kwargs: if key in ('joliet_path', 'rr_path', 'iso_path', 'udf_path'): if kwargs[key] is not None: num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if 'udf_path' in kwargs: udf_rec = self._get_udf_entry(kwargs['udf_path']) if not udf_rec.is_dir(): raise pycdlibexception.PyCdlibInvalidInput('UDF File Entry is not a directory!') for fi_desc in udf_rec.fi_descs: yield fi_desc.file_entry else: if 'joliet_path' in kwargs: rec = self._get_entry(None, None, self._normalize_joliet_path(kwargs['joliet_path'])) elif 'rr_path' in kwargs: rec = self._get_entry(None, utils.normpath(kwargs['rr_path']), None) else: rec = self._get_entry(utils.normpath(kwargs['iso_path']), None, None) for c in _yield_children(rec): yield c def get_entry(self, iso_path, joliet=False): # type: (str, bool) -> dr.DirectoryRecord ''' (deprecated) Get the directory record for a particular path. It is recommended to use the 'get_record' API instead. Parameters: iso_path - The path on the ISO to look up information for. joliet - Whether to look for the path in the Joliet portion of the ISO. Returns: A dr.DirectoryRecord object representing the path. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if joliet: return self._get_entry(None, None, self._normalize_joliet_path(iso_path)) return self._get_entry(utils.normpath(iso_path), None, None) def get_record(self, **kwargs): # type: (str) -> Union[dr.DirectoryRecord, udfmod.UDFFileEntry] ''' Get the directory record for a particular path. Parameters: iso_path - The absolute path on the ISO9660 filesystem to get the record for. rr_path - The absolute path on the Rock Ridge filesystem to get the record for. joliet_path - The absolute path on the Joliet filesystem to get the record for. udf_path - The absolute path on the UDF filesystem to get the record for. Returns: An object that represents the path. This may be a dr.DirectoryRecord object (in the cases of iso_path, rr_path, or joliet_path), or a udf.UDFFileEntry object (in the case of udf_path). ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_paths = 0 for key in kwargs: if key in ('joliet_path', 'rr_path', 'iso_path', 'udf_path'): if kwargs[key] is not None: num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if 'joliet_path' in kwargs: return self._get_entry(None, None, self._normalize_joliet_path(kwargs['joliet_path'])) if 'rr_path' in kwargs: return self._get_entry(None, utils.normpath(kwargs['rr_path']), None) if 'udf_path' in kwargs: return self._get_udf_entry(kwargs['udf_path']) return self._get_entry(utils.normpath(kwargs['iso_path']), None, None) def add_isohybrid(self, part_entry=1, mbr_id=None, part_offset=0, geometry_sectors=32, geometry_heads=64, part_type=None, mac=False, efi=None): # type: (int, Optional[int], int, int, int, Optional[int], bool, Optional[bool]) -> None ''' Make an ISO a 'hybrid', which means that it can be booted either from a CD or from more traditional media (like a USB stick). This requires that the ISO already have El Torito, and will use the El Torito boot file as a bootable image. That image must contain a certain signature in order to work as a hybrid (if using syslinux, this generally means the isohdpfx.bin files). Parameters: part_entry - The partition entry to use; one by default. mbr_id - The mbr_id to use. If set to None (the default), a random one will be generated. part_offset - The partition offset to use; zero by default. geometry_sectors - The number of sectors to assign; thirty-two by default. geometry_heads - The number of heads to assign; sixty-four by default. part_type - The partition type to assign; twenty-three by default, but will automatically be set to 0 if mac or efi are True. mac - Add support for Mac; False by default. efi - Add support for EFI; False by default, but will automatically be set to True if mac is True. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if self.eltorito_boot_catalog is None: raise pycdlibexception.PyCdlibInvalidInput('The ISO must have an El Torito Boot Record to add isohybrid support') if self.eltorito_boot_catalog.initial_entry.sector_count != 4: raise pycdlibexception.PyCdlibInvalidInput('El Torito Boot Catalog sector count must be 4 (was actually 0x%x)' % (self.eltorito_boot_catalog.initial_entry.sector_count)) if efi is not None: if not efi and mac: raise pycdlibexception.PyCdlibInvalidInput('If mac is True, efi must also be True') else: efi = False if mac: efi = True if part_type is None: part_type = 0x17 if mac or efi: part_type = 0 # Check that the eltorito boot file contains the appropriate # signature (offset 0x40, '\xFB\xC0\x78\x70'). with inode.InodeOpenData(self.eltorito_boot_catalog.initial_entry.inode, self.logical_block_size) as (data_fp, data_len_unused): data_fp.seek(0x40, os.SEEK_CUR) signature = data_fp.read(4) if signature != b'\xfb\xc0\x78\x70': raise pycdlibexception.PyCdlibInvalidInput('Invalid signature on boot file for iso hybrid') self.isohybrid_mbr = isohybrid.IsoHybrid() self.isohybrid_mbr.new(efi, mac, part_entry, mbr_id, part_offset, geometry_sectors, geometry_heads, part_type) def rm_isohybrid(self): # type: () -> None ''' Remove the 'hybridization' of an ISO, making it a traditional ISO again. This means the ISO will no longer be able to be copied and booted off of traditional media (like USB sticks). Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') self.isohybrid_mbr = None def full_path_from_dirrecord(self, rec, rockridge=False): # type: (Union[dr.DirectoryRecord, udfmod.UDFFileEntry], bool) -> str ''' Get the absolute path of a directory record. Parameters: rec - The directory record to get the full path for. rockridge - Whether to get the rock ridge full path. Returns: A string representing the absolute path to the file on the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') ret = b'' if isinstance(rec, dr.DirectoryRecord): encoding = 'utf-8' if self.joliet_vd is not None and id(rec.vd) == id(self.joliet_vd): encoding = 'utf-16_be' slash = '/'.encode(encoding) # A root entry has no Rock Ridge entry, even on a Rock Ridge ISO. # Always return / here. if rec.is_root: return '/' if rockridge and rec.rock_ridge is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot generate a Rock Ridge path on a non-Rock Ridge ISO') parent = rec # type: Optional[dr.DirectoryRecord] while parent is not None: if not parent.is_root: if rockridge and parent.rock_ridge is not None: ret = slash + parent.rock_ridge.name() + ret else: ret = slash + parent.file_identifier() + ret parent = parent.parent else: if rec.parent is None: return '/' if rec.file_ident is not None: encoding = rec.file_ident.encoding else: encoding = 'utf-8' slash = '/'.encode(encoding) udfparent = rec # type: Optional[udfmod.UDFFileEntry] while udfparent is not None: ident = udfparent.file_identifier() if ident != b'/': ret = slash + ident + ret udfparent = udfparent.parent if sys.version_info >= (3, 0): # Python 3, just return the encoded version. return ret.decode(encoding) # Python 2. return ret.decode(encoding).encode('utf-8') def duplicate_pvd(self): # type: () -> None ''' Add a duplicate PVD to the ISO. This is a mostly useless feature allowed by Ecma-119 to have duplicate PVDs to avoid possible corruption. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') pvd = headervd.PrimaryOrSupplementaryVD(headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY) pvd.copy(self.pvd) self.pvds.append(pvd) self._finish_add(self.logical_block_size, 0) def set_hidden(self, iso_path=None, rr_path=None, joliet_path=None): # type: (Optional[str], Optional[str], Optional[str]) -> None ''' Set the ISO9660 hidden attribute on a file or directory. This will cause the file or directory not to show up when listing entries on the ISO. Exactly one of iso_path, rr_path, or joliet_path must be specified. Parameters: iso_path - The path on the ISO to set the hidden bit on. rr_path - The Rock Ridge path on the ISO to set the hidden bit on. joliet_path - The Joliet path on the ISO to set the hidden bit on. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if len([x for x in (iso_path, rr_path, joliet_path) if x is not None]) != 1: raise pycdlibexception.PyCdlibInvalidInput('Must provide exactly one of iso_path, rr_path, or joliet_path') if iso_path is not None: rec = self._find_iso_record(utils.normpath(iso_path)) elif rr_path is not None: rec = self._find_rr_record(utils.normpath(rr_path)) elif joliet_path is not None: joliet_path_bytes = self._normalize_joliet_path(joliet_path) rec = self._find_joliet_record(joliet_path_bytes) rec.change_existence(True) def clear_hidden(self, iso_path=None, rr_path=None, joliet_path=None): # type: (Optional[str], Optional[str], Optional[str]) -> None ''' Clear the ISO9660 hidden attribute on a file or directory. This will cause the file or directory to show up when listing entries on the ISO. Exactly one of iso_path, rr_path, or joliet_path must be specified. Parameters: iso_path - The path on the ISO to clear the hidden bit from. rr_path - The Rock Ridge path on the ISO to clear the hidden bit from. joliet_path - The Joliet path on the ISO to clear the hidden bit from. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if len([x for x in (iso_path, rr_path, joliet_path) if x is not None]) != 1: raise pycdlibexception.PyCdlibInvalidInput('Must provide exactly one of iso_path, rr_path, or joliet_path') if iso_path is not None: rec = self._find_iso_record(utils.normpath(iso_path)) elif rr_path is not None: rec = self._find_rr_record(utils.normpath(rr_path)) elif joliet_path is not None: joliet_path_bytes = self._normalize_joliet_path(joliet_path) rec = self._find_joliet_record(joliet_path_bytes) rec.change_existence(False) def force_consistency(self): # type: () -> None ''' Make sure the ISO object is fully consistent. PyCdlib typically delays doing work until it is necessary, and this detail is usually hidden from users. However, there are times that a user may want a fully consistent view of the ISO without calling one of the methods that forces consistency. This method allows the user to force a consistent view of this object. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') self._reshuffle_extents() def set_relocated_name(self, name, rr_name): # type: (str, str) -> None ''' Set the name of the relocated directory on a Rock Ridge ISO. The ISO must be a Rock Ridge one, and must not have previously had the relocated name set. Parameters: name - The name for a relocated directory. rr_name - The Rock Ridge name for a relocated directory. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Can only set the relocated name on a Rock Ridge ISO') encoded_name = name.encode('utf-8') encoded_rr_name = rr_name.encode('utf-8') if self._rr_moved_name is not None: if self._rr_moved_name == encoded_name and self._rr_moved_rr_name == encoded_rr_name: return raise pycdlibexception.PyCdlibInvalidInput('Changing the existing rr_moved name is not allowed') _check_iso9660_directory(encoded_name, self.interchange_level) self._rr_moved_name = encoded_name self._rr_moved_rr_name = encoded_rr_name def walk(self, **kwargs): # type: (str) -> Generator ''' Walk the entries on the ISO, starting at the given path. One, and only one, of iso_path, rr_path, joliet_path, and udf_path is allowed. Similar to os.walk(), yield a 3-tuple of (path-to-here, dirlist, filelist) for each directory level. Parameters: iso_path - The absolute ISO path to the starting entry on the ISO. rr_path - The absolute Rock Ridge path to the starting entry on the ISO. joliet_path - The absolute Joliet path to the starting entry on the ISO. udf_path - The absolute UDF path to the starting entry on the ISO. Yields: 3-tuples of (path-to-here, dirlist, filelist) Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_paths = 0 for key in kwargs: if key in ('joliet_path', 'rr_path', 'iso_path', 'udf_path'): if kwargs[key] is not None: num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") rec = None # type: Optional[Union[dr.DirectoryRecord, udfmod.UDFFileEntry]] if 'joliet_path' in kwargs: joliet_path = self._normalize_joliet_path(kwargs['joliet_path']) rec = self._find_joliet_record(joliet_path) path_type = 'joliet_path' encoding = 'utf-16_be' elif 'udf_path' in kwargs: if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') (ident_unused, rec) = self._find_udf_record(utils.normpath(kwargs['udf_path'])) if rec is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot get entry for empty UDF File Entry') path_type = 'udf_path' encoding = '' elif 'rr_path' in kwargs: if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a rr_path from a non-Rock Ridge ISO') rec = self._find_rr_record(utils.normpath(kwargs['rr_path'])) path_type = 'rr_path' encoding = 'utf-8' else: rec = self._find_iso_record(utils.normpath(kwargs['iso_path'])) path_type = 'iso_path' encoding = 'utf-8' dirs = collections.deque([rec]) while dirs: dir_record = dirs.popleft() relpath = self.full_path_from_dirrecord(dir_record, rockridge=path_type == 'rr_path') dirlist = [] filelist = [] dirdict = {} for child in reversed(list(self.list_children(**{path_type: relpath}))): if child is None or child.is_dot() or child.is_dotdot(): continue if isinstance(child, udfmod.UDFFileEntry) and child.file_ident is not None: encoding = child.file_ident.encoding if path_type == 'rr_path': name = child.rock_ridge.name() else: name = child.file_identifier() if sys.version_info >= (3, 0): # Python 3, just return the encoded version. encoded = name.decode(encoding) else: # Python 2. encoded = name.decode(encoding).encode('utf-8') if child.is_dir(): dirlist.append(encoded) dirdict[encoded] = child else: filelist.append(encoded) yield relpath, dirlist, filelist # We allow the user to modify dirlist along the way, so we # add the children to dirs *after* yield returns. for name in dirlist: dirs.appendleft(dirdict[name]) def open_file_from_iso(self, **kwargs): # type: (str) -> pycdlibio.PyCdlibIO ''' Open a file for reading in a context manager. This allows the user to operate on the file in user-defined chunks (utilizing the read() method of the returned context manager). Parameters: iso_path - The absolute ISO path to the file on the ISO. rr_path - The absolute Rock Ridge path to the file on the ISO. joliet_path - The absolute Joliet path to the file on the ISO. udf_path - The absolute UDF path to the file on the ISO. Returns: A PyCdlibIO object allowing access to the file. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_paths = 0 rec = None # type: Optional[Union[dr.DirectoryRecord, udfmod.UDFFileEntry]] for key in kwargs: if key in ('joliet_path', 'rr_path', 'iso_path', 'udf_path'): if kwargs[key] is not None: num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if 'joliet_path' in kwargs: joliet_path = self._normalize_joliet_path(kwargs['joliet_path']) rec = self._find_joliet_record(joliet_path) elif 'udf_path' in kwargs: if self.udf_root is None: raise pycdlibexception.PyCdlibInvalidInput('Can only specify a UDF path for a UDF ISO') (ident_unused, rec) = self._find_udf_record(utils.normpath(kwargs['udf_path'])) if rec is None: raise pycdlibexception.PyCdlibInvalidInput('Cannot get entry for empty UDF File Entry') elif 'rr_path' in kwargs: if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a rr_path from a non-Rock Ridge ISO') rec = self._find_rr_record(utils.normpath(kwargs['rr_path'])) else: rec = self._find_iso_record(utils.normpath(kwargs['iso_path'])) if not rec.is_file(): raise pycdlibexception.PyCdlibInvalidInput('Path to open must be a file') if rec.inode is None: raise pycdlibexception.PyCdlibInvalidInput('File has no data') return pycdlibio.PyCdlibIO(rec.inode, self.logical_block_size) def has_rock_ridge(self): # type: () -> bool ''' Returns whether this ISO has Rock Ridge extensions. Parameters: None. Returns: True if this ISO has Rock Ridge extensions, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') return self.rock_ridge != '' def has_joliet(self): # type: () -> bool ''' Returns whether this ISO has Joliet extensions. Parameters: None. Returns: True if this ISO has Joliet, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') return self.joliet_vd is not None def has_udf(self): # type: () -> bool ''' Returns whether this ISO has UDF extensions. Parameters: None. Returns: True if this ISO has UDF, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') return self._has_udf def get_iso9660_facade(self): # type: () -> facade.PyCdlibISO9660 ''' Return a 'facade' that simplifies some of the complexities of the PyCdlib class, while giving up some of the full power. This facade only allows manipulation of the ISO9660 portions of the ISO. Parameters: None. Returns: A PyCdlibISO9660 object that can be used to interact with the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') return facade.PyCdlibISO9660(self) def get_joliet_facade(self): # type: () -> facade.PyCdlibJoliet ''' Return a 'facade' that simplifies some of the complexities of the PyCdlib class, while giving up some of the full power. This facade only allows manipulation of the Joliet portions of the ISO. Parameters: None. Returns: A PyCdlibJoliet object that can be used to interact with the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if self.joliet_vd is None: raise pycdlibexception.PyCdlibInvalidInput('Can only get a Joliet facade for a Joliet ISO') return facade.PyCdlibJoliet(self) def get_rock_ridge_facade(self): # type: () -> facade.PyCdlibRockRidge ''' Return a 'facade' that simplifies some of the complexities of the PyCdlib class, while giving up some of the full power. This facade only allows manipulation of the Rock Ridge portions of the ISO. Parameters: None. Returns: A PyCdlibRockRidge object that can be used to interact with the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if self.rock_ridge == '': raise pycdlibexception.PyCdlibInvalidInput('Can only get a Rock Ridge facade for a Rock Ridge ISO') return facade.PyCdlibRockRidge(self) def get_udf_facade(self): # type: () -> facade.PyCdlibUDF ''' Return a 'facade' that simplifies some of the complexities of the PyCdlib class, while giving up some of the full power. This facade only allows manipulation of the UDF portions of the ISO. Parameters: None. Returns: A PyCdlibUDF object that can be used to interact with the ISO. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if not self._has_udf: raise pycdlibexception.PyCdlibInvalidInput('Can only get a UDF facade for a UDF ISO') return facade.PyCdlibUDF(self) def file_mode(self, **kwargs): # type: (str) -> Optional[int] ''' Get the POSIX file mode of the file if is a Rock Ridge file. Parameters: iso_path - The absolute ISO path to the file on the ISO. rr_path - The absolute Rock Ridge path to the file on the ISO. joliet_path - The absolute Joliet path to the file on the ISO. udf_path - The absolute UDF path to the file on the ISO. Returns: An integer representing the POSIX file mode of the file if it is Rock Ridge, or None otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') num_paths = 0 for key in kwargs: if key in ('joliet_path', 'rr_path', 'iso_path', 'udf_path'): if kwargs[key] is not None: num_paths += 1 else: raise pycdlibexception.PyCdlibInvalidInput("Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") if num_paths != 1: raise pycdlibexception.PyCdlibInvalidInput("Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") file_mode = None if 'rr_path' in kwargs: if not self.rock_ridge: raise pycdlibexception.PyCdlibInvalidInput('Cannot fetch a rr_path from a non-Rock Ridge ISO') rec = self._find_rr_record(utils.normpath(kwargs['rr_path'])) if rec.rock_ridge is not None: if rec.rock_ridge.dr_entries.px_record is not None or rec.rock_ridge.ce_entries.px_record is not None: file_mode = rec.rock_ridge.get_file_mode() # Neither Joliet nor ISO know the file_mode, and we don't support setting # the file mode for UDF, so just return None in those cases return file_mode def close(self): # type: () -> None ''' Close the PyCdlib object, and re-initialize the object to the defaults. The object can then be re-used for manipulation of another ISO. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInvalidInput('This object is not initialized; call either open() or new() to create an ISO') if self._managing_fp: self._cdfp.close() self._initialize() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1570403507.645111 pycdlib-1.11.0/pycdlib/pycdlibexception.py0000664000175000017500000000316100000000000023351 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' The custom exception class for PyCdlib. ''' class PyCdlibException(Exception): ''' The custom Exception class for PyCdlib. ''' def __init__(self, msg): # type: (str) -> None Exception.__init__(self, msg) class PyCdlibInternalError(PyCdlibException): ''' The Internal Error Exception class for PyCdlib. ''' def __init__(self, msg): # type: (str) -> None PyCdlibException.__init__(self, msg) class PyCdlibInvalidInput(PyCdlibException): ''' The Invalid User Input Exception class for PyCdlib. ''' def __init__(self, msg): # type: (str) -> None PyCdlibException.__init__(self, msg) class PyCdlibInvalidISO(PyCdlibException): ''' The Invalid ISO Exception class for PyCdlib. ''' def __init__(self, msg): # type: (str) -> None PyCdlibException.__init__(self, msg) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5719948 pycdlib-1.11.0/pycdlib/pycdlibio.py0000664000175000017500000002044200000000000021763 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2019-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' PyCdlibIO class. ''' from __future__ import absolute_import import array import io import sys from pycdlib import inode from pycdlib import pycdlibexception # For mypy annotations if False: # pylint: disable=using-constant-test from typing import Optional # NOQA pylint: disable=unused-import class PyCdlibIO(io.RawIOBase): ''' The class that implements the user-facing python io-style context manager. Since ISOs are generally only readable, this is only a readable context manager. ''' __slots__ = ('_ctxt', '_fp', '_length', '_offset', '_open', '_startpos') def __init__(self, ino, logical_block_size): # type: (inode.Inode, int) -> None super(PyCdlibIO, self).__init__() self._ctxt = inode.InodeOpenData(ino, logical_block_size) self._open = True def __enter__(self): # _fp is the real file descriptor. _length is the logical length # of the file. _offset is the logical offset of this context # into the file. _startpos is the absolute offset of the start of # this file into the backing file. (self._fp, self._length) = self._ctxt.__enter__() self._startpos = self._fp.tell() self._offset = 0 return self def read(self, size=None): # type: (Optional[int]) -> bytes ''' Read and return up to size bytes. Parameters: size - Optional parameter to read size number of bytes; if None or negative, all remaining bytes in the file will be read Returns: The number of bytes requested or the rest of the data left in the file, whichever is smaller. If the file is at or past EOF, returns an empty bytestring. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') if self._offset >= self._length: return b'' if size is None or size < 0: data = self.readall() else: readsize = min(self._length - self._offset, size) data = self._fp.read(readsize) self._offset += readsize return data def readall(self): # type: () -> bytes ''' Read and return the remaining bytes in the file. Parameters: None. Returns: The rest of the data left in the file. If the file is at or past EOF, returns an empty bytestring. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') readsize = self._length - self._offset if readsize > 0: data = self._fp.read(readsize) self._offset += readsize else: data = b'' return data def readinto(self, b): # type: (bytes) -> int if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') readsize = self._length - self._offset if readsize > 0: if sys.version_info >= (3, 0): # Python 3 mv = memoryview(b) # type: ignore m = mv.cast('B') # type: ignore readsize = min(readsize, len(m)) data = self._fp.read(readsize) n = len(data) m[:n] = data else: # Python 2 readsize = min(readsize, len(b)) data = self._fp.read(readsize) n = len(data) try: b[:n] = data except TypeError as err: if not isinstance(b, array.array): raise err b[:n] = array.array(b'b', data) else: n = 0 return n def seek(self, offset, whence=0): # type: (int, int) -> int ''' Change the stream position to byte offset offset. The offset is interpreted relative to the position indicated by whence. Valid values for whence are: * 0 -- start of stream (the default); offset should be zero or positive * 1 -- current stream position; offset may be negative * 2 -- end of stream; offset is usually negative Parameters: offset - The byte offset to seek to. whence - The position in the file to start from (0 for start, 1 for current, 2 for end) Returns: The new absolute position. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') if isinstance(offset, float): raise pycdlibexception.PyCdlibInvalidInput('an integer is required') if whence == 0: # From beginning of file if offset < 0: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (must be positive)') if offset < self._length: self._fp.seek(self._startpos + offset, 0) self._offset = offset elif whence == 1: # From current file position if self._offset + offset < 0: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (cannot seek before start of file)') if self._offset + offset < self._length: self._fp.seek(self._startpos + self._offset + offset, 0) self._offset += offset elif whence == 2: # From end of file if offset < 0 and abs(offset) > self._length: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (cannot seek before start of file)') if self._length + offset < self._length: self._fp.seek(self._length + offset, 0) self._offset = self._length + offset else: raise pycdlibexception.PyCdlibInvalidInput('Invalid value for whence (options are 0, 1, and 2)') return self._offset def tell(self): # type: () -> int ''' Return the current stream position. Parameters: None. Returns: The current stream position. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return self._offset def length(self): # type: () -> int ''' Return the length of the current file. Parameters: None. Returns: The length of the file. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return self._length def readable(self): # type: () -> bool ''' Determine whether this file is readable. Parameters: None. Returns: True in all cases. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return True def seekable(self): # type: () -> bool ''' Determine whether this file is seekable. Parameters: None. Returns: True in all cases. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return True def close(self): # type: () -> None ''' Close this file stream. Parameters: None. Returns: Nothing. ''' self._open = False self._ctxt.__exit__() def __exit__(self, *args): self._ctxt.__exit__() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1602034223.839599 pycdlib-1.11.0/pycdlib/rockridge.py0000664000175000017500000040063500000000000021764 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Classes and utilities to support Rock Ridge extensions. ''' from __future__ import absolute_import import bisect import struct from pycdlib import dates from pycdlib import pycdlibexception from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import Dict, List, Optional # NOQA pylint: disable=unused-import # NOTE: this has to be here to avoid circular deps from pycdlib import dr # NOQA pylint: disable=unused-import SU_ENTRY_VERSION = 1 ALLOWED_DR_SIZE = 254 TF_FLAGS = 0x0e EXT_ID_109 = b'RRIP_1991A' EXT_DES_109 = b'THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS' EXT_SRC_109 = b'PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR CONTACT INFORMATION.' EXT_ID_112 = b'IEEE_P1282' EXT_DES_112 = b'THE IEEE P1282 PROTOCOL PROVIDES SUPPORT FOR POSIX FILE SYSTEM SEMANTICS' EXT_SRC_112 = b'PLEASE CONTACT THE IEEE STANDARDS DEPARTMENT, PISCATAWAY, NJ, USA FOR THE P1282 SPECIFICATION' class RRSPRecord(object): ''' A class that represents a Rock Ridge Sharing Protocol record. This record indicates that the sharing protocol is in use, and how many bytes to skip prior to parsing a Rock Ridge entry out of a directory record. ''' __slots__ = ('_initialized', 'bytes_to_skip') FMT = '=BBBBB' def __init__(self): # type: () -> None self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Sharing Protocol record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SP record already initialized') (su_len, su_entry_version_unused, check_byte1, check_byte2, self.bytes_to_skip) = struct.unpack_from(self.FMT, rrstr[:7], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != RRSPRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') if check_byte1 != 0xbe or check_byte2 != 0xef: raise pycdlibexception.PyCdlibInvalidISO('Invalid check bytes on rock ridge extension') self._initialized = True def new(self, bytes_to_skip): # type: (int) -> None ''' Create a new Rock Ridge Sharing Protocol record. Parameters: bytes_to_skip - The number of bytes to skip. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SP record already initialized') self.bytes_to_skip = bytes_to_skip self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Sharing Protocol record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SP record not initialized') return b'SP' + struct.pack(self.FMT, RRSPRecord.length(), SU_ENTRY_VERSION, 0xbe, 0xef, self.bytes_to_skip) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Sharing Protocol record. Parameters: None. Returns: The length of this record in bytes. ''' return 7 class RRRRRecord(object): ''' A class that represents a Rock Ridge Rock Ridge record. This optional record indicates which other Rock Ridge fields are present. ''' __slots__ = ('_initialized', 'rr_flags') FMT = '=BBB' def __init__(self): # type: () -> None self.rr_flags = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Rock Ridge record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('RR record already initialized') (su_len, su_entry_version_unused, self.rr_flags) = struct.unpack_from(self.FMT, rrstr[:5], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != RRRRRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Rock Ridge record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('RR record already initialized') self.rr_flags = 0 self._initialized = True def append_field(self, fieldname): # type: (str) -> None ''' Mark a field as present in the Rock Ridge records. Parameters: fieldname - The name of the field to mark as present; should be one of 'PX', 'PN', 'SL', 'NM', 'CL', 'PL', 'RE', or 'TF'. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('RR record not initialized') field_to_bit = { 'PX': 0, 'PN': 1, 'SL': 2, 'NM': 3, 'CL': 4, 'PL': 5, 'RE': 6, 'TF': 7 } try: self.rr_flags |= (1 << field_to_bit[fieldname]) except KeyError: raise pycdlibexception.PyCdlibInternalError('Unknown RR field name %s' % (fieldname)) def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Rock Ridge record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('RR record not initialized') return b'RR' + struct.pack(self.FMT, RRRRRecord.length(), SU_ENTRY_VERSION, self.rr_flags) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Rock Ridge record. Parameters: None. Returns: The length of this record in bytes. ''' return 5 class RRCERecord(object): ''' A class that represents a Rock Ridge Continuation Entry record. This record represents additional information that did not fit in the standard directory record. ''' __slots__ = ('_initialized', 'bl_cont_area', 'offset_cont_area', 'len_cont_area') FMT = ' None self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Continuation Entry record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record already initialized') (su_len, su_entry_version_unused, bl_cont_area_le, bl_cont_area_be, offset_cont_area_le, offset_cont_area_be, len_cont_area_le, len_cont_area_be) = struct.unpack_from(self.FMT, rrstr[:28], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != RRCERecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') if bl_cont_area_le != utils.swab_32bit(bl_cont_area_be): raise pycdlibexception.PyCdlibInvalidISO('CE record big and little endian continuation area do not agree') if offset_cont_area_le != utils.swab_32bit(offset_cont_area_be): raise pycdlibexception.PyCdlibInvalidISO('CE record big and little endian continuation area offset do not agree') if len_cont_area_le != utils.swab_32bit(len_cont_area_be): raise pycdlibexception.PyCdlibInvalidISO('CE record big and little endian continuation area length do not agree') self.bl_cont_area = bl_cont_area_le self.offset_cont_area = offset_cont_area_le self.len_cont_area = len_cont_area_le self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Continuation Entry record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record already initialized') self.bl_cont_area = 0 # This will get set during reshuffle_extents self.offset_cont_area = 0 # This will get set during reshuffle_extents self.len_cont_area = 0 # This will be calculated based on fields put in self._initialized = True def update_extent(self, extent): # type: (int) -> None ''' Update the extent for this CE record. Parameters: extent - The new extent for this CE record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record not initialized') self.bl_cont_area = extent def update_offset(self, offset): # type: (int) -> None ''' Update the offset for this CE record. Parameters: extent - The new offset for this CE record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record not initialized') self.offset_cont_area = offset def add_record(self, length): # type: (int) -> None ''' Add some more length to this CE record. Used when a new record is going to get recorded into the CE (rather than the DR). Parameters: length - The length to add to this CE record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record not initialized') self.len_cont_area += length def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Continuation Entry record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CE record not initialized') return b'CE' + struct.pack(self.FMT, RRCERecord.length(), SU_ENTRY_VERSION, self.bl_cont_area, utils.swab_32bit(self.bl_cont_area), self.offset_cont_area, utils.swab_32bit(self.offset_cont_area), self.len_cont_area, utils.swab_32bit(self.len_cont_area)) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Continuation Entry record. Parameters: None. Returns: The length of this record in bytes. ''' return 28 class RRPXRecord(object): ''' A class that represents a Rock Ridge POSIX File Attributes record. This record contains information about the POSIX file mode, file links, user ID, group ID, and serial number of a directory record. ''' __slots__ = ('_initialized', 'posix_file_mode', 'posix_file_links', 'posix_user_id', 'posix_group_id', 'posix_serial_number') FMT = ' None self.posix_file_mode = 0 self.posix_file_links = 1 self.posix_user_id = 0 self.posix_group_id = 0 self.posix_serial_number = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> int ''' Parse a Rock Ridge POSIX File Attributes record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: The length of the record in bytes. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PX record already initialized') (su_len, su_entry_version_unused, posix_file_mode_le, posix_file_mode_be, posix_file_links_le, posix_file_links_be, posix_file_user_id_le, posix_file_user_id_be, posix_file_group_id_le, posix_file_group_id_be) = struct.unpack_from(self.FMT, rrstr[:38], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if posix_file_mode_le != utils.swab_32bit(posix_file_mode_be): raise pycdlibexception.PyCdlibInvalidISO('PX record big and little-endian file mode do not agree') if posix_file_links_le != utils.swab_32bit(posix_file_links_be): raise pycdlibexception.PyCdlibInvalidISO('PX record big and little-endian file links do not agree') if posix_file_user_id_le != utils.swab_32bit(posix_file_user_id_be): raise pycdlibexception.PyCdlibInvalidISO('PX record big and little-endian file user ID do not agree') if posix_file_group_id_le != utils.swab_32bit(posix_file_group_id_be): raise pycdlibexception.PyCdlibInvalidISO('PX record big and little-endian file group ID do not agree') # In Rock Ridge 1.09 and 1.10, there is no serial number so the su_len # is 36, while in Rock Ridge 1.12, there is an 8-byte serial number so # su_len is 44. if su_len == 36: posix_file_serial_number_le = 0 elif su_len == 44: (posix_file_serial_number_le, posix_file_serial_number_be) = struct.unpack_from(' None ''' Create a new Rock Ridge POSIX File Attributes record. Parameters: mode - The Unix file mode for this record. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PX record already initialized') self.posix_file_mode = mode self.posix_file_links = 1 self.posix_user_id = 0 self.posix_group_id = 0 self.posix_serial_number = 0 self._initialized = True def record(self, rr_version): # type: (str) -> bytes ''' Generate a string representing the Rock Ridge POSIX File Attributes record. Parameters: rr_version - The Rock Ridge version to use. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('PX record not initialized') outlist = [b'PX', struct.pack(self.FMT, RRPXRecord.length(rr_version), SU_ENTRY_VERSION, self.posix_file_mode, utils.swab_32bit(self.posix_file_mode), self.posix_file_links, utils.swab_32bit(self.posix_file_links), self.posix_user_id, utils.swab_32bit(self.posix_user_id), self.posix_group_id, utils.swab_32bit(self.posix_group_id))] if rr_version == '1.12': outlist.append(struct.pack(' int ''' Static method to return the length of the Rock Ridge POSIX File Attributes record. Parameters: rr_version - The version of Rock Ridge in use; must be '1.09', '1.10', or '1.12'. Returns: The length of this record in bytes. ''' if rr_version in ('1.09', '1.10'): return 36 if rr_version == '1.12': return 44 # This should never happen raise pycdlibexception.PyCdlibInternalError('Invalid rr_version') class RRERRecord(object): ''' A class that represents a Rock Ridge Extensions Reference record. ''' __slots__ = ('_initialized', 'ext_id', 'ext_des', 'ext_src', 'ext_ver') FMT = '=BBBBBB' def __init__(self): # type: () -> None self.ext_id = b'' self.ext_des = b'' self.ext_src = b'' self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Extensions Reference record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ER record already initialized') (su_len, su_entry_version_unused, len_id, len_des, len_src, self.ext_ver) = struct.unpack_from(self.FMT, rrstr[:8], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. # Ensure that the length isn't crazy if su_len > len(rrstr): raise pycdlibexception.PyCdlibInvalidISO('Length of ER record much too long') # Also ensure that the combination of len_id, len_des, and len_src # doesn't overrun su_len; because of the check above, this means it # can't overrun len(rrstr) either total_length = len_id + len_des + len_src if total_length > su_len: raise pycdlibexception.PyCdlibInvalidISO('Combined length of ER ID, des, and src longer than record') fmtstr = '=%ds%ds%ds' % (len_id, len_des, len_src) (self.ext_id, self.ext_des, self.ext_src) = struct.unpack_from(fmtstr, rrstr, 8) self._initialized = True def new(self, ext_id, ext_des, ext_src): # type: (bytes, bytes, bytes) -> None ''' Create a new Rock Ridge Extensions Reference record. Parameters: ext_id - The extension identifier to use. ext_des - The extension descriptor to use. ext_src - The extension specification source to use. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ER record already initialized') self.ext_id = ext_id self.ext_des = ext_des self.ext_src = ext_src self.ext_ver = 1 self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Extensions Reference record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('ER record not initialized') return b'ER' + struct.pack(self.FMT, RRERRecord.length(self.ext_id, self.ext_des, self.ext_src), SU_ENTRY_VERSION, len(self.ext_id), len(self.ext_des), len(self.ext_src), self.ext_ver) + self.ext_id + self.ext_des + self.ext_src @staticmethod def length(ext_id, ext_des, ext_src): # type: (bytes, bytes, bytes) -> int ''' Static method to return the length of the Rock Ridge Extensions Reference record. Parameters: ext_id - The extension identifier to use. ext_des - The extension descriptor to use. ext_src - The extension specification source to use. Returns: The length of this record in bytes. ''' return 8 + len(ext_id) + len(ext_des) + len(ext_src) class RRESRecord(object): ''' A class that represents a Rock Ridge Extension Selector record. ''' __slots__ = ('_initialized', 'extension_sequence') FMT = '=BBB' def __init__(self): # type: () -> None self.extension_sequence = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Extension Selector record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ES record already initialized') # We assume that the caller has already checked the su_entry_version, # so we don't bother. (su_len, su_entry_version_unused, self.extension_sequence) = struct.unpack_from(self.FMT, rrstr[:5], 2) if su_len != RRESRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') self._initialized = True def new(self, extension_sequence): # type: (int) -> None ''' Create a new Rock Ridge Extension Selector record. Parameters: extension_sequence - The sequence number of this extension. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ES record already initialized') self.extension_sequence = extension_sequence self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Extension Selector record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('ES record not initialized') return b'ES' + struct.pack(self.FMT, RRESRecord.length(), SU_ENTRY_VERSION, self.extension_sequence) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Extensions Selector record. Parameters: None. Returns: The length of this record in bytes. ''' return 5 class RRPNRecord(object): ''' A class that represents a Rock Ridge POSIX Device Number record. This record represents a device major and minor special file. ''' __slots__ = ('_initialized', 'dev_t_high', 'dev_t_low') FMT = ' None self.dev_t_high = 0 self.dev_t_low = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge POSIX Device Number record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PN record already initialized') (su_len, su_entry_version_unused, dev_t_high_le, dev_t_high_be, dev_t_low_le, dev_t_low_be) = struct.unpack_from(self.FMT, rrstr[:20], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != RRPNRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') if dev_t_high_le != utils.swab_32bit(dev_t_high_be): raise pycdlibexception.PyCdlibInvalidISO('Dev_t high little-endian does not match big-endian') if dev_t_low_le != utils.swab_32bit(dev_t_low_be): raise pycdlibexception.PyCdlibInvalidISO('Dev_t low little-endian does not match big-endian') self.dev_t_high = dev_t_high_le self.dev_t_low = dev_t_low_le self._initialized = True def new(self, dev_t_high, dev_t_low): # type: (int, int) -> None ''' Create a new Rock Ridge POSIX device number record. Parameters: dev_t_high - The high-order 32-bits of the device number. dev_t_low - The low-order 32-bits of the device number. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PN record already initialized') self.dev_t_high = dev_t_high self.dev_t_low = dev_t_low self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge POSIX Device Number record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('PN record not initialized') return b'PN' + struct.pack(self.FMT, RRPNRecord.length(), SU_ENTRY_VERSION, self.dev_t_high, utils.swab_32bit(self.dev_t_high), self.dev_t_low, utils.swab_32bit(self.dev_t_low)) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge POSIX Device Number record. Parameters: None. Returns: The length of this record in bytes. ''' return 20 class RRSLRecord(object): ''' A class that represents a Rock Ridge Symbolic Link record. This record represents some or all of a symbolic link. For a symbolic link, Rock Ridge specifies that each component (part of path separated by /) be in a separate component entry, and individual components may be split across multiple Symbolic Link records. This class takes care of all of those details. ''' __slots__ = ('_initialized', 'symlink_components', 'flags') class Component(object): ''' A class that represents one component of a Symbolic Link Record. ''' __slots__ = ('flags', 'curr_length', 'data') def __init__(self, flags, length, data): # type: (int, int, bytes) -> None if flags not in (0, 1, 2, 4, 8): raise pycdlibexception.PyCdlibInternalError('Invalid Rock Ridge symlink flags 0x%x' % (flags)) if (flags & (1 << 1) or flags & (1 << 2) or flags & (1 << 3)) and length != 0: raise pycdlibexception.PyCdlibInternalError('Rock Ridge symlinks to dot, dotdot, or root should have zero length') # A Component can't both be a continuation and one of dot, dotdot, # or root, but this case is caught by the initial flags check so we # don't check for it again here. self.flags = flags self.curr_length = length self.data = data def name(self): # type: () -> bytes ''' Retrieve the human-readable name of this component. Parameters: None. Returns: Human readable name of this component. ''' if self.flags & (1 << 1): return b'.' if self.flags & (1 << 2): return b'..' if self.flags & (1 << 3): return b'/' return self.data def is_continued(self): # type: () -> bool ''' Determine whether this component is continued in the next component. Parameters: None. Returns: True if this component is continued in the next component, False otherwise. ''' return self.flags & (1 << 0) != 0 def record(self): # type: () -> bytes ''' Return the representation of this component that is suitable for writing to disk. Parameters: None. Returns: Representation of this compnent suitable for writing to disk. ''' if self.flags & (1 << 1): return struct.pack('=BB', (1 << 1), 0) if self.flags & (1 << 2): return struct.pack('=BB', (1 << 2), 0) if self.flags & (1 << 3): return struct.pack('=BB', (1 << 3), 0) return struct.pack('=BB', self.flags, self.curr_length) + self.data def set_continued(self): # type: () -> None ''' Set the continued flag on this component. Parameters: None. Returns: Nothing. ''' self.flags |= (1 << 0) def __eq__(self, other): # type: (object) -> bool if not isinstance(other, RRSLRecord.Component): return NotImplemented return self.flags == other.flags and self.curr_length == other.curr_length and self.data == other.data def __ne__(self, other): # type: (object) -> bool return not self.__eq__(other) @staticmethod def length(symlink_component): # type: (bytes) -> int ''' Static method to compute the length of one symlink component. Parameters: symlink_component - String representing one symlink component. Returns: Length of symlink component plus overhead. ''' length = 2 if symlink_component not in (b'.', b'..', b'/'): length += len(symlink_component) return length @staticmethod def factory(name): # type: (bytes) -> RRSLRecord.Component ''' A static method to create a new, valid Component given a human readable name. Parameters: name - The name to create the Component from. Returns: A new Component object representing this name. ''' if name == b'.': flags = (1 << 1) length = 0 elif name == b'..': flags = (1 << 2) length = 0 elif name == b'/': flags = (1 << 3) length = 0 else: flags = 0 length = len(name) # Theoretically, this factory method could be passed a name # that wouldn't fit into either this SL record or into a single # component. However, the only caller of this factory method # (add_component(), below) already checks to make sure this # name would fit into the SL record, and the job of making sure # everything fits into an SL record really belongs there. # Further, we recognize that an SL record and a component # record both use an 8-bit quantity for the length, so there is # never a time when something would fit into the SL record but # would not fit into a component. Thus, we elide any length # checks here. return RRSLRecord.Component(flags, length, name) def __init__(self): # type: () -> None self.symlink_components = [] # type: List[RRSLRecord.Component] self.flags = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Symbolic Link record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record already initialized') (su_len, su_entry_version_unused, self.flags) = struct.unpack_from('=BBB', rrstr[:5], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. cr_offset = 5 data_len = su_len - 5 while data_len > 0: (cr_flags, len_cp) = struct.unpack_from('=BB', rrstr[:cr_offset + 2], cr_offset) data_len -= 2 cr_offset += 2 self.symlink_components.append(self.Component(cr_flags, len_cp, rrstr[cr_offset:cr_offset + len_cp])) # FIXME: if this is the last component in this SL record, # but the component continues on in the next SL record, we will # fail to record this bit. We should fix that. cr_offset += len_cp data_len -= len_cp self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Symbolic Link record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record already initialized') self._initialized = True def add_component(self, symlink_comp): # type: (bytes) -> None ''' Add a new component to this symlink record. Parameters: symlink_comp - The string to add to this symlink record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') if (self.current_length() + RRSLRecord.Component.length(symlink_comp)) > 255: raise pycdlibexception.PyCdlibInvalidInput('Symlink would be longer than 255') self.symlink_components.append(self.Component.factory(symlink_comp)) def current_length(self): # type: () -> int ''' Calculate the current length of this symlink record. Parameters: None. Returns: Length of this symlink record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') strlist = [] for comp in self.symlink_components: strlist.append(comp.name()) return RRSLRecord.length(strlist) def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Symbolic Link record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') outlist = [b'SL', struct.pack('=BBB', self.current_length(), SU_ENTRY_VERSION, self.flags)] for comp in self.symlink_components: outlist.append(comp.record()) return b''.join(outlist) def name(self): # type: () -> bytes ''' Generate a string that contains all components of the symlink. Parameters: None Returns: String containing all components of the symlink. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') outlist = [] # type: List[bytes] continued = False for comp in self.symlink_components: name = comp.name() if name == b'/': outlist = [] continued = False name = b'' if not continued: outlist.append(name) else: outlist[-1] += name continued = comp.is_continued() return b'/'.join(outlist) def set_continued(self): # type: () -> None ''' Set this SL record as continued in the next System Use Entry. Parameters: None Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') self.flags |= (1 << 0) def set_last_component_continued(self): # type: () -> None ''' Set the previous component of this SL record to continued. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') if not self.symlink_components: raise pycdlibexception.PyCdlibInternalError('Trying to set continued on a non-existent component!') self.symlink_components[-1].set_continued() def last_component_continued(self): # type: () -> bool ''' Determines whether the previous component of this SL record is a continued one or not. Parameters: None. Returns: True if the previous component of this SL record is continued, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SL record not initialized') if not self.symlink_components: raise pycdlibexception.PyCdlibInternalError('Trying to get continued on a non-existent component!') return self.symlink_components[-1].is_continued() @staticmethod def header_length(): # type: () -> int ''' Static method to return the length of a Rock Ridge Symbolic Link header. Parameters: None Returns: The length of the RRSLRecord header. ''' return 5 @staticmethod def maximum_component_area_length(): # type: () -> int ''' Static method to return the absolute maximum length a Rock Ridge Symbolic Link component area can be. Parameters: None Returns: The maximum length a Symbolic Link component area can be. ''' return 255 - RRSLRecord.header_length() @staticmethod def length(symlink_components): # type: (List[bytes]) -> int ''' Static method to return the length of the Rock Ridge Symbolic Link record. Parameters: symlink_components - A list containing a string for each of the symbolic link components. Returns: The length of this record in bytes. ''' length = RRSLRecord.header_length() for comp in symlink_components: length += RRSLRecord.Component.length(comp) return length class RRALRecord(object): ''' A class that represents an Arbitrary Attribute Interchange Protocol record. This is an unoffical extension by libisofs: https://dev.lovelyhq.com/libburnia/libisofs/src/commit/d297ce3aed5935e469bb108a36b7d6e31763a075/doc/susp_aaip_2_0.txt The goal of this record is to allow arbitrary attributes with arbitrary name/value pairs in the SUSP record. It is split up much like an SL record, so a lot of the code is copied from that class. ''' __slots__ = ('_initialized', 'flags', 'components') class Component(object): ''' A class that represents one component of an Arbitrary Attribute. ''' __slots__ = ('flags', 'curr_length', 'data') def __init__(self, flags, length, data): # type: (int, int, bytes) -> None if flags not in (0, 1): raise pycdlibexception.PyCdlibInternalError('Invalid Arbitrary Attribute flags 0x%x' % (flags)) self.flags = flags self.curr_length = length self.data = data def record(self): # type: () -> bytes ''' Return the representation of this component that is suitable for writing to disk. Parameters: None. Returns: Representation of this compnent suitable for writing to disk. ''' return struct.pack('=BB', self.flags, self.curr_length) + self.data def set_continued(self): # type: () -> None ''' Set the continued flag on this component. Parameters: None. Returns: Nothing. ''' self.flags |= (1 << 0) @staticmethod def length(attr): # type: (bytes) -> int ''' Method to compute the length of a component. Parameters: None. Returns: Length of this component plus overhead. ''' return 2 + len(attr) @staticmethod def factory(component): # type: (bytes) -> RRALRecord.Component ''' A static method to create a new, valid Component given an attribute. Parameters: component - The string to create the Component from. Returns: A new Component object representing this string. ''' # Theoretically, this factory method could be passed a name # that wouldn't fit into either this AL record or into a single # component. However, the only caller of this factory method # (add_component(), below) already checks to make sure this # name would fit into the AL record, and the job of making sure # everything fits into an AL record really belongs there. # Further, we recognize that an AL record and a component # record both use an 8-bit quantity for the length, so there is # never a time when something would fit into the AL record but # would not fit into a component. Thus, we elide any length # checks here. return RRALRecord.Component(0, len(component), component) def __init__(self): # type: () -> None self.flags = 0 self.components = [] # type: List[RRALRecord.Component] self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse an Arbitrary Attribute record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record already initialized') (su_len, su_entry_version_unused, self.flags) = struct.unpack_from('=BBB', rrstr[:5], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. cr_offset = 5 data_len = su_len - 5 while data_len > 0: (cr_flags, len_cp) = struct.unpack_from('=BB', rrstr[:cr_offset + 2], cr_offset) data_len -= 2 cr_offset += 2 self.components.append(self.Component(cr_flags, len_cp, rrstr[cr_offset:cr_offset + len_cp])) # FIXME: if this is the last component in this AL record, # but the component continues on in the next AL record, we will # fail to record this bit. We should fix that. cr_offset += len_cp data_len -= len_cp self._initialized = True def current_length(self): # type: () -> int ''' Calculate the current length of this Arbitrary Attribute record. Parameters: None. Returns: Length of this Arbitrary Attribute record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record not initialized') strlist = [] for comp in self.components: strlist.append(comp.data) return RRALRecord.length(strlist) def record(self): # type: () -> bytes ''' Generate a string representing the Arbitrary Attribute record. Parameters: None. Returns: String containing the Arbitrary Attribute record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record not initialized') outlist = [b'AL', struct.pack('=BBB', self.current_length(), SU_ENTRY_VERSION, self.flags)] for comp in self.components: outlist.append(comp.record()) return b''.join(outlist) def new(self): # type: () -> None ''' Create a new Arbitrary Attribute record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record already initialized') self._initialized = True def set_continued(self): # type: () -> None ''' Set this AL record as continued in the next System Use Entry. Parameters: None Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record not initialized') self.flags |= (1 << 0) def set_last_component_continued(self): # type: () -> None ''' Set the previous component of this AL record to continued. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record not initialized') if not self.components: raise pycdlibexception.PyCdlibInternalError('Trying to set continued on a non-existent component!') self.components[-1].set_continued() def add_component(self, comp): # type: (bytes) -> None ''' Add a new component to this Arbitrary Attribute record. Parameters: comp - The string to add to this Arbitrary Attribute record. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('AL record not initialized') if (self.current_length() + RRALRecord.Component.length(comp)) > 255: raise pycdlibexception.PyCdlibInvalidInput('Attribute would be longer than 255') self.components.append(self.Component.factory(comp)) @staticmethod def header_length(): # type: () -> int ''' Static method to return the length of an Arbitrary Attribute header. Parameters: None Returns: The length of the RRALRecord header. ''' return 5 @staticmethod def maximum_component_area_length(): # type: () -> int ''' Static method to return the absolute maximum length an Arbitrary Attribute component area can be. Parameters: None Returns: The maximum length an Arbitrary Attribute component area can be. ''' return 255 - RRALRecord.header_length() @staticmethod def length(attrs): # type: (List[bytes]) -> int ''' Static method to return the length of a list of attributes. Parameters: attrs - A list of attributes. Returns: The length of the entire record in bytes. ''' length = RRALRecord.header_length() for attr in attrs: length += RRALRecord.Component.length(attr) return length class RRNMRecord(object): ''' A class that represents a Rock Ridge Alternate Name record. ''' __slots__ = ('_initialized', 'posix_name_flags', 'posix_name') FMT = '=BBB' def __init__(self): # type: () -> None self._initialized = False self.posix_name_flags = 0 self.posix_name = b'' def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Alternate Name record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('NM record already initialized') (su_len, su_entry_version_unused, self.posix_name_flags) = struct.unpack_from(self.FMT, rrstr[:5], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. name_len = su_len - 5 if (self.posix_name_flags & 0x7) not in (0, 1, 2, 4): raise pycdlibexception.PyCdlibInvalidISO('Invalid Rock Ridge NM flags') if name_len != 0: if (self.posix_name_flags & (1 << 1)) or (self.posix_name_flags & (1 << 2)) or (self.posix_name_flags & (1 << 5)): raise pycdlibexception.PyCdlibInvalidISO('Invalid name in Rock Ridge NM entry (0x%x %d)' % (self.posix_name_flags, name_len)) self.posix_name += rrstr[5:5 + name_len] self._initialized = True def new(self, rr_name): # type: (bytes) -> None ''' Create a new Rock Ridge Alternate Name record. Parameters: rr_name - The name for the new record. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('NM record already initialized') self.posix_name = rr_name self.posix_name_flags = 0 self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Alternate Name record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('NM record not initialized') return b'NM' + struct.pack(self.FMT, RRNMRecord.length(self.posix_name), SU_ENTRY_VERSION, self.posix_name_flags) + self.posix_name def set_continued(self): # type: () -> None ''' Mark this alternate name record as continued. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('NM record not initialized') self.posix_name_flags |= (1 << 0) @staticmethod def length(rr_name): # type: (bytes) -> int ''' Static method to return the length of the Rock Ridge Alternate Name record. Parameters: rr_name - The name to use. Returns: The length of this record in bytes. ''' return 5 + len(rr_name) class RRCLRecord(object): ''' A class that represents a Rock Ridge Child Link record. This record represents the logical block where a deeply nested directory was relocated to. ''' __slots__ = ('_initialized', 'child_log_block_num') FMT = ' None self.child_log_block_num = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Child Link record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('CL record already initialized') # We assume that the caller has already checked the su_entry_version, # so we don't bother. (su_len, su_entry_version_unused, child_log_block_num_le, child_log_block_num_be) = struct.unpack_from(self.FMT, rrstr[:12], 2) if su_len != RRCLRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') if child_log_block_num_le != utils.swab_32bit(child_log_block_num_be): raise pycdlibexception.PyCdlibInvalidISO('Little endian block num does not equal big endian; corrupt ISO') self.child_log_block_num = child_log_block_num_le self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Child Link record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('CL record already initialized') self.child_log_block_num = 0 # This gets set later self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Child Link record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CL record not initialized') return b'CL' + struct.pack(self.FMT, RRCLRecord.length(), SU_ENTRY_VERSION, self.child_log_block_num, utils.swab_32bit(self.child_log_block_num)) def set_log_block_num(self, bl): # type: (int) -> None ''' Set the logical block number for the child. Parameters: bl - Logical block number of the child. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('CL record not initialized') self.child_log_block_num = bl @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Child Link record. Parameters: None. Returns: The length of this record in bytes. ''' return 12 class RRPLRecord(object): ''' A class that represents a Rock Ridge Parent Link record. This record represents the logical block where a deeply nested directory was located from. ''' __slots__ = ('_initialized', 'parent_log_block_num') FMT = ' None self.parent_log_block_num = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Parent Link record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PL record already initialized') # We assume that the caller has already checked the su_entry_version, # so we don't bother. (su_len, su_entry_version_unused, parent_log_block_num_le, parent_log_block_num_be) = struct.unpack_from(self.FMT, rrstr[:12], 2) if su_len != RRPLRecord.length(): raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') if parent_log_block_num_le != utils.swab_32bit(parent_log_block_num_be): raise pycdlibexception.PyCdlibInvalidISO('Little endian block num does not equal big endian; corrupt ISO') self.parent_log_block_num = parent_log_block_num_le self._initialized = True def new(self): # type: () -> None ''' Generate a string representing the Rock Ridge Parent Link record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PL record already initialized') self.parent_log_block_num = 0 # This will get set later self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Child Link record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('PL record not initialized') return b'PL' + struct.pack(self.FMT, RRPLRecord.length(), SU_ENTRY_VERSION, self.parent_log_block_num, utils.swab_32bit(self.parent_log_block_num)) def set_log_block_num(self, bl): # type: (int) -> None ''' Set the logical block number for the parent. Parameters: bl - Logical block number of the parent. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('PL record not initialized') self.parent_log_block_num = bl @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Parent Link record. Parameters: None. Returns: The length of this record in bytes. ''' return 12 class RRTFRecord(object): ''' A class that represents a Rock Ridge Time Stamp record. This record represents the creation timestamp, the access time timestamp, the modification time timestamp, the attribute change time timestamp, the backup time timestamp, the expiration time timestamp, and the effective time timestamp. Each of the timestamps can be selectively enabled or disabled. Additionally, the timestamps can be configured to be Directory Record style timestamps (7 bytes) or Volume Descriptor style timestamps (17 bytes). ''' __slots__ = ('_initialized', 'creation_time', 'access_time', 'modification_time', 'attribute_change_time', 'backup_time', 'expiration_time', 'effective_time', 'time_flags') FIELDNAMES = ('creation_time', 'access_time', 'modification_time', 'attribute_change_time', 'backup_time', 'expiration_time', 'effective_time') def __init__(self): # type: () -> None for fieldname in self.FIELDNAMES: setattr(self, fieldname, None) self.time_flags = 0 self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Time Stamp record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('TF record already initialized') # We assume that the caller has already checked the su_entry_version, # so we don't bother. (su_len, su_entry_version_unused, self.time_flags) = struct.unpack_from('=BBB', rrstr[:5], 2) if su_len < 5: raise pycdlibexception.PyCdlibInvalidISO('Not enough bytes in the TF record') tflen = 7 if self.time_flags & (1 << 7): tflen = 17 offset = 5 for index, fieldname in enumerate(self.FIELDNAMES): if self.time_flags & (1 << index): if tflen == 7: setattr(self, fieldname, dates.DirectoryRecordDate()) elif tflen == 17: setattr(self, fieldname, dates.VolumeDescriptorDate()) getattr(self, fieldname).parse(rrstr[offset:offset + tflen]) offset += tflen self._initialized = True def new(self, time_flags): # type: (int) -> None ''' Create a new Rock Ridge Time Stamp record. Parameters: time_flags - The flags to use for this time stamp record. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('TF record already initialized') self.time_flags = time_flags tflen = 7 if self.time_flags & (1 << 7): tflen = 17 for index, fieldname in enumerate(self.FIELDNAMES): if self.time_flags & (1 << index): if tflen == 7: setattr(self, fieldname, dates.DirectoryRecordDate()) elif tflen == 17: setattr(self, fieldname, dates.VolumeDescriptorDate()) getattr(self, fieldname).new() self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Time Stamp record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('TF record not initialized') outlist = [b'TF', struct.pack('=BBB', RRTFRecord.length(self.time_flags), SU_ENTRY_VERSION, self.time_flags)] for fieldname in self.FIELDNAMES: field = getattr(self, fieldname) if field is not None: outlist.append(field.record()) return b''.join(outlist) @staticmethod def length(time_flags): # type: (int) -> int ''' Static method to return the length of the Rock Ridge Time Stamp record. Parameters: time_flags - Integer representing the flags to use. Returns: The length of this record in bytes. ''' tf_each_size = 7 if time_flags & (1 << 7): tf_each_size = 17 time_flags &= 0x7f tf_num = 0 while time_flags: time_flags &= time_flags - 1 tf_num += 1 return 5 + tf_each_size * tf_num class RRSFRecord(object): ''' A class that represents a Rock Ridge Sparse File record. This record represents the full file size of a sparsely-populated file. ''' __slots__ = ('_initialized', 'virtual_file_size_high', 'virtual_file_size_low', 'table_depth') def __init__(self): # type: () -> None self.table_depth = None # type: Optional[int] self.virtual_file_size_low = 0 self.virtual_file_size_high = None # type: Optional[int] self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Sparse File record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SF record already initialized') # We assume that the caller has already checked the su_entry_version, # so we don't bother. (su_len, su_entry_version_unused) = struct.unpack_from('=BB', rrstr[:4], 2) if su_len == 12: # This is a Rock Ridge version 1.10 SF Record, which is 12 bytes. (virtual_file_size_le, virtual_file_size_be) = struct.unpack_from(' None ''' Create a new Rock Ridge Sparse File record. Parameters: file_size_high - The high-order 32-bits of the file size. file_size_low - The low-order 32-bits of the file size. table_depth - The maximum virtual file size. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('SF record already initialized') self.virtual_file_size_high = file_size_high self.virtual_file_size_low = file_size_low self.table_depth = table_depth self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Sparse File record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('SF record not initialized') length = 12 if self.virtual_file_size_high is not None: length = 21 ret = b'SF' + struct.pack('=BB', length, SU_ENTRY_VERSION) if self.virtual_file_size_high is not None and self.table_depth is not None: ret += struct.pack(' int ''' Static method to return the length of the Rock Ridge Sparse File record. Parameters: rr_version - The version of Rock Ridge in use; must be '1.10' or '1.12'. Returns: The length of this record in bytes. ''' if rr_version == '1.10': return 12 if rr_version == '1.12': return 21 # This should never happen raise pycdlibexception.PyCdlibInternalError('Invalid rr_version') class RRRERecord(object): ''' A class that represents a Rock Ridge Relocated Directory record. This record is used to mark an entry as having been relocated because it was deeply nested. ''' __slots__ = ('_initialized',) FMT = '=BB' def __init__(self): # type: () -> None self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Relocated Directory record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('RE record already initialized') (su_len, su_entry_version_unused) = struct.unpack_from(self.FMT, rrstr[:4], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != 4: raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Relocated Directory record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('RE record already initialized') self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Relocated Directory record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('RE record not initialized') return b'RE' + struct.pack(self.FMT, RRRERecord.length(), SU_ENTRY_VERSION) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge Relocated Directory record. Parameters: None. Returns: The length of this record in bytes. ''' return 4 class RRSTRecord(object): ''' A class that represents a Rock Ridge System Terminator record. This record is used to terminate the SUSP/Rock Ridge records in a Directory Entry. ''' __slots__ = ('_initialized',) FMT = '=BB' def __init__(self): # type: () -> None self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge System Terminator record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ST record already initialized') (su_len, su_entry_version_unused) = struct.unpack_from(self.FMT, rrstr[:4], 2) # We assume that the caller has already checked the su_entry_version, # so we don't bother. if su_len != 4: raise pycdlibexception.PyCdlibInvalidISO('Invalid length on rock ridge extension') self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge System Terminator record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('ST record already initialized') self._initialized = True def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge System Terminator record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('ST record not initialized') return b'ST' + struct.pack(self.FMT, RRSTRecord.length(), SU_ENTRY_VERSION) @staticmethod def length(): # type: () -> int ''' Static method to return the length of the Rock Ridge System Terminator record. Parameters: None. Returns: The length of this record in bytes. ''' return 4 class RRPDRecord(object): ''' A class that represents a Rock Ridge Platform Dependent record. This record is used to add platform-specific information to a Directory Entry, and may also be used as a terminator for Rock Ridge entries. ''' __slots__ = ('_initialized', 'padding') FMT = '=BB' def __init__(self): # type: () -> None self._initialized = False def parse(self, rrstr): # type: (bytes) -> None ''' Parse a Rock Ridge Platform Dependent record out of a string. Parameters: rrstr - The string to parse the record out of. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PD record already initialized') (su_len_unused, su_entry_version_unused) = struct.unpack_from(self.FMT, rrstr[:4], 2) self.padding = rrstr[4:] # We assume that the caller has already checked the su_entry_version, # so we don't bother. self._initialized = True def new(self): # type: () -> None ''' Create a new Rock Ridge Platform Dependent record. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('PD record already initialized') self._initialized = True self.padding = b'' def record(self): # type: () -> bytes ''' Generate a string representing the Rock Ridge Platform Dependent record. Parameters: None. Returns: String containing the Rock Ridge record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('PD record not initialized') return b'PD' + struct.pack(self.FMT, RRPDRecord.length(self.padding), SU_ENTRY_VERSION) + self.padding @staticmethod def length(padding): # type: (bytes) -> int ''' Static method to return the length of the Rock Ridge Platform Dependent record. Parameters: padding - The padding bytes that this record will use. Returns: The length of this record in bytes. ''' return 4 + len(padding) class RockRidgeEntries(object): ''' A simple class container to hold a long list of possible Rock Ridge records. ''' __slots__ = ('sp_record', 'rr_record', 'ce_record', 'px_record', 'er_record', 'es_records', 'pn_record', 'sl_records', 'nm_records', 'cl_record', 'pl_record', 'tf_record', 'sf_record', 're_record', 'st_record', 'pd_records', 'al_records') def __init__(self): # type: () -> None self.sp_record = None # type: Optional[RRSPRecord] self.rr_record = None # type: Optional[RRRRRecord] self.ce_record = None # type: Optional[RRCERecord] self.px_record = None # type: Optional[RRPXRecord] self.er_record = None # type: Optional[RRERRecord] self.es_records = [] # type: List[RRESRecord] self.pn_record = None # type: Optional[RRPNRecord] self.sl_records = [] # type: List[RRSLRecord] self.nm_records = [] # type: List[RRNMRecord] self.cl_record = None # type: Optional[RRCLRecord] self.pl_record = None # type: Optional[RRPLRecord] self.tf_record = None # type: Optional[RRTFRecord] self.sf_record = None # type: Optional[RRSFRecord] self.re_record = None # type: Optional[RRRERecord] self.st_record = None # type: Optional[RRSTRecord] self.pd_records = [] # type: List[RRPDRecord] self.al_records = [] # type: List[RRALRecord] # This is the class that implements the Rock Ridge extensions for PyCdlib. The # Rock Ridge extensions are a set of extensions for embedding POSIX semantics # on an ISO9660 filesystem. Rock Ridge works by utilizing the 'System Use' # area of the directory record to store additional metadata about files. This # includes things like POSIX users, groups, ctime, mtime, atime, etc., as well # as the ability to have directory structures deeper than 8 and filenames longer # than 8.3. Rock Ridge depends on the System Use and Sharing Protocol (SUSP), # which defines some standards on how to use the System Area. class RockRidge(object): ''' A class representing Rock Ridge entries. ''' __slots__ = ('_initialized', 'dr_entries', 'ce_entries', 'cl_to_moved_dr', 'moved_to_cl_dr', 'parent_link', 'rr_version', 'ce_block', 'bytes_to_skip', '_full_name') def __init__(self): # type: () -> None self.dr_entries = RockRidgeEntries() self.ce_entries = RockRidgeEntries() self.cl_to_moved_dr = None # type: Optional[dr.DirectoryRecord] self.moved_to_cl_dr = None # type: Optional[dr.DirectoryRecord] self.parent_link = None # type: Optional[dr.DirectoryRecord] self.rr_version = '' self.ce_block = None # type: Optional[RockRidgeContinuationBlock] self._initialized = False def has_entry(self, name): # type: (str) -> bool ''' An internal method to tell if we have already parsed an entry of the named type. Parameters: name - The name of the entry to check. Returns: True if we have already parsed an entry of the named type, False otherwise. ''' return getattr(self.dr_entries, name) or getattr(self.ce_entries, name) def parse(self, record, is_first_dir_record_of_root, bytes_to_skip, continuation): # type: (bytes, bool, int, bool) -> None ''' Method to parse a rock ridge record. Parameters: record - The record to parse. is_first_dir_record_of_root - Whether this is the first directory record of the root directory record; certain Rock Ridge entries are only valid there. bytes_to_skip - The number of bytes to skip at the beginning of the record. continuation - Whether the new entries should go in the continuation list or in the DR list. Returns: Nothing. ''' # Note that we very explicitly do not check if self._initialized is True # here; this can be called multiple times in the case where there is # a continuation entry. if continuation: entry_list = self.ce_entries else: entry_list = self.dr_entries self.bytes_to_skip = bytes_to_skip offset = bytes_to_skip left = len(record) px_record_length = None has_es_record = False sf_record_length = None er_id = None while True: if left == 0: break if left == 1: # There may be a padding byte on the end. if bytes(bytearray([record[offset]])) != b'\x00': raise pycdlibexception.PyCdlibInvalidISO('Invalid pad byte') break if left < 4: raise pycdlibexception.PyCdlibInvalidISO('Not enough bytes left in the System Use field') (rtype, su_len, su_entry_version) = struct.unpack_from('=2sBB', record[:offset + 4], offset) if su_entry_version != SU_ENTRY_VERSION: raise pycdlibexception.PyCdlibInvalidISO('Invalid RR version %d!' % su_entry_version) if su_len == 0: raise pycdlibexception.PyCdlibInvalidISO('Zero size for Rock Ridge entry length') recslice = record[offset:] if rtype in (b'SP', b'RR', b'CE', b'PX', b'ST', b'ER', b'PN', b'CL', b'PL', b'RE', b'TF', b'SF'): recname = rtype.decode('utf-8').lower() + '_record' if self.has_entry(recname): raise pycdlibexception.PyCdlibInvalidISO('Only single %s record supported' % (rtype.decode('utf-8'))) if rtype == b'SP': if left < 7 or not is_first_dir_record_of_root: raise pycdlibexception.PyCdlibInvalidISO('Invalid SUSP SP record') # OK, this is the first Directory Record of the root # directory, which means we should check it for the SUSP/RR # extension, which is exactly 7 bytes and starts with 'SP'. entry_list.sp_record = RRSPRecord() entry_list.sp_record.parse(recslice) elif rtype == b'RR': entry_list.rr_record = RRRRRecord() entry_list.rr_record.parse(recslice) elif rtype == b'CE': entry_list.ce_record = RRCERecord() entry_list.ce_record.parse(recslice) elif rtype == b'PX': entry_list.px_record = RRPXRecord() px_record_length = entry_list.px_record.parse(recslice) elif rtype == b'PD': pd = RRPDRecord() pd.parse(recslice) entry_list.pd_records.append(pd) elif rtype == b'ST': entry_list.st_record = RRSTRecord() entry_list.st_record.parse(recslice) elif rtype == b'ER': entry_list.er_record = RRERRecord() entry_list.er_record.parse(recslice) er_id = entry_list.er_record.ext_id elif rtype == b'ES': es = RRESRecord() es.parse(recslice) entry_list.es_records.append(es) has_es_record = True elif rtype == b'PN': entry_list.pn_record = RRPNRecord() entry_list.pn_record.parse(recslice) elif rtype == b'SL': new_sl_record = RRSLRecord() new_sl_record.parse(recslice) entry_list.sl_records.append(new_sl_record) elif rtype == b'NM': new_nm_record = RRNMRecord() new_nm_record.parse(recslice) entry_list.nm_records.append(new_nm_record) elif rtype == b'CL': entry_list.cl_record = RRCLRecord() entry_list.cl_record.parse(recslice) elif rtype == b'PL': entry_list.pl_record = RRPLRecord() entry_list.pl_record.parse(recslice) elif rtype == b'RE': entry_list.re_record = RRRERecord() entry_list.re_record.parse(recslice) elif rtype == b'TF': entry_list.tf_record = RRTFRecord() entry_list.tf_record.parse(recslice) elif rtype == b'SF': entry_list.sf_record = RRSFRecord() entry_list.sf_record.parse(recslice) sf_record_length = len(recslice) elif rtype == b'AL': new_al_record = RRALRecord() new_al_record.parse(recslice) entry_list.al_records.append(new_al_record) else: raise pycdlibexception.PyCdlibInvalidISO('Unknown SUSP record') offset += su_len left -= su_len # Now let's determine the version of Rock Ridge that we have (1.09, # 1.10, or 1.12). Unfortunately, there is no direct information from # Rock Ridge, so we infer it from what is present. In an ideal world, # the following table would tell us: # # | Feature/Rock Ridge version | 1.09 | 1.10 | 1.12 | # +----------------------------+---------------+---------------+---------------+ # | Has RR Record? | True or False | False | False | # | Has ES Record? | False | False | True or False | # | Has SF Record? | False | True or False | True or False | # | PX Record length | 36 | 36 | 44 | # | SF Record length | N/A | 12 | 21 | # | ER Desc string | RRIP_1991A | RRIP_1991A | IEEE_P1282 | # +----------------------------+---------------+---------------+---------------+ # # While that is a good start, we don't live in an ideal world. In # particular, we've seen ISOs in the wild (OpenSolaris 2008) that put an # RR record into an otherwise 1.12 Rock Ridge entry. So we'll use the # above as a hint, and allow for some wiggle room. if px_record_length == 44 or sf_record_length == 21 or has_es_record or er_id == EXT_ID_112: self.rr_version = '1.12' else: # Not 1.12, so either 1.09 or 1.10. if sf_record_length == 12: self.rr_version = '1.10' else: self.rr_version = '1.09' namelist = [nm.posix_name for nm in self.dr_entries.nm_records] namelist.extend([nm.posix_name for nm in self.ce_entries.nm_records]) self._full_name = b''.join(namelist) self._initialized = True def _record(self, entries): # type: (RockRidgeEntries) -> bytes ''' Return a string representing the Rock Ridge entry. Parameters: entries - The dr_entries or ce_entries to generate a record for. Returns: A string representing the Rock Ridge entry. ''' outlist = [] if entries.sp_record is not None: outlist.append(entries.sp_record.record()) if entries.rr_record is not None: outlist.append(entries.rr_record.record()) for nm_record in entries.nm_records: outlist.append(nm_record.record()) if entries.px_record is not None: outlist.append(entries.px_record.record(self.rr_version)) for sl_record in entries.sl_records: outlist.append(sl_record.record()) if entries.tf_record is not None: outlist.append(entries.tf_record.record()) if entries.cl_record is not None: outlist.append(entries.cl_record.record()) if entries.pl_record is not None: outlist.append(entries.pl_record.record()) if entries.re_record is not None: outlist.append(entries.re_record.record()) for es_record in entries.es_records: outlist.append(es_record.record()) if entries.er_record is not None: outlist.append(entries.er_record.record()) for al_record in entries.al_records: outlist.append(al_record.record()) if entries.ce_record is not None: outlist.append(entries.ce_record.record()) for pd_record in entries.pd_records: outlist.append(pd_record.record()) if entries.st_record is not None: outlist.append(entries.st_record.record()) if entries.sf_record is not None: outlist.append(entries.sf_record.record()) return b''.join(outlist) def record_dr_entries(self): # type: () -> bytes ''' Return a string representing the Rock Ridge entries in the Directory Record. Parameters: None. Returns: A string representing the Rock Ridge entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self._record(self.dr_entries) def record_ce_entries(self): # type: () -> bytes ''' Return a string representing the Rock Ridge entries in the Continuation Entry. Parameters: None. Returns: A string representing the Rock Ridge entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self._record(self.ce_entries) def _new_symlink(self, symlink_path, curr_dr_len): # type: (bytes, int) -> int ''' An internal method to add the appropriate symlink record(s) to the ISO. Parameters: symlink_path - The absolute symlink path to add to the ISO. curr_dr_len - The current directory record length. Returns: The new directory record length. ''' # This is more complicated than I realized. There are up to 3 layers # of maximum length: # 1. If we are still using the directory record, then we are # subject to the maximum length left in the directory record. # 2. The SL entry length is an 8-bit number, so we may need multiple # SL entries in order to encode all of the components. # 3. The Component header is also an 8-bit number, so we may need # multiple SL records to record this component. # # Note that the component header length can never be longer than the SL # entry length. Thus, we are reduced to 2 lengths to worry about. if curr_dr_len + RRSLRecord.length(symlink_path.split(b'/')) > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 curr_sl = RRSLRecord() curr_sl.new() sl_rec_header_len = RRSLRecord.header_length() thislen = RRSLRecord.length([b'a']) if curr_dr_len + thislen < ALLOWED_DR_SIZE: # There is enough room in the directory record for at least # part of the symlink curr_comp_area_length = ALLOWED_DR_SIZE - curr_dr_len - sl_rec_header_len self.dr_entries.sl_records.append(curr_sl) curr_dr_len += sl_rec_header_len sl_in_dr = True else: # Not enough room in the directory record, so proceed to # the continuation entry directly. curr_comp_area_length = RRSLRecord.maximum_component_area_length() self.ce_entries.sl_records.append(curr_sl) if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(sl_rec_header_len) sl_in_dr = False for index, comp in enumerate(symlink_path.split(b'/')): special = False if index == 0 and comp == b'': comp = b'/' special = True mincomp = comp elif comp == b'.': special = True mincomp = comp elif comp == b'..': special = True mincomp = comp else: mincomp = b'a' offset = 0 done = False while not done: minimum = RRSLRecord.Component.length(mincomp) if minimum > curr_comp_area_length: # There wasn't enough room in the last SL record # for more data. Set the 'continued' flag on the old # SL record, and then create a new one. curr_sl.set_continued() if offset != 0: # If we need to continue this particular # *component* in the next SL record, then we # also need to mark the curr_sl's last component # header as continued. curr_sl.set_last_component_continued() curr_sl = RRSLRecord() curr_sl.new() self.ce_entries.sl_records.append(curr_sl) curr_comp_area_length = RRSLRecord.maximum_component_area_length() if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(sl_rec_header_len) sl_in_dr = False if special: complen = minimum length = 0 compslice = comp else: complen = RRSLRecord.Component.length(comp[offset:]) if complen > curr_comp_area_length: length = curr_comp_area_length - 2 else: length = complen compslice = comp[offset:offset + length] curr_sl.add_component(compslice) if sl_in_dr: curr_dr_len += RRSLRecord.Component.length(compslice) else: if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(RRSLRecord.Component.length(compslice)) offset += length curr_comp_area_length = curr_comp_area_length - length - 2 if special: done = True else: if offset >= len(comp): done = True return curr_dr_len def _new_attributes(self, attributes, curr_dr_len): # type: (Dict[bytes, bytes], int) -> int ''' An internal method to add arbitrary attributes to the ISO. Parameters: attributes - A dictionary of attributes to add to the ISO. curr_dr_len - The current directory record length. Returns: The new directory record length. ''' attr_list = list(attributes.keys()) + list(attributes.values()) if curr_dr_len + RRALRecord.length(attr_list) > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 curr_al = RRALRecord() curr_al.new() al_rec_header_len = RRALRecord.header_length() thislen = RRALRecord.length([b'a']) if curr_dr_len + thislen < ALLOWED_DR_SIZE: # There is enough room in the directory record for at least # part of one of the attributes. curr_comp_area_length = ALLOWED_DR_SIZE - curr_dr_len - al_rec_header_len self.dr_entries.al_records.append(curr_al) curr_dr_len += al_rec_header_len al_in_dr = True else: # Not enough room in the directory record, so proceed to # the continuation entry directly. curr_comp_area_length = RRALRecord.maximum_component_area_length() if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(al_rec_header_len) self.ce_entries.al_records.append(curr_al) al_in_dr = False for attr in attr_list: offset = 0 done = False while not done: minimum = RRALRecord.Component.length(b'a') if minimum > curr_comp_area_length: # There wasn't enough room in the last AL record # for more data. Set the 'continued' flag on the old # AL record, and then create a new one. curr_al.set_continued() if offset != 0: # If we need to continue this particular # *component* in the next AL record, then we # also need to mark the curr_al's last component # header as continued. curr_al.set_last_component_continued() curr_al = RRALRecord() curr_al.new() self.ce_entries.al_records.append(curr_al) curr_comp_area_length = RRALRecord.maximum_component_area_length() if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(al_rec_header_len) al_in_dr = False complen = RRALRecord.Component.length(attr[offset:]) if complen > curr_comp_area_length: length = curr_comp_area_length - 2 else: length = complen compslice = attr[offset:offset + length] curr_al.add_component(compslice) if al_in_dr: curr_dr_len += RRALRecord.Component.length(compslice) else: if self.dr_entries.ce_record is not None: self.dr_entries.ce_record.add_record(RRALRecord.Component.length(compslice)) offset += length curr_comp_area_length = curr_comp_area_length - length - 2 if offset >= len(attr): done = True return curr_dr_len def _add_name(self, rr_name, curr_dr_len): # type: (bytes, int) -> int ''' An internal method to add the appropriate name records to the ISO. Parameters: rr_name - The Rock Ridge name to add to the ISO. curr_dr_len - The current directory record length. Returns: The new directory record length. ''' # The length we are putting in this object (as opposed to the # continuation entry) is the maximum, minus how much is already in the # DR, minus 5 for the NM metadata. We know that at least part of the # NM record will always fit in this DR. That's because the DR is a # maximum size of 255, and the ISO9660 fields uses a maximum of 34 bytes # for metadata and 8+1+3+1+5 (8 for name, 1 for dot, 3 for extension, # 1 for semicolon, and 5 for version number, allowed up to 32767), which # leaves the System Use entry with 255 - 34 - 18 = 203 bytes. Before # this record, the only records we ever put in place could be the SP or # the RR record, and the combination of them is never > 203, so we will # always put some NM data in here. len_here = ALLOWED_DR_SIZE - curr_dr_len - 5 if len_here < len(rr_name): # If there isn't room in the DR entry for the entire name, we know # we need a CE record to fit it. if self.dr_entries.ce_record is None: return -1 if len_here < 0: len_here = 0 curr_nm = None if len_here > 0: curr_nm = RRNMRecord() curr_nm.new(rr_name[:len_here]) self.dr_entries.nm_records.append(curr_nm) curr_dr_len += RRNMRecord.length(rr_name[:len_here]) offset = len_here while offset < len(rr_name): if self.dr_entries.ce_record is None: return -1 if curr_nm is not None: curr_nm.set_continued() # We clip the length for this NM entry to 250, as that is # the maximum possible size for an NM entry. length = min(len(rr_name[offset:]), 250) curr_nm = RRNMRecord() curr_nm.new(rr_name[offset:offset + length]) self.ce_entries.nm_records.append(curr_nm) self.dr_entries.ce_record.add_record(RRNMRecord.length(rr_name[offset:offset + length])) offset += length return curr_dr_len def _assign_entries(self, is_first_dir_record_of_root, rr_name, file_mode, symlink_path, rr_relocated_child, rr_relocated, rr_relocated_parent, bytes_to_skip, curr_dr_len, attributes): # type: (bool, bytes, int, bytes, bool, bool, bool, int, int, Dict[bytes, bytes]) -> int ''' Assign Rock Ridge entries to the appropriate DR or CE record. Parameters: is_first_dir_record_of_root - Whether this is the first directory record of the root directory record; certain Rock Ridge entries are only valid there. rr_name - The alternate name for this Rock Ridge entry. file_mode - The Unix file mode for this Rock Ridge entry. symlink_path - The path to the target of the symlink, or None if this is not a symlink. rr_relocated_child - Whether this is a relocated child entry. rr_relocated - Whether this is a relocated entry. rr_relocated_parent - Whether this is a relocated parent entry. bytes_to_skip - The number of bytes to skip for the record. curr_dr_len - The current length of the directory record; this is used when figuring out whether a continuation entry is needed. attributes - Arbitrary attributes to add to the Rock Ridge entry. Returns: The length of the directory record after the Rock Ridge extension has been added, or -1 if the entry will not fit. ''' # For SP Record if is_first_dir_record_of_root: new_sp = RRSPRecord() new_sp.new(bytes_to_skip) thislen = RRSPRecord.length() if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: # In reality, this can never happen. If the SP record pushes # us over the DR limit, then there is no room for a CE record # either, and we are going to fail. We leave this in place # both for consistency with other records and to keep mypy # happy. return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.sp_record = new_sp else: curr_dr_len += thislen self.dr_entries.sp_record = new_sp # For RR Record rr_record = None if self.rr_version == '1.09': rr_record = RRRRRecord() rr_record.new() thislen = RRRRRecord.length() if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: # In reality, this can never happen. If the RR record pushes # us over the DR limit, then there is no room for a CE record # either, and we are going to fail. We leave this in place # both for consistency with other records and to keep mypy # happy. return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.rr_record = rr_record else: curr_dr_len += thislen self.dr_entries.rr_record = rr_record # For NM record if rr_name: curr_dr_len = self._add_name(rr_name, curr_dr_len) if curr_dr_len < 0: return -1 if rr_record is not None: rr_record.append_field('NM') # For PX record new_px = RRPXRecord() new_px.new(file_mode) thislen = RRPXRecord.length(self.rr_version) if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.px_record = new_px else: curr_dr_len += thislen self.dr_entries.px_record = new_px if rr_record is not None: rr_record.append_field('PX') # For SL record if symlink_path: curr_dr_len = self._new_symlink(symlink_path, curr_dr_len) if curr_dr_len < 0: return -1 if rr_record is not None: rr_record.append_field('SL') # For TF record new_tf = RRTFRecord() new_tf.new(TF_FLAGS) thislen = RRTFRecord.length(TF_FLAGS) if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.tf_record = new_tf else: curr_dr_len += thislen self.dr_entries.tf_record = new_tf if rr_record is not None: rr_record.append_field('TF') # For CL record if rr_relocated_child: new_cl = RRCLRecord() new_cl.new() thislen = RRCLRecord.length() if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.cl_record = new_cl else: curr_dr_len += thislen self.dr_entries.cl_record = new_cl if rr_record is not None: rr_record.append_field('CL') # For RE record if rr_relocated: new_re = RRRERecord() new_re.new() thislen = RRRERecord.length() if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.re_record = new_re else: curr_dr_len += thislen self.dr_entries.re_record = new_re if rr_record is not None: rr_record.append_field('RE') # For PL record if rr_relocated_parent: new_pl = RRPLRecord() new_pl.new() thislen = RRPLRecord.length() if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.pl_record = new_pl else: curr_dr_len += thislen self.dr_entries.pl_record = new_pl if rr_record is not None: rr_record.append_field('PL') # For ER record if is_first_dir_record_of_root: new_er = RRERRecord() if self.rr_version in ('1.09', '1.10'): new_er.new(EXT_ID_109, EXT_DES_109, EXT_SRC_109) thislen = RRERRecord.length(EXT_ID_109, EXT_DES_109, EXT_SRC_109) else: # Assume 1.12 new_er.new(EXT_ID_112, EXT_DES_112, EXT_SRC_112) thislen = RRERRecord.length(EXT_ID_112, EXT_DES_112, EXT_SRC_112) if curr_dr_len + thislen > ALLOWED_DR_SIZE: if self.dr_entries.ce_record is None: return -1 self.dr_entries.ce_record.add_record(thislen) self.ce_entries.er_record = new_er else: curr_dr_len += thislen self.dr_entries.er_record = new_er # For AL record if attributes: curr_dr_len = self._new_attributes(attributes, curr_dr_len) if curr_dr_len < 0: return -1 return curr_dr_len def new(self, is_first_dir_record_of_root, rr_name, file_mode, symlink_path, rr_version, rr_relocated_child, rr_relocated, rr_relocated_parent, bytes_to_skip, curr_dr_len, attributes): # type: (bool, bytes, int, bytes, str, bool, bool, bool, int, int, Dict[bytes, bytes]) -> int ''' Create a new Rock Ridge record. Parameters: is_first_dir_record_of_root - Whether this is the first directory record of the root directory record; certain Rock Ridge entries are only valid there. rr_name - The alternate name for this Rock Ridge entry. file_mode - The Unix file mode for this Rock Ridge entry. symlink_path - The path to the target of the symlink, or None if this is not a symlink. rr_version - The version of Rock Ridge to use; must be '1.09', '1.10', or '1.12'. rr_relocated_child - Whether this is a relocated child entry. rr_relocated - Whether this is a relocated entry. rr_relocated_parent - Whether this is a relocated parent entry. bytes_to_skip - The number of bytes to skip for the record. curr_dr_len - The current length of the directory record; this is used when figuring out whether a continuation entry is needed. attributes - Arbitrary attributes to add to the record. This is a non-standard extension, so use with care. Returns: The length of the directory record after the Rock Ridge extension has been added. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension already initialized') if rr_version not in ('1.09', '1.10', '1.12'): raise pycdlibexception.PyCdlibInvalidInput('Only Rock Ridge versions 1.09, 1.10, and 1.12 are implemented') self.rr_version = rr_version new_dr_len = self._assign_entries(is_first_dir_record_of_root, rr_name, file_mode, symlink_path, rr_relocated_child, rr_relocated, rr_relocated_parent, bytes_to_skip, curr_dr_len, attributes) if new_dr_len < 0: self.dr_entries = RockRidgeEntries() self.ce_entries = RockRidgeEntries() self.dr_entries.ce_record = RRCERecord() self.dr_entries.ce_record.new() curr_dr_len += RRCERecord.length() new_dr_len = self._assign_entries(is_first_dir_record_of_root, rr_name, file_mode, symlink_path, rr_relocated_child, rr_relocated, rr_relocated_parent, bytes_to_skip, curr_dr_len, attributes) if new_dr_len < 0: raise pycdlibexception.PyCdlibInternalError('Could not assign Rock Ridge entries') if new_dr_len > ALLOWED_DR_SIZE: raise pycdlibexception.PyCdlibInternalError('Rock Ridge entry increased DR length too far') new_dr_len += (new_dr_len % 2) namelist = [nm.posix_name for nm in self.dr_entries.nm_records] namelist.extend([nm.posix_name for nm in self.ce_entries.nm_records]) self._full_name = b''.join(namelist) self._initialized = True return new_dr_len def add_to_file_links(self): # type: () -> None ''' Increment the number of POSIX file links on this entry by one. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.dr_entries.px_record is None: if self.ce_entries.px_record is None: raise pycdlibexception.PyCdlibInvalidInput('No Rock Ridge file links') self.ce_entries.px_record.posix_file_links += 1 else: self.dr_entries.px_record.posix_file_links += 1 def remove_from_file_links(self): # type: () -> None ''' Decrement the number of POSIX file links on this entry by one. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.dr_entries.px_record is None: if self.ce_entries.px_record is None: raise pycdlibexception.PyCdlibInvalidInput('No Rock Ridge file links') self.ce_entries.px_record.posix_file_links -= 1 else: self.dr_entries.px_record.posix_file_links -= 1 def copy_file_links(self, src): # type: (RockRidge) -> None ''' Copy the number of file links from the source Rock Ridge entry into this Rock Ridge entry. Parameters: src - The source Rock Ridge entry to copy from. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') # First, get the src data if src.dr_entries.px_record is None: if src.ce_entries.px_record is None: raise pycdlibexception.PyCdlibInvalidInput('No Rock Ridge file links') num_links = src.ce_entries.px_record.posix_file_links else: num_links = src.dr_entries.px_record.posix_file_links # Now apply it to this record. if self.dr_entries.px_record is None: if self.ce_entries.px_record is None: raise pycdlibexception.PyCdlibInvalidInput('No Rock Ridge file links') self.ce_entries.px_record.posix_file_links = num_links else: self.dr_entries.px_record.posix_file_links = num_links def get_file_mode(self): # type: () -> int ''' Get the POSIX file mode bits for this Rock Ridge entry. Parameters: None. Returns: The POSIX file mode bits for this Rock Ridge entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.dr_entries.px_record is None: if self.ce_entries.px_record is None: raise pycdlibexception.PyCdlibInvalidInput('No Rock Ridge file mode') return self.ce_entries.px_record.posix_file_mode return self.dr_entries.px_record.posix_file_mode def name(self): # type: () -> bytes ''' Get the alternate name from this Rock Ridge entry. Parameters: None. Returns: The alternate name from this Rock Ridge entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self._full_name def _is_symlink(self): # type: () -> bool ''' Internal method to determine whether this Rock Ridge entry is a symlink. ''' return len(self.dr_entries.sl_records) > 0 or len(self.ce_entries.sl_records) > 0 def is_symlink(self): # type: () -> bool ''' Determine whether this Rock Ridge entry describes a symlink. Parameters: None. Returns: True if this Rock Ridge entry describes a symlink, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self._is_symlink() def symlink_path(self): # type: () -> bytes ''' Get the path as a string of the symlink target of this Rock Ridge entry (if this is a symlink). Parameters: None. Returns: Symlink path as a string. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if not self._is_symlink(): raise pycdlibexception.PyCdlibInvalidInput('Entry is not a symlink!') outlist = [] saved = b'' for rec in self.dr_entries.sl_records + self.ce_entries.sl_records: if rec.last_component_continued(): saved += rec.name() else: saved += rec.name() outlist.append(saved) saved = b'' if saved != b'': raise pycdlibexception.PyCdlibInvalidISO('Saw a continued symlink record with no end; ISO is probably malformed') return b'/'.join(outlist) def child_link_record_exists(self): # type: () -> bool ''' Determine whether this Rock Ridge entry has a child link record (used for relocating deep directory records). Parameters: None. Returns: True if this Rock Ridge entry has a child link record, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self.dr_entries.cl_record is not None or self.ce_entries.cl_record is not None def child_link_update_from_dirrecord(self): # type: () -> None ''' Update the logical extent number stored in the child link record (if there is one), from the directory record entry that was stored in the child_link member. This is used at the end of reshuffling extents to properly update the child link records. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.cl_to_moved_dr is None: raise pycdlibexception.PyCdlibInvalidInput('No child link found!') if self.dr_entries.cl_record is not None: self.dr_entries.cl_record.set_log_block_num(self.cl_to_moved_dr.extent_location()) elif self.ce_entries.cl_record is not None: self.ce_entries.cl_record.set_log_block_num(self.cl_to_moved_dr.extent_location()) else: raise pycdlibexception.PyCdlibInvalidInput('Could not find child link record!') def child_link_extent(self): # type: () -> int ''' Get the extent of the child of this entry if it has one. Parameters: None. Returns: The logical block number of the child if it exists. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.dr_entries.cl_record is not None: return self.dr_entries.cl_record.child_log_block_num if self.ce_entries.cl_record is not None: return self.ce_entries.cl_record.child_log_block_num raise pycdlibexception.PyCdlibInternalError('Asked for child extent for non-existent child record') def parent_link_record_exists(self): # type: () -> bool ''' Determine whether this Rock Ridge entry has a parent link record (used for relocating deep directory records). Parameters: None: Returns: True if this Rock Ridge entry has a parent link record, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self.dr_entries.pl_record is not None or self.ce_entries.pl_record is not None def parent_link_update_from_dirrecord(self): # type: () -> None ''' Update the logical extent number stored in the parent link record (if there is one), from the directory record entry that was stored in the parent_link member. This is used at the end of reshuffling extents to properly update the parent link records. Parameters: None. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.parent_link is None: raise pycdlibexception.PyCdlibInvalidInput('No parent link found!') if self.dr_entries.pl_record is not None: self.dr_entries.pl_record.set_log_block_num(self.parent_link.extent_location()) elif self.ce_entries.pl_record is not None: self.ce_entries.pl_record.set_log_block_num(self.parent_link.extent_location()) else: raise pycdlibexception.PyCdlibInvalidInput('Could not find parent link record!') def parent_link_extent(self): # type: () -> int ''' Get the extent of the parent of this entry if it has one. Parameters: None. Returns: The logical block number of the parent if it exists. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') if self.dr_entries.pl_record is not None: return self.dr_entries.pl_record.parent_log_block_num if self.ce_entries.pl_record is not None: return self.ce_entries.pl_record.parent_log_block_num raise pycdlibexception.PyCdlibInternalError('Asked for parent extent for non-existent parent record') def relocated_record(self): # type: () -> bool ''' Determine whether this Rock Ridge entry has a relocated record (used for relocating deep directory records). Parameters: None. Returns: True if this Rock Ridge entry has a relocated record, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') return self.dr_entries.re_record is not None or self.ce_entries.re_record is not None def update_ce_block(self, block): # type: (RockRidgeContinuationBlock) -> None ''' Update the Continuation Entry block object used by this Rock Ridge Record. Parameters: block - The new block object. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('Rock Ridge extension not initialized') self.ce_block = block class RockRidgeContinuationEntry(object): ''' A class representing one 'abstract' Rock Ridge Continuation Entry. These entries are strictly for keeping tabs of the offset and size of each entry in a continuation block; they have no smarts beyond that. ''' __slots__ = ('_offset', '_length') def __init__(self, offset, length): # type: (int, int) -> None self._offset = offset self._length = length @property def offset(self): # type: () -> int ''' Property method to return the offset of this entry. Parameters: None. Returns: The offset of this entry. ''' return self._offset @property def length(self): # type: () -> int ''' Property method to return the length of this entry. Parameters: None. Returns: The length of this entry. ''' return self._length def __lt__(self, other): return self._offset < other.offset class RockRidgeContinuationBlock(object): ''' A class representing one 'abstract' Rock Ridge Continuation Block. A Continuation Block is one extent holding many Rock Ridge Continuation Entries. However, this is just used for tracking how many entries will fit in one block; all tracking of the actual data must be done elsewhere. ''' __slots__ = ('_extent', '_max_block_size', '_entries') def __init__(self, extent, max_block_size): # type: (int, int) -> None self._extent = extent self._max_block_size = max_block_size self._entries = [] # type: List[RockRidgeContinuationEntry] def extent_location(self): # type: () -> int ''' Get the extent location that this block resides at. Parameters: None. Returns: The extent location that this block resides at. ''' return self._extent def set_extent_location(self, loc): # type: (int) -> None ''' Set the extent location that this block resides at. Parameters: loc - The new extent location. Returns: Nothing. ''' self._extent = loc def track_entry(self, offset, length): # type: (int, int) -> None ''' Track an already allocated entry in this Rock Ridge Continuation Block. Parameters: offset - The offset at which to place the entry. length - The length of the entry to track. Returns: Nothing. ''' newlen = offset + length - 1 for entry in self._entries: thislen = entry.offset + entry.length - 1 overlap = range(max(entry.offset, offset), min(thislen, newlen) + 1) if overlap: raise pycdlibexception.PyCdlibInvalidISO('Overlapping CE regions on the ISO') # OK, there were no overlaps with existing entries. Let's see if # the new entry fits at the end. if offset + length > self._max_block_size: raise pycdlibexception.PyCdlibInvalidISO('No room in continuation block to track entry') # We passed all of the checks; add the new entry to track in. bisect.insort_left(self._entries, RockRidgeContinuationEntry(offset, length)) def add_entry(self, length): # type: (int) -> int ''' Add a new entry to this Rock Ridge Continuation Block. This method attempts to find a gap that fits the new length anywhere within this Continuation Block. If successful, it returns the offset at which it placed this entry. If unsuccessful, it returns None. Parameters: length - The length of the entry to find a gap for. Returns: The offset the entry was placed at, or None if no gap was found. ''' offset = -1 # Need to find a gap for index, entry in enumerate(self._entries): if index == 0: if entry.offset != 0 and length <= entry.offset: # We can put it at the beginning! offset = 0 break else: lastentry = self._entries[index - 1] lastend = lastentry.offset + lastentry.length - 1 gapsize = entry.offset - lastend - 1 if gapsize >= length: # We found a spot for it! offset = lastend + 1 break else: # We reached the end without finding a gap for it. Look at the last # entry and see if there is room at the end. if self._entries: lastentry = self._entries[-1] lastend = lastentry.offset + lastentry.length - 1 left = self._max_block_size - lastend - 1 if left >= length: offset = lastend + 1 else: if self._max_block_size >= length: offset = 0 if offset >= 0: bisect.insort_left(self._entries, RockRidgeContinuationEntry(offset, length)) return offset def remove_entry(self, offset, length): # type: (int, int) -> None ''' Given an offset and length, find and remove the entry in this block that corresponds. Parameters: offset - The offset of the entry to look for. length - The length of the entry to look for. Returns: Nothing. ''' for index, entry in enumerate(self._entries): if entry.offset == offset and entry.length == length: del self._entries[index] break else: raise pycdlibexception.PyCdlibInternalError('Could not find an entry for the RR CE entry in the CE block!') ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1599880065.576995 pycdlib-1.11.0/pycdlib/udf.py0000664000175000017500000057363200000000000020601 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2018-2019 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Classes to support UDF. ''' from __future__ import absolute_import import random import struct import sys import time try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO # pylint: disable=ungrouped-imports from pycdlib import pycdlibexception from pycdlib import utils # For mypy annotations if False: # pylint: disable=using-constant-test from typing import List, Optional, Type, Union # NOQA pylint: disable=unused-import # NOTE: this import has to be here to avoid circular deps from pycdlib import inode # NOQA pylint: disable=unused-import # This is the CRC CCITT table generated with a polynomial of 0x11021 and # 16-bits. The following code will re-generate the table: # # def _bytecrc(crc, poly, n): # mask = 1<<(n-1) # for i in range(8): # if crc & mask: # crc = (crc << 1) ^ poly # else: # crc = crc << 1 # mask = (1< int ''' Calculate the CRC over a range of bytes using the CCITT polynomial. Parameters: data - The array of bytes to calculate the CRC over. Returns: The CCITT CRC of the data. ''' crc = 0 if have_py_3: for x in data: crc = crc_ccitt_table[x ^ ((crc >> 8) & 0xFF)] ^ ((crc << 8) & 0xFF00) else: for x in data: crc = crc_ccitt_table[ord(x) ^ ((crc >> 8) & 0xFF)] ^ ((crc << 8) & 0xFF00) # type: ignore return crc def _ostaunicode(src): # type: (str) -> bytes ''' Internal function to create an OSTA byte string from a source string. ''' if have_py_3: bytename = src else: bytename = src.decode('utf-8') # type: ignore try: enc = bytename.encode('latin-1') encbyte = b'\x08' except (UnicodeEncodeError, UnicodeDecodeError): enc = bytename.encode('utf-16_be') encbyte = b'\x10' return encbyte + enc def _ostaunicode_zero_pad(src, fulllen): # type: (str, int) -> bytes ''' Internal function to create a zero-padded Identifier byte string from a source string. Parameters: src - The src string to start from. fulllen - The padded out length of the result. Returns: A full identifier byte string containing the source string. ''' byte_src = _ostaunicode(src) return byte_src + b'\x00' * (fulllen - 1 - len(byte_src)) + (struct.pack('=B', len(byte_src))) class BEAVolumeStructure(object): ''' A class representing a UDF Beginning Extended Area Volume Structure (ECMA-167, Part 2, 9.2). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc') FMT = '=B5sB2041s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> None ''' Parse the passed in data into a UDF BEA Volume Structure. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('BEA Volume Structure already initialized') (structure_type, standard_ident, structure_version, reserved_unused) = struct.unpack_from(self.FMT, data, 0) if structure_type != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure type') if standard_ident != b'BEA01': raise pycdlibexception.PyCdlibInvalidISO('Invalid standard identifier') if structure_version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure version') self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF BEA Volume Structure. Parameters: None. Returns: A string representing this UDF BEA Volume Strucutre. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('BEA Volume Structure not initialized') return struct.pack(self.FMT, 0, b'BEA01', 1, b'\x00' * 2041) def new(self): # type: () -> None ''' Create a new UDF BEA Volume Structure. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('BEA Volume Structure already initialized') self._initialized = True def extent_location(self): # type: () -> int ''' Get the extent location of this UDF BEA Volume Structure. Parameters: None. Returns: Integer extent location of this UDF BEA Volume Structure. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF BEA Volume Structure not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF BEA Volume Structure. Parameters: extent - The new extent location to set for this UDF BEA Volume Structure. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent class NSRVolumeStructure(object): ''' A class representing a UDF NSR Volume Structure (ECMA-167, Part 3, 9.1). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'standard_ident') FMT = '=B5sB2041s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> None ''' Parse the passed in data into a UDF NSR Volume Structure. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF NSR Volume Structure already initialized') (structure_type, self.standard_ident, structure_version, reserved_unused) = struct.unpack_from(self.FMT, data, 0) if structure_type != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure type') if self.standard_ident not in (b'NSR02', b'NSR03'): raise pycdlibexception.PyCdlibInvalidISO('Invalid standard identifier') if structure_version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure version') self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF NSR Volume Structure. Parameters: None. Returns: A string representing this UDF BEA Volume Strucutre. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF NSR Volume Structure not initialized') return struct.pack(self.FMT, 0, self.standard_ident, 1, b'\x00' * 2041) def new(self, version): # type: (int) -> None ''' Create a new UDF NSR Volume Structure. Parameters: version - The version of the NSR Volume Structure to create; only 2 and 3 are supported. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF NSR Volume Structure already initialized') if version == 2: self.standard_ident = b'NSR02' elif version == 3: self.standard_ident = b'NSR03' else: raise pycdlibexception.PyCdlibInternalError('Invalid NSR version requested') self._initialized = True def extent_location(self): # type: () -> int ''' Get the extent location of this UDF NSR Volume Structure. Parameters: None. Returns: Integer extent location of this UDF NSR Volume Structure. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF NSR Volume Structure not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF NSR Volume Structure. Parameters: extent - The new extent location to set for this UDF NSR Volume Structure. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent class TEAVolumeStructure(object): ''' A class representing a UDF Terminating Extended Area Volume Structure (ECMA-167, Part 2, 9.3). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc') FMT = '=B5sB2041s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> None ''' Parse the passed in data into a UDF TEA Volume Structure. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('TEA Volume Structure already initialized') (structure_type, standard_ident, structure_version, reserved_unused) = struct.unpack_from(self.FMT, data, 0) if structure_type != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure type') if standard_ident != b'TEA01': raise pycdlibexception.PyCdlibInvalidISO('Invalid standard identifier') if structure_version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure version') self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF TEA Volume Structure. Parameters: None. Returns: A string representing this UDF TEA Volume Strucutre. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF TEA Volume Structure not initialized') return struct.pack(self.FMT, 0, b'TEA01', 1, b'\x00' * 2041) def new(self): # type: () -> None ''' Create a new UDF TEA Volume Structure. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF TEA Volume Structure already initialized') self._initialized = True def extent_location(self): # type: () -> int ''' Get the extent location of this UDF TEA Volume Structure. Parameters: None. Returns: Integer extent location of this UDF TEA Volume Structure. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF TEA Volume Structure not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF TEA Volume Structure. Parameters: extent - The new extent location to set for this UDF TEA Volume Structure. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor is not initialized') self.new_extent_loc = extent class UDFBootDescriptor(object): ''' A class representing a UDF Boot Descriptor (ECMA-167, Part 2, 9.4). ''' __slots__ = ('_initialized', 'architecture_type', 'boot_identifier', 'boot_extent_loc', 'boot_extent_len', 'load_address', 'start_address', 'desc_creation_time', 'flags', 'boot_use', 'orig_extent_loc', 'new_extent_loc') FMT = ' None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> None ''' Parse the passed in data into a UDF Boot Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Boot Descriptor already initialized') (structure_type, standard_ident, structure_version, reserved1, architecture_type, boot_ident, self.boot_extent_loc, self.boot_extent_len, self.load_address, self.start_address, desc_creation_time, self.flags, reserved2, self.boot_use) = struct.unpack_from(self.FMT, data, 0) if structure_type != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure type') if standard_ident != b'BOOT2': raise pycdlibexception.PyCdlibInvalidISO('Invalid standard identifier') if structure_version != 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid structure version') if reserved1 != 0: raise pycdlibexception.PyCdlibInvalidISO('Invalid reserved1') if self.flags > 1: raise pycdlibexception.PyCdlibInvalidISO('Invalid flags (must be 0 or 1)') if reserved2 != b'\x00' * 32: raise pycdlibexception.PyCdlibInvalidISO('Invalid reserved2') self.architecture_type = UDFEntityID() self.architecture_type.parse(architecture_type) self.boot_identifier = UDFEntityID() self.boot_identifier.parse(boot_ident) self.desc_creation_time = UDFTimestamp() self.desc_creation_time.parse(desc_creation_time) self.orig_extent_loc = extent self._initialized = True def new(self): # type: () -> None ''' Create a new Boot Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Boot Descriptor already initialized') self.flags = 0 # FIXME: allow the user to set this self.architecture_type = UDFEntityID() self.architecture_type.new(0) # FIXME: allow the user to set this self.boot_identifier = UDFEntityID() self.boot_identifier.new(0) # FIXME: allow the user to set this self.boot_extent_loc = 0 # FIXME: allow the user to set this self.boot_extent_len = 0 # FIXME: allow the user to set this self.load_address = 0 # FIXME: allow the user to set this self.start_address = 0 # FIXME: allow the user to set this self.desc_creation_time = UDFTimestamp() self.desc_creation_time.new() self.flags = 0 # FIXME: allow the user to set this self.boot_use = b'\x00' * 1906 # FIXME: allow the user to set this self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Boot Descriptor. Parameters: None. Returns: A string representing this UDF Boot Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Boot Descriptor not initialized') return struct.pack(self.FMT, 0, b'BOOT2', 1, 0, self.architecture_type.record(), self.boot_identifier.record(), self.boot_extent_loc, self.boot_extent_len, self.load_address, self.start_address, self.desc_creation_time.record(), self.flags, b'\x00' * 32, self.boot_use) def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Boot Descriptor. Parameters: None. Returns: Integer extent location of this UDF Boot Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Boot Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF Boot Descriptor. Parameters: extent - The new extent location to set for this UDF Boot Descriptor. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('This UDF Boot Descriptor is not initialized') self.new_extent_loc = extent def _compute_csum(data): # type: (bytes) -> int ''' Compute a simple checksum over the given data. Parameters: data - The data to compute the checksum over. Returns: The checksum. ''' csum = 0 if have_py_3: for byte in data: csum += byte csum -= data[4] else: for byte in data: csum += ord(byte) # type: ignore csum -= ord(data[4]) # type: ignore return csum % 256 class UDFTag(object): ''' A class representing a UDF Descriptor Tag (ECMA-167, Part 3, 7.2). ''' __slots__ = ('_initialized', 'tag_ident', 'desc_version', 'tag_serial_number', 'tag_location', 'desc_crc_length') FMT = ' None self.desc_crc_length = -1 self._initialized = False def parse(self, data, extent): # type: (bytes, int) -> None ''' Parse the passed in data into a UDF Descriptor tag. Parameters: data - The data to parse. extent - The extent to compare against for the tag location. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Tag already initialized') (self.tag_ident, self.desc_version, tag_checksum, reserved, self.tag_serial_number, desc_crc, self.desc_crc_length, self.tag_location) = struct.unpack_from(self.FMT, data, 0) if reserved != 0: raise pycdlibexception.PyCdlibInvalidISO('Reserved data not 0!') if _compute_csum(data[:16]) != tag_checksum: raise pycdlibexception.PyCdlibInvalidISO('Tag checksum does not match!') if self.tag_location != extent: # In theory, we should abort (throw an exception) if we see that a # tag location that doesn't match an actual location. However, we # have seen UDF ISOs in the wild (most notably PS2 GT4 ISOs) that # have an invalid tag location for the second anchor and File Set # Terminator. So that we can support those ISOs, just silently # fix it up. We lose a little bit of detection of whether this is # "truly" a UDFTag, but it is really not a big risk. self.tag_location = extent if self.desc_version not in (2, 3): raise pycdlibexception.PyCdlibInvalidISO('Tag version not 2 or 3') if (len(data) - 16) < self.desc_crc_length: raise pycdlibexception.PyCdlibInternalError('Not enough bytes to compute CRC') if desc_crc != crc_ccitt(data[16:16 + self.desc_crc_length]): raise pycdlibexception.PyCdlibInvalidISO('Tag CRC does not match!') self._initialized = True def record(self, crc_bytes): # type: (bytes) -> bytes ''' Generate the string representing this UDF Descriptor Tag. Parameters: crc_bytes - The string to compute the CRC over. Returns: A string representing this UDF Descriptor Tag. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Descriptor Tag not initialized') crc_byte_len = len(crc_bytes) if self.desc_crc_length >= 0: crc_byte_len = self.desc_crc_length # We need to compute the checksum, but we'll do that by first creating # the output buffer with the csum field set to 0, computing the csum, # and then setting that record back as usual. rec = struct.pack(self.FMT, self.tag_ident, self.desc_version, 0, 0, self.tag_serial_number, crc_ccitt(crc_bytes[:crc_byte_len]), crc_byte_len, self.tag_location) csum = _compute_csum(rec) ba = bytearray(rec) ba[4] = csum return bytes(ba) def new(self, tag_ident, tag_serial=0): # type: (int, int) -> None ''' Create a new UDF Descriptor Tag. Parameters: tag_ident - The tag identifier number for this tag. tag_serial - The tag serial number for this tag. Returns: Nothing ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Tag already initialized') self.tag_ident = tag_ident self.desc_version = 2 self.tag_serial_number = tag_serial self.tag_location = 0 # This will be set later. self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFTag): return NotImplemented return self.tag_ident == other.tag_ident and \ self.desc_version == other.desc_version and \ self.tag_serial_number == other.tag_serial_number and \ self.tag_location == other.tag_location and \ self.desc_crc_length == other.desc_crc_length class UDFAnchorVolumeStructure(object): ''' A class representing a UDF Anchor Volume Structure (ECMA-167, Part 3, 10.2). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'main_vd', 'reserve_vd', 'desc_tag') FMT = '=16s8s8s480s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Anchor Volume Structure. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('Anchor Volume Structure already initialized') (tag_unused, main_vd, reserve_vd, reserved_unused) = struct.unpack_from(self.FMT, data, 0) self.main_vd = UDFExtentAD() self.main_vd.parse(main_vd) self.reserve_vd = UDFExtentAD() self.reserve_vd.parse(reserve_vd) self.desc_tag = desc_tag self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Anchor Volume Structure. Parameters: None. Returns: A string representing this UDF Anchor Volume Structure. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Anchor Volume Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.main_vd.record(), self.reserve_vd.record(), b'\x00' * 480)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Anchor Volume Structure. Parameters: None. Returns: Integer extent location of this UDF Anchor Volume Structure. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Anchor Volume Structure not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Anchor Volume Structure. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Anchor Volume Structure already initialized') self.desc_tag = UDFTag() self.desc_tag.new(2) # FIXME: let the user set serial_number self.main_vd = UDFExtentAD() self.main_vd.new(32768, 0) # The location will get set later. self.reserve_vd = UDFExtentAD() self.reserve_vd.new(32768, 0) # The location will get set later. self._initialized = True def set_extent_location(self, new_location, main_vd_extent, reserve_vd_extent): # type: (int, int, int) -> None ''' Set a new location for this Anchor Volume Structure. Parameters: new_location - The new extent that this Anchor Volume Structure should be located at. main_vd_extent - The extent containing the main Volume Descriptors. reserve_vd_extent - The extent containing the reserve Volume Descriptors. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Anchor Volume Structure not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location self.main_vd.extent_location = main_vd_extent self.reserve_vd.extent_location = reserve_vd_extent def __ne__(self, other): # type: (object) -> bool return not self == other def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFAnchorVolumeStructure): return NotImplemented return self.main_vd.extent_location == other.main_vd.extent_location and self.reserve_vd.extent_location == other.reserve_vd.extent_location class UDFVolumeDescriptorPointer(object): ''' A class representing a UDF Volume Descriptor Pointer (ECMA-167, Part 3, 10.3). ''' __slots__ = ('initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_seqnum', 'next_vol_desc_seq_extent', 'desc_tag') FMT = '<16sL8s484s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self.initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Volume Descriptor Pointer. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Volume Descriptor Pointer already initialized') (tag_unused, self.vol_seqnum, next_vol_extent, reserved_unused) = struct.unpack_from(self.FMT, data, 0) self.next_vol_desc_seq_extent = UDFExtentAD() self.next_vol_desc_seq_extent.parse(next_vol_extent) self.desc_tag = desc_tag self.orig_extent_loc = extent self.initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Volume Descriptor Pointer. Parameters: None. Returns: A string representing this UDF Volume Descriptor Pointer. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Volume Descriptor Pointer not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_seqnum, self.next_vol_desc_seq_extent.record(), b'\x00' * 484)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Volume Descriptor Pointer. Parameters: None. Returns: Integer extent location of this UDF Volume Descriptor Pointer. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Volume Descriptor Pointer not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Volume Descriptor Pointer. Parameters: None. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Volume Descriptor Pointer already initialized') self.desc_tag = UDFTag() self.desc_tag.new(3) # FIXME: let the user set serial_number self.vol_seqnum = 0 # FIXME: let the user set this self.next_vol_desc_seq_extent = UDFExtentAD() self.next_vol_desc_seq_extent.new(0, 0) # FIXME: let the user set this self.new_extent_loc = 0 # This will be set later self.initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the new location for this UDF Volume Descriptor Pointer. Parameters: new_location - The new extent this UDF Volume Descriptor Pointer should be located at. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Volume Descriptor Pointer not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location class UDFTimestamp(object): ''' A class representing a UDF timestamp (ECMA-167, Part 1, 7.3). ''' __slots__ = ('_initialized', 'year', 'month', 'day', 'hour', 'minute', 'second', 'centiseconds', 'hundreds_microseconds', 'microseconds', 'timetype', 'tz') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Timestamp. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Timestamp already initialized') (tz, timetype, self.year, self.month, self.day, self.hour, self.minute, self.second, self.centiseconds, self.hundreds_microseconds, self.microseconds) = struct.unpack_from(self.FMT, data, 0) self.timetype = timetype >> 4 def twos_comp(val, bits): # type: (int, int) -> int ''' Compute the 2's complement of int value val ''' if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 val = val - (1 << bits) # compute negative value return val # return positive value as is self.tz = twos_comp(((timetype & 0xf) << 8) | tz, 12) if self.tz < -1440 or self.tz > 1440: if self.tz != -2047: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF timezone') if self.year < 1 or self.year > 9999: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF year') if self.month < 1 or self.month > 12: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF month') if self.day < 1 or self.day > 31: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF day') if self.hour < 0 or self.hour > 23: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF hour') if self.minute < 0 or self.minute > 59: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF minute') if self.second < 0 or self.second > 59: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF second') self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Timestamp. Parameters: None. Returns: A string representing this UDF Timestamp. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Timestamp not initialized') tmp = ((1 << 16) - 1) & self.tz newtz = tmp & 0xff newtimetype = ((tmp >> 8) & 0x0f) | (self.timetype << 4) return struct.pack(self.FMT, newtz, newtimetype, self.year, self.month, self.day, self.hour, self.minute, self.second, self.centiseconds, self.hundreds_microseconds, self.microseconds) def new(self): # type: () -> None ''' Create a new UDF Timestamp. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Timestamp already initialized') tm = time.time() local = time.localtime(tm) self.tz = utils.gmtoffset_from_tm(tm, local) # FIXME: for the timetype, 0 is UTC, 1 is local, 2 is 'agreement'. # let the user set this. self.timetype = 1 self.year = local.tm_year self.month = local.tm_mon self.day = local.tm_mon self.hour = local.tm_hour self.minute = local.tm_min self.second = local.tm_sec self.centiseconds = 0 self.hundreds_microseconds = 0 self.microseconds = 0 self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFTimestamp): return NotImplemented return self.year == other.year and self.month == other.month and \ self.day == other.day and self.hour == other.hour and \ self.minute == other.minute and self.second == other.second and \ self.centiseconds == other.centiseconds and \ self.hundreds_microseconds == other.hundreds_microseconds and \ self.microseconds == other.microseconds and \ self.timetype == other.timetype and self.tz == other.tz class UDFEntityID(object): ''' A class representing a UDF Entity ID (ECMA-167, Part 1, 7.4). ''' __slots__ = ('_initialized', 'flags', 'identifier', 'suffix') FMT = '=B23s8s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Entity ID. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Entity ID already initialized') (self.flags, self.identifier, self.suffix) = struct.unpack_from(self.FMT, data, 0) if self.flags > 3: raise pycdlibexception.PyCdlibInvalidISO('UDF Entity ID flags must be between 0 and 3') self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Entity ID. Parameters: None. Returns: A string representing this UDF Entity ID. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Entity ID not initialized') return struct.pack(self.FMT, self.flags, self.identifier, self.suffix) def new(self, flags, identifier=b'', suffix=b''): # type: (int, bytes, bytes) -> None ''' Create a new UDF Entity ID. Parameters: flags - The flags to set for this Entity ID. identifier - The identifier to set for this Entity ID. suffix - The suffix to set for this Entity ID. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Entity ID already initialized') if flags > 3: raise pycdlibexception.PyCdlibInvalidInput('UDF Entity ID flags must be between 0 and 3') if len(identifier) > 23: raise pycdlibexception.PyCdlibInvalidInput('UDF Entity ID identifier must be less than 23 characters') if len(suffix) > 8: raise pycdlibexception.PyCdlibInvalidInput('UDF Entity ID suffix must be less than 8 characters') self.flags = flags self.identifier = identifier + b'\x00' * (23 - len(identifier)) self.suffix = suffix + b'\x00' * (8 - len(suffix)) self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFEntityID): return NotImplemented return self.flags == other.flags and self.identifier == other.identifier and self.suffix == other.suffix class UDFCharspec(object): ''' A class representing a UDF charspec (ECMA-167, Part 1, 7.2.1). ''' __slots__ = ('_initialized', 'set_type', 'set_information') FMT = '=B63s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Charspec. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Charspec already initialized') (self.set_type, self.set_information) = struct.unpack_from(self.FMT, data, 0) if self.set_type > 8: raise pycdlibexception.PyCdlibInvalidISO('Invalid charset parsed; only 0-8 supported') self._initialized = True def new(self, set_type, set_information): # type: (int, bytes) -> None ''' Create a new UDF Charspc. Parameters: set_type - The integer set type. Must be between 0 and 8. set_information - Additional set information. Must be less than 64 bytes. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Charspec already initialized') if set_type > 8: raise pycdlibexception.PyCdlibInvalidInput('Invalid charset specified; only 0-8 supported') if len(set_information) > 63: raise pycdlibexception.PyCdlibInvalidInput('Invalid charset information; exceeds maximum size of 63') self.set_type = set_type self.set_information = set_information + b'\x00' * (63 - len(set_information)) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Charspec. Parameters: None. Returns: A string representing this UDF Charspec. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Charspec not initialized') return struct.pack(self.FMT, self.set_type, self.set_information) def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFCharspec): return NotImplemented return self.set_type == other.set_type and self.set_information == other.set_information class UDFExtentAD(object): ''' A class representing a UDF Extent Descriptor (ECMA-167, Part 3, 7.1). ''' __slots__ = ('_initialized', 'extent_length', 'extent_location') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Extent AD. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extent descriptor already initialized') (self.extent_length, self.extent_location) = struct.unpack_from(self.FMT, data, 0) if self.extent_length >= 0x3fffffff: raise pycdlibexception.PyCdlibInvalidISO('UDF Extent descriptor length must be less than 0x3fffffff') self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Extent AD. Parameters: None. Returns: A string representing this UDF Extent AD. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extent AD not initialized') return struct.pack(self.FMT, self.extent_length, self.extent_location) def new(self, length, blocknum): # type: (int, int) -> None ''' Create a new UDF Short AD. Parameters: length - The length of the data in the allocation. blocknum - The logical block number the allocation starts at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extent AD already initialized') if length >= 0x3fffffff: raise pycdlibexception.PyCdlibInternalError('UDF Extent AD length must be less than 0x3fffffff') self.extent_length = length self.extent_location = blocknum self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFExtentAD): return NotImplemented return self.extent_length == other.extent_length and self.extent_location == other.extent_location class UDFPrimaryVolumeDescriptor(object): ''' A class representing a UDF Primary Volume Descriptor (ECMA-167, Part 3, 10.1). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_desc_seqnum', 'desc_num', 'vol_ident', 'vol_set_ident', 'desc_char_set', 'explanatory_char_set', 'vol_abstract', 'vol_copyright', 'implementation_use', 'predecessor_vol_desc_location', 'desc_tag', 'recording_date', 'app_ident', 'impl_ident', 'max_interchange_level', 'interchange_level', 'flags') FMT = '<16sLL32sHHHHLL128s64s64s8s8s32s12s32s64sLH22s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Primary Volume Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Primary Volume Descriptor already initialized') (tag_unused, self.vol_desc_seqnum, self.desc_num, self.vol_ident, vol_seqnum, max_vol_seqnum, self.interchange_level, self.max_interchange_level, char_set_list, max_char_set_list, self.vol_set_ident, desc_char_set, explanatory_char_set, vol_abstract, vol_copyright, app_ident, recording_date, impl_ident, self.implementation_use, self.predecessor_vol_desc_location, self.flags, reserved) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag if vol_seqnum != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if max_vol_seqnum != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if self.interchange_level not in (2, 3): raise pycdlibexception.PyCdlibInvalidISO('Unsupported interchange level (only 2 and 3 supported)') if char_set_list != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if max_char_set_list != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if self.flags not in (0, 1): raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF flags') if reserved != b'\x00' * 22: raise pycdlibexception.PyCdlibInvalidISO('UDF Primary Volume Descriptor reserved data not 0') self.desc_char_set = UDFCharspec() self.desc_char_set.parse(desc_char_set) self.explanatory_char_set = UDFCharspec() self.explanatory_char_set.parse(explanatory_char_set) self.vol_abstract = UDFExtentAD() self.vol_abstract.parse(vol_abstract) self.vol_copyright = UDFExtentAD() self.vol_copyright.parse(vol_copyright) self.recording_date = UDFTimestamp() self.recording_date.parse(recording_date) self.app_ident = UDFEntityID() self.app_ident.parse(app_ident) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Primary Volume Descriptor. Parameters: None. Returns: A string representing this UDF Primary Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Primary Volume Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_desc_seqnum, self.desc_num, self.vol_ident, 1, 1, self.interchange_level, self.max_interchange_level, 1, 1, self.vol_set_ident, self.desc_char_set.record(), self.explanatory_char_set.record(), self.vol_abstract.record(), self.vol_copyright.record(), self.app_ident.record(), self.recording_date.record(), self.impl_ident.record(), self.implementation_use, self.predecessor_vol_desc_location, self.flags, b'\x00' * 22)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Primary Volume Descriptor. Parameters: None. Returns: Integer extent location of this UDF Primary Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Primary Volume Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Primary Volume Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Primary Volume Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(1) # FIXME: let the user set serial_number self.vol_desc_seqnum = 0 # FIXME: let the user set this self.desc_num = 0 # FIXME: let the user set this self.vol_ident = _ostaunicode_zero_pad('CDROM', 32) # According to UDF 2.60, 2.2.2.5, the VolumeSetIdentifier should have # at least the first 16 characters be a unique value. Further, the # first 8 bytes of that should be a time value in ASCII hexadecimal # representation. To make it truly unique, we use that time plus a # random value, all ASCII encoded. unique = format(int(time.time()), '08x') + format(random.getrandbits(26), '08x') self.vol_set_ident = _ostaunicode_zero_pad(unique, 128) self.desc_char_set = UDFCharspec() self.desc_char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.explanatory_char_set = UDFCharspec() self.explanatory_char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.vol_abstract = UDFExtentAD() self.vol_abstract.new(0, 0) # FIXME: let the user set this self.vol_copyright = UDFExtentAD() self.vol_copyright.new(0, 0) # FIXME: let the user set this self.app_ident = UDFEntityID() self.app_ident.new(0) self.recording_date = UDFTimestamp() self.recording_date.new() self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib') self.implementation_use = b'\x00' * 64 # FIXME: let the user set this self.predecessor_vol_desc_location = 0 # FIXME: let the user set this self.interchange_level = 2 self.max_interchange_level = 2 self.flags = 0 self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the new location for this UDF Primary Volume Descriptor. Parameters: new_location - The extent that this Primary Volume Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Primary Volume Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFPrimaryVolumeDescriptor): return NotImplemented return self.vol_desc_seqnum == other.vol_desc_seqnum and \ self.desc_num == other.desc_num and \ self.vol_ident == other.vol_ident and \ self.vol_set_ident == other.vol_set_ident and \ self.desc_char_set == other.desc_char_set and \ self.explanatory_char_set == other.explanatory_char_set and \ self.vol_abstract == other.vol_abstract and \ self.vol_copyright == other.vol_copyright and \ self.implementation_use == other.implementation_use and \ self.predecessor_vol_desc_location == other.predecessor_vol_desc_location and \ self.desc_tag == other.desc_tag and \ self.recording_date == other.recording_date and \ self.app_ident == other.app_ident and \ self.impl_ident == other.impl_ident and \ self.max_interchange_level == other.max_interchange_level and \ self.interchange_level == other.interchange_level and \ self.flags == other.flags class UDFImplementationUseVolumeDescriptorImplementationUse(object): ''' A class representing the Implementation Use field of the Implementation Use Volume Descriptor. ''' __slots__ = ('_initialized', 'char_set', 'log_vol_ident', 'lv_info1', 'lv_info2', 'lv_info3', 'impl_ident', 'impl_use') FMT = '=64s128s36s36s36s32s128s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Implementation Use Volume Descriptor Implementation Use field. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor Implementation Use field already initialized') (char_set, self.log_vol_ident, self.lv_info1, self.lv_info2, self.lv_info3, impl_ident, self.impl_use) = struct.unpack_from(self.FMT, data, 0) self.char_set = UDFCharspec() self.char_set.parse(char_set) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Implementation Use Volume Descriptor Implementation Use field. Parameters: None. Returns: A string representing this UDF Implementation Use Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor Implementation Use field not initialized') return struct.pack(self.FMT, self.char_set.record(), self.log_vol_ident, self.lv_info1, self.lv_info2, self.lv_info3, self.impl_ident.record(), self.impl_use) def new(self): # type: () -> None ''' Create a new UDF Implementation Use Volume Descriptor Implementation Use field. Parameters: None: Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor Implementation Use field already initialized') self.char_set = UDFCharspec() self.char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.log_vol_ident = _ostaunicode_zero_pad('CDROM', 128) self.lv_info1 = b'\x00' * 36 self.lv_info2 = b'\x00' * 36 self.lv_info3 = b'\x00' * 36 self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib', b'') self.impl_use = b'\x00' * 128 self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFImplementationUseVolumeDescriptorImplementationUse): return NotImplemented return self.char_set == other.char_set and \ self.log_vol_ident == other.log_vol_ident and \ self.lv_info1 == other.lv_info1 and \ self.lv_info2 == other.lv_info2 and \ self.lv_info3 == other.lv_info3 and \ self.impl_ident == other.impl_ident and \ self.impl_use == other.impl_use class UDFImplementationUseVolumeDescriptor(object): ''' A class representing a UDF Implementation Use Volume Structure (ECMA-167, Part 3, 10.4). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_desc_seqnum', 'impl_use', 'desc_tag', 'impl_ident') FMT = '<16sL32s460s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Implementation Use Volume Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor already initialized') (tag_unused, self.vol_desc_seqnum, impl_ident, impl_use) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) if self.impl_ident.identifier[:12] != b'*UDF LV Info': raise pycdlibexception.PyCdlibInvalidISO("Implementation Use Identifier not '*UDF LV Info'") self.impl_use = UDFImplementationUseVolumeDescriptorImplementationUse() self.impl_use.parse(impl_use) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Implementation Use Volume Descriptor. Parameters: None. Returns: A string representing this UDF Implementation Use Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_desc_seqnum, self.impl_ident.record(), self.impl_use.record())[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Implementation Use Volume Descriptor. Parameters: None. Returns: Integer extent location of this UDF Implementation Use Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Implementation Use Volume Descriptor. Parameters: None: Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(4) # FIXME: let the user set serial_number self.vol_desc_seqnum = 1 self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*UDF LV Info', b'\x02\x01') self.impl_use = UDFImplementationUseVolumeDescriptorImplementationUse() self.impl_use.new() self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the new location for this UDF Implementation Use Volume Descriptor. Parameters: new_location - The new extent this UDF Implementation Use Volume Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Implementation Use Volume Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFImplementationUseVolumeDescriptor): return NotImplemented return self.vol_desc_seqnum == other.vol_desc_seqnum and \ self.impl_use == other.impl_use and \ self.desc_tag == other.desc_tag and \ self.impl_ident == other.impl_ident class UDFPartitionHeaderDescriptor(object): ''' A class representing a UDF Partition Header Descriptor. ''' __slots__ = ('_initialized', 'unalloc_space_table', 'unalloc_space_bitmap', 'partition_integrity_table', 'freed_space_table', 'freed_space_bitmap') FMT = '=8s8s8s8s8s88s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Partition Header Descriptor. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Header Descriptor already initialized') (unalloc_space_table, unalloc_space_bitmap, partition_integrity_table, freed_space_table, freed_space_bitmap, reserved_unused) = struct.unpack_from(self.FMT, data, 0) self.unalloc_space_table = UDFShortAD() self.unalloc_space_table.parse(unalloc_space_table) self.unalloc_space_bitmap = UDFShortAD() self.unalloc_space_bitmap.parse(unalloc_space_bitmap) self.partition_integrity_table = UDFShortAD() self.partition_integrity_table.parse(partition_integrity_table) self.freed_space_table = UDFShortAD() self.freed_space_table.parse(freed_space_table) self.freed_space_bitmap = UDFShortAD() self.freed_space_bitmap.parse(freed_space_bitmap) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Partition Header Descriptor. Parameters: None. Returns: A string representing this UDF Partition Header Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Header Descriptor not initialized') return struct.pack(self.FMT, self.unalloc_space_table.record(), self.unalloc_space_bitmap.record(), self.partition_integrity_table.record(), self.freed_space_table.record(), self.freed_space_bitmap.record(), b'\x00' * 88) def new(self): # type: () -> None ''' Create a new UDF Partition Header Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Header Descriptor already initialized') self.unalloc_space_table = UDFShortAD() self.unalloc_space_table.new(0) self.unalloc_space_bitmap = UDFShortAD() self.unalloc_space_bitmap.new(0) self.partition_integrity_table = UDFShortAD() self.partition_integrity_table.new(0) self.freed_space_table = UDFShortAD() self.freed_space_table.new(0) self.freed_space_bitmap = UDFShortAD() self.freed_space_bitmap.new(0) self._initialized = True def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFPartitionHeaderDescriptor): return NotImplemented return self.unalloc_space_table == other.unalloc_space_table and self.unalloc_space_bitmap == other.unalloc_space_bitmap and self.partition_integrity_table == other.partition_integrity_table and self.freed_space_table == other.freed_space_table and self.freed_space_bitmap == other.freed_space_bitmap class UDFPartitionVolumeDescriptor(object): ''' A class representing a UDF Partition Volume Structure (ECMA-167, Part 3, 10.5). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_desc_seqnum', 'part_flags', 'part_num', 'access_type', 'part_start_location', 'part_length', 'implementation_use', 'desc_tag', 'part_contents', 'impl_ident', 'part_contents_use') FMT = '<16sLHH32s128sLLL32s128s156s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Partition Volume Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor already initialized') (tag_unused, self.vol_desc_seqnum, self.part_flags, self.part_num, part_contents, part_contents_use, self.access_type, self.part_start_location, self.part_length, impl_ident, self.implementation_use, reserved_unused) = struct.unpack_from(self.FMT, data, 0) if self.part_flags not in (0, 1): raise pycdlibexception.PyCdlibInvalidISO('Invalid partition flags') self.desc_tag = desc_tag self.part_contents = UDFEntityID() self.part_contents.parse(part_contents) if self.part_contents.identifier[:6] not in (b'+FDC01', b'+CD001', b'+CDW02', b'+NSR02', b'+NSR03'): raise pycdlibexception.PyCdlibInvalidISO("Partition Contents Identifier not '+FDC01', '+CD001', '+CDW02', '+NSR02', or '+NSR03'") if self.access_type > 0x1f: raise pycdlibexception.PyCdlibInvalidISO('Invalid UDF partition access type') self.part_contents_use = UDFPartitionHeaderDescriptor() self.part_contents_use.parse(part_contents_use) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Partition Volume Descriptor. Parameters: None. Returns: A string representing this UDF Partition Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_desc_seqnum, self.part_flags, self.part_num, self.part_contents.record(), self.part_contents_use.record(), self.access_type, self.part_start_location, self.part_length, self.impl_ident.record(), self.implementation_use, b'\x00' * 156)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Partition Volume Descriptor. Parameters: None. Returns: Integer extent location of this UDF Partition Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self, version): # type: (int) -> None ''' Create a new UDF Partition Volume Descriptor. Parameters: version - The version of to make this partition; must be 2 or 3. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(5) # FIXME: let the user set serial_number self.vol_desc_seqnum = 2 self.part_flags = 1 # FIXME: how should we set this? self.part_num = 0 # FIXME: how should we set this? self.part_contents = UDFEntityID() if version == 2: self.part_contents.new(2, b'+NSR02') elif version == 3: self.part_contents.new(2, b'+NSR03') else: raise pycdlibexception.PyCdlibInternalError('Invalid NSR version requested') self.part_contents_use = UDFPartitionHeaderDescriptor() self.part_contents_use.new() self.access_type = 1 self.part_start_location = 0 # This will get set later self.part_length = 3 # This will get set later self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib') self.implementation_use = b'\x00' * 128 # FIXME: let the user set this self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the location of this UDF Partition Volume Descriptor. Parameters: new_location - The new extent this UDF Partition Volume Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location def set_start_location(self, new_location): # type: (int) -> None ''' Set the location of the start of the UDF partition. Parameters: new_location - The new extent the UDF partition should start at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Volume Descriptor not initialized') self.part_start_location = new_location def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFPartitionVolumeDescriptor): return NotImplemented return self.vol_desc_seqnum == other.vol_desc_seqnum and \ self.part_flags == other.part_flags and \ self.part_num == other.part_num and \ self.access_type == other.access_type and \ self.part_start_location == other.part_start_location and \ self.part_length == other.part_length and \ self.implementation_use == other.implementation_use and \ self.desc_tag == other.desc_tag and \ self.part_contents == other.part_contents and \ self.impl_ident == other.impl_ident and \ self.part_contents_use == other.part_contents_use class UDFType0PartitionMap(object): ''' A class representing a UDF Type 0 Partition Map (ECMA-167, Part 3, 10.7). ''' __slots__ = ('_initialized', 'data') FMT = '=BB' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Type 0 Partition Map. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 0 Partition Map already initialized') (map_type, map_length) = struct.unpack_from(self.FMT, data, 0) if map_type != 0: raise pycdlibexception.PyCdlibInvalidISO('UDF Type 0 Partition Map type is not 0') if map_length != len(data): raise pycdlibexception.PyCdlibInvalidISO('UDF Type 0 Partition Map length does not equal data length') self.data = data[2:] self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Type 0 Partition Map. Parameters: None. Returns: A string representing this UDF Type 0 Partition Map. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 0 Partition Map not initialized') return struct.pack(self.FMT, 0, 2 + len(self.data)) + self.data def new(self): # type: () -> None ''' Create a new UDF Type 0 Partition Map. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 0 Partition Map already initialized') self.data = b'' # FIXME: let the user set this self._initialized = True class UDFType1PartitionMap(object): ''' A class representing a UDF Type 1 Partition Map (ECMA-167, Part 3, 10.7). ''' __slots__ = ('_initialized', 'part_num', 'vol_seqnum') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Type 1 Partition Map. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 1 Partition Map already initialized') (map_type, map_length, self.vol_seqnum, self.part_num) = struct.unpack_from(self.FMT, data, 0) if map_type != 1: raise pycdlibexception.PyCdlibInvalidISO('UDF Type 1 Partition Map type is not 1') if map_length != 6: raise pycdlibexception.PyCdlibInvalidISO('UDF Type 1 Partition Map length is not 6') self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Type 1 Partition Map. Parameters: None. Returns: A string representing this UDF Type 1 Partition Map. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 1 Partition Map not initialized') return struct.pack(self.FMT, 1, 6, self.vol_seqnum, self.part_num) def new(self): # type: () -> None ''' Create a new UDF Type 1 Partition Map. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 1 Partition Map already initialized') self.part_num = 0 # FIXME: let the user set this self.vol_seqnum = 1 # FIXME: let the user set this self._initialized = True class UDFType2PartitionMap(object): ''' A class representing a UDF Type 2 Partition Map (ECMA-167, Part 3, 10.7). ''' __slots__ = ('_initialized', 'part_ident') FMT = '=BB62s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Type 2 Partition Map. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 2 Partition Map already initialized') (map_type, map_length, self.part_ident) = struct.unpack_from(self.FMT, data, 0) if map_type != 2: raise pycdlibexception.PyCdlibInvalidISO('UDF Type 2 Partition Map type is not 2') if map_length != 64: raise pycdlibexception.PyCdlibInvalidISO('UDF Type 2 Partition Map length is not 64') self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Type 2 Partition Map. Parameters: None. Returns: A string representing this UDF Type 2 Partition Map. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 2 Partition Map not initialized') return struct.pack(self.FMT, 2, 64, self.part_ident) def new(self): # type: () -> None ''' Create a new UDF Partition Map. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Type 2 Partition Map already initialized') self.part_ident = b'\x00' * 62 # FIXME: let the user set this self._initialized = True class UDFExtendedAD(object): ''' A class representing a UDF Extended Allocation Descriptor (ECMA-167, Part 4, 14.14.3). ''' __slots__ = ('_initialized', 'extent_length', 'recorded_length', 'information_length', 'extent_location', 'impl_use') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parsed the passed in data into a UDF Extended Allocation Descriptor. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Allocation descriptor already initialized') (self.extent_length, self.recorded_length, self.information_length, extent_location, self.impl_use) = struct.unpack_from(self.FMT, data, 0) self.extent_location = UDFLBAddr() self.extent_location.parse(extent_location) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Extended Allocation Descriptor. Parameters: None. Returns: A string representing this UDF Extended Allocation Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Allocation Descriptor not initialized') return struct.pack(self.FMT, self.extent_length, self.recorded_length, self.information_length, self.extent_location.record(), self.impl_use) def new(self): # type: () -> None ''' Create a new UDF Extended AD. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Allocation Descriptor already initialized') self.extent_length = 0 # FIXME: let the user set this self.recorded_length = 0 # FIXME: let the user set this self.information_length = 0 # FIXME: let the user set this self.extent_location = UDFLBAddr() self.extent_location.new(0) self.impl_use = b'\x00\x00' self._initialized = True class UDFShortAD(object): ''' A class representing a UDF Short Allocation Descriptor (ECMA-167, Part 4, 14.14.1). ''' __slots__ = ('_initialized', 'extent_length', 'log_block_num', 'offset', 'extent_type') FMT = ' None self.offset = 0 self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Short AD. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Short Allocation descriptor already initialized') (self.extent_length, self.log_block_num) = struct.unpack_from(self.FMT, data, 0) self.extent_length = self.extent_length & 0x3FFFFFFF self.extent_type = (self.extent_length & 0xc0000000) >> 30 self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Short AD. Parameters: None. Returns: A string representing this UDF Short AD. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Short AD not initialized') length = self.extent_length | (self.extent_type << 30) return struct.pack(self.FMT, length, self.log_block_num) def new(self, length): # type: (int) -> None ''' Create a new UDF Short AD. Parameters: length - The length of the data in the allocation. blocknum - The logical block number the allocation starts at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Short AD already initialized') if length > 0x3fffffff: raise pycdlibexception.PyCdlibInternalError('UDF Short AD length must be less than or equal to 0x3fffffff') self.extent_length = length self.extent_type = 0 # FIXME: let the user set this self.log_block_num = 0 # this will get set later self._initialized = True def set_extent_location(self, new_location, tag_location): # pylint: disable=unused-argument # type: (int, int) -> None ''' Set the location fields of this UDF Short AD. Parameters: new_location - The new relative extent that this UDF Short AD references. tag_location - The new absolute extent that this UDF Short AD references. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Short AD not initialized') self.log_block_num = tag_location def length(self): # pylint: disable=no-self-use # type: () -> int ''' Method to return the length of the UDF Short Allocation Descriptor. Parameters: None. Returns: The length of this descriptor in bytes. ''' return 8 def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFShortAD): return NotImplemented return self.extent_length == other.extent_length and self.log_block_num == other.log_block_num class UDFLongAD(object): ''' A class representing a UDF Long Allocation Descriptor (ECMA-167, Part 4, 14.14.2. ''' __slots__ = ('_initialized', 'extent_length', 'log_block_num', 'part_ref_num', 'impl_use', 'offset') FMT = ' None self.offset = 0 self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Long AD. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Long Allocation descriptor already initialized') (self.extent_length, self.log_block_num, self.part_ref_num, self.impl_use) = struct.unpack_from(self.FMT, data, 0) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Long AD. Parameters: None. Returns: A string representing this UDF Long AD. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Long AD not initialized') return struct.pack(self.FMT, self.extent_length, self.log_block_num, self.part_ref_num, self.impl_use) def new(self, length, blocknum): # type: (int, int) -> None ''' Create a new UDF Long AD. Parameters: length - The length of the data in the allocation. blocknum - The logical block number the allocation starts at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Long AD already initialized') self.extent_length = length self.log_block_num = blocknum self.part_ref_num = 0 # FIXME: let the user set this self.impl_use = b'\x00' * 6 self._initialized = True def set_extent_location(self, new_location, tag_location): # type: (int, int) -> None ''' Set the location fields of this UDF Long AD. Parameters: new_location - The new relative extent that this UDF Long AD references. tag_location - The new absolute extent that this UDF Long AD references. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Long AD not initialized') self.log_block_num = tag_location self.impl_use = b'\x00\x00' + struct.pack(' int ''' Method to return the length of the UDF Long Allocation Descriptor. Parameters: None. Returns: The length of this descriptor in bytes. ''' return 16 def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFLongAD): return NotImplemented return self.extent_length == other.extent_length and \ self.log_block_num == other.log_block_num and \ self.part_ref_num == other.part_ref_num and \ self.impl_use == other.impl_use class UDFInlineAD(object): ''' A class representing a UDF Inline Allocation Descriptor. This isn't explicitly defined in the specification, but is a convenient structure to use for ICBTag flags type 3 Allocation Descriptors. ''' __slots__ = ('_initialized', 'extent_length', 'log_block_num', 'offset') def __init__(self): # type: () -> None self._initialized = False def parse(self, extent_length, log_block_num, offset): # type: (int, int, int) -> None ''' Create a new UDF Inline AD from the given data. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Inline Allocation Descriptor already initialized') self.extent_length = extent_length self.log_block_num = log_block_num self.offset = offset self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Inline AD. Parameters: None. Returns: A string representing this UDF Inline AD. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Inline AD not initialized') return b'' def new(self, extent_length, log_block_num, offset): # type: (int, int, int) -> None ''' Create a new UDF Inline AD. Parameters: extent_length - The length of the data in the allocation. log_block_num - The logical block number the allocation starts at. offset - The offset the allocation starts at. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Inline AD already initialized') self.extent_length = extent_length self.log_block_num = log_block_num self.offset = offset self._initialized = True def set_extent_location(self, new_location, tag_location): # pylint: disable=unused-argument # type: (int, int) -> None ''' Set the location fields of this UDF Inline AD. Parameters: new_location - The new relative extent that this UDF Inline AD references. tag_location - The new absolute extent that this UDF Inline AD references. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Inline AD not initialized') self.log_block_num = tag_location def length(self): # type: () -> int ''' Method to return the length of the UDF Inline Allocation Descriptor. Parameters: None. Returns: The length of this descriptor in bytes. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Inline AD not initialized') return self.extent_length class UDFLogicalVolumeDescriptor(object): ''' A class representing a UDF Logical Volume Descriptor (ECMA-167, Part 3, 10.6). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_desc_seqnum', 'desc_char_set', 'logical_vol_ident', 'implementation_use', 'integrity_sequence', 'desc_tag', 'domain_ident', 'impl_ident', 'partition_maps', 'logical_volume_contents_use') FMT = '<16sL64s128sL32s16sLL32s128s8s72s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self.partition_maps = [] # type: List[Union[UDFType0PartitionMap, UDFType1PartitionMap, UDFType2PartitionMap]] self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Logical Volume Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor already initialized') (tag_unused, self.vol_desc_seqnum, desc_char_set, self.logical_vol_ident, logical_block_size, domain_ident, logical_volume_contents_use, map_table_length, num_partition_maps, impl_ident, self.implementation_use, integrity_sequence, partition_maps) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.desc_char_set = UDFCharspec() self.desc_char_set.parse(desc_char_set) if logical_block_size != 2048: raise pycdlibexception.PyCdlibInvalidISO('Volume Descriptor block size is not 2048') self.domain_ident = UDFEntityID() self.domain_ident.parse(domain_ident) if self.domain_ident.identifier[:19] != b'*OSTA UDF Compliant': raise pycdlibexception.PyCdlibInvalidISO("Volume Descriptor Identifier not '*OSTA UDF Compliant'") if map_table_length >= len(partition_maps): raise pycdlibexception.PyCdlibInvalidISO('Map table length greater than size of partition map data; ISO corrupt') self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) self.integrity_sequence = UDFExtentAD() self.integrity_sequence.parse(integrity_sequence) offset = 0 map_table_length_left = map_table_length for p_unused in range(0, num_partition_maps): # The generic partition map starts with 1 byte for the type and # 1 byte for the length. (map_type, map_len) = struct.unpack_from('=BB', partition_maps, offset) if offset + map_len > len(partition_maps[offset:]): raise pycdlibexception.PyCdlibInvalidISO('Partition map goes beyond end of data, ISO corrupt') if offset + map_len > map_table_length_left: raise pycdlibexception.PyCdlibInvalidISO('Partition map goes beyond map_table_length left, ISO corrupt') if map_type == 0: partmap0 = UDFType0PartitionMap() partmap0.parse(partition_maps[offset:offset + map_len]) self.partition_maps.append(partmap0) elif map_type == 1: partmap1 = UDFType1PartitionMap() partmap1.parse(partition_maps[offset:offset + map_len]) self.partition_maps.append(partmap1) elif map_type == 2: partmap2 = UDFType2PartitionMap() partmap2.parse(partition_maps[offset:offset + map_len]) self.partition_maps.append(partmap2) else: raise pycdlibexception.PyCdlibInvalidISO('Unsupported partition map type') offset += map_len map_table_length_left -= map_len self.logical_volume_contents_use = UDFLongAD() self.logical_volume_contents_use.parse(logical_volume_contents_use) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Logical Volume Descriptor. Parameters: None. Returns: A string representing this UDF Logical Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor not initialized') all_partmaps = b'' for part in self.partition_maps: all_partmaps += part.record() partmap_pad = BytesIO() utils.zero_pad(partmap_pad, len(all_partmaps), 72) rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_desc_seqnum, self.desc_char_set.record(), self.logical_vol_ident, 2048, self.domain_ident.record(), self.logical_volume_contents_use.record(), len(all_partmaps), len(self.partition_maps), self.impl_ident.record(), self.implementation_use, self.integrity_sequence.record(), all_partmaps + partmap_pad.getvalue())[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Logical Volume Descriptor. Parameters: None. Returns: Integer extent location of this UDF Logical Volume Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Logical Volume Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(6) # FIXME: let the user set serial_number self.vol_desc_seqnum = 3 self.desc_char_set = UDFCharspec() self.desc_char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.logical_vol_ident = _ostaunicode_zero_pad('CDROM', 128) self.domain_ident = UDFEntityID() self.domain_ident.new(0, b'*OSTA UDF Compliant', b'\x02\x01\x03') self.logical_volume_contents_use = UDFLongAD() self.logical_volume_contents_use.new(4096, 0) self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib') self.implementation_use = b'\x00' * 128 # FIXME: let the user set this self.integrity_sequence = UDFExtentAD() self.integrity_sequence.new(4096, 0) # The location will get set later. self._initialized = True def add_partition_map(self, partmaptype): # type: (int) -> None ''' Add a new partition map to this UDF Logical Volume Descriptor. Parameters: partmaptype - Must be 0, 1, or 2. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor not initialized') partmap = None # type: Optional[Union[UDFType0PartitionMap, UDFType1PartitionMap, UDFType2PartitionMap]] if partmaptype == 0: partmap = UDFType0PartitionMap() elif partmaptype == 1: partmap = UDFType1PartitionMap() elif partmaptype == 2: partmap = UDFType2PartitionMap() else: raise pycdlibexception.PyCdlibInternalError('UDF Partition map type must be 0, 1, or 2') partmap.new() all_partmaps = b'' for part in self.partition_maps: all_partmaps += part.record() if len(all_partmaps) > 72: raise pycdlibexception.PyCdlibInternalError('Too many UDF partition maps') self.partition_maps.append(partmap) def set_extent_location(self, new_location): # type: (int) -> None ''' Set the location of this UDF Logical Volume Descriptor. Parameters: new_location - The new extent this UDF Logical Volume Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location def set_integrity_location(self, integrity_extent): # type: (int) -> None ''' Set the location of the UDF Integrity sequence that this descriptor references. Parameters: integrity_extent - The new extent that the UDF Integrity sequence should start at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Descriptor not initialized') self.integrity_sequence.extent_location = integrity_extent def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFLogicalVolumeDescriptor): return NotImplemented return self.vol_desc_seqnum == other.vol_desc_seqnum and \ self.desc_char_set == other.desc_char_set and \ self.logical_vol_ident == other.logical_vol_ident and \ self.implementation_use == other.implementation_use and \ self.integrity_sequence == other.integrity_sequence and \ self.desc_tag == other.desc_tag and \ self.domain_ident == other.domain_ident and \ self.impl_ident == other.impl_ident and \ self.logical_volume_contents_use == other.logical_volume_contents_use class UDFUnallocatedSpaceDescriptor(object): ''' A class representing a UDF Unallocated Space Descriptor (ECMA-167, Part 3, 10.8). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'vol_desc_seqnum', 'desc_tag', 'num_alloc_descriptors', 'alloc_descs') FMT = '<16sLL488s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self.alloc_descs = [] # type: List[UDFExtentAD] self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Unallocated Space Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Descriptor already initialized') (tag_unused, self.vol_desc_seqnum, self.num_alloc_descriptors, alloc_descs) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag if self.num_alloc_descriptors * 8 > len(alloc_descs): raise pycdlibexception.PyCdlibInvalidISO('Too many allocation descriptors') for num in range(0, self.num_alloc_descriptors): offset = num * 8 extent_ad = UDFExtentAD() extent_ad.parse(alloc_descs[offset:offset + 8]) self.alloc_descs.append(extent_ad) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Unallocated Space Descriptor. Parameters: None. Returns: A string representing this UDF Unallocated Space Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Descriptor not initialized') alloc_desc_bytes = b'' for desc in self.alloc_descs: alloc_desc_bytes += desc.record() alloc_desc_bytes += b'\x00' * (488 - len(alloc_desc_bytes)) rec = struct.pack(self.FMT, b'\x00' * 16, self.vol_desc_seqnum, self.num_alloc_descriptors, alloc_desc_bytes)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Unallocated Space Descriptor. Parameters: None. Returns: Integer extent location of this UDF Unallocated Space Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Unallocated Space Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(7) # FIXME: let the user set serial_number self.vol_desc_seqnum = 4 self.num_alloc_descriptors = 0 self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the location of this UDF Unallocated Space Descriptor. Parameters: new_location - The new extent this UDF Unallocated Space Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFUnallocatedSpaceDescriptor): return NotImplemented return self.vol_desc_seqnum == other.vol_desc_seqnum and \ self.desc_tag == other.desc_tag and \ self.num_alloc_descriptors == other.num_alloc_descriptors class UDFTerminatingDescriptor(object): ''' A class representing a UDF Terminating Descriptor (ECMA-167, Part 3, 10.9). ''' __slots__ = ('initialized', 'orig_extent_loc', 'new_extent_loc', 'desc_tag') FMT = '=16s496s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self.initialized = False def parse(self, extent, desc_tag): # type: (int, UDFTag) -> None ''' Parse the passed in data into a UDF Terminating Descriptor. Parameters: extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminating Descriptor already initialized') self.desc_tag = desc_tag self.orig_extent_loc = extent self.initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Terminating Descriptor. Parameters: None. Returns: A string representing this UDF Terminating Descriptor. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminating Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, b'\x00' * 496)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Terminating Descriptor. Parameters: None. Returns: Integer extent location of this UDF Terminating Descriptor. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminating Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Terminating Descriptor. Parameters: None. Returns: Nothing. ''' if self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminating Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(8) # FIXME: let the user set serial_number self.initialized = True def set_extent_location(self, new_location, tag_location=None): # type: (int, int) -> None ''' Set the location of this UDF Terminating Descriptor. Parameters: new_location - The new extent this UDF Terminating Descriptor should be located at. tag_location - The tag location to set for this UDF Terminator Descriptor. Returns: Nothing. ''' if not self.initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminating Descriptor not initialized') self.new_extent_loc = new_location if tag_location is None: tag_location = new_location self.desc_tag.tag_location = tag_location class UDFLogicalVolumeHeaderDescriptor(object): ''' A class representing a UDF Logical Volume Header Descriptor (ECMA-167, Part 4, 14.15). ''' __slots__ = ('_initialized', 'unique_id') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Logical Volume Header Descriptor. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Header Descriptor already initialized') (self.unique_id, reserved_unused) = struct.unpack_from(self.FMT, data, 0) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Logical Volume Header Descriptor. Parameters: None. Returns: A string representing this UDF Logical Volume Header Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Header Descriptor not initialized') return struct.pack(self.FMT, self.unique_id, b'\x00' * 24) def new(self): # type: () -> None ''' Create a new UDF Logical Volume Header Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Header Descriptor already initialized') self.unique_id = 261 self._initialized = True class UDFLogicalVolumeImplementationUse(object): ''' A class representing a UDF Logical Volume Implementation Use. ''' __slots__ = ('_initialized', 'num_files', 'num_dirs', 'min_udf_read_revision', 'min_udf_write_revision', 'max_udf_write_revision', 'impl_id', 'impl_use') FMT = '<32sLLHHH' def __init__(self): # type: () -> None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF Logical Volume Implementation Use. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Implementation Use already initialized') (impl_id, self.num_files, self.num_dirs, self.min_udf_read_revision, self.min_udf_write_revision, self.max_udf_write_revision) = struct.unpack_from(self.FMT, data, 0) self.impl_id = UDFEntityID() self.impl_id.parse(impl_id) self.impl_use = data[46:] self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Logical Volume Implementation Use. Parameters: None. Returns: A string representing this UDF Logical Volume Implementation Use. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Implementation Use not initialized') return struct.pack(self.FMT, self.impl_id.record(), self.num_files, self.num_dirs, self.min_udf_read_revision, self.min_udf_write_revision, self.max_udf_write_revision) + self.impl_use def new(self): # type: () -> None ''' Create a new UDF Logical Volume Implementation Use. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Implementation Use already initialized') self.impl_id = UDFEntityID() self.impl_id.new(0, b'*pycdlib') self.num_files = 0 self.num_dirs = 1 self.min_udf_read_revision = 258 self.min_udf_write_revision = 258 self.max_udf_write_revision = 258 self.impl_use = b'\x00' * 378 # FIXME: let the user set this self._initialized = True class UDFLogicalVolumeIntegrityDescriptor(object): ''' A class representing a UDF Logical Volume Integrity Descriptor (ECMA-167, Part 3, 10.10). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'length_impl_use', 'free_space_tables', 'size_tables', 'desc_tag', 'recording_date', 'logical_volume_contents_use', 'logical_volume_impl_use', 'next_integrity_extent', 'integrity_type', 'num_partitions') FMT = '<16s12sL8s32sLL432s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self.free_space_tables = [] # type: List[int] self.size_tables = [] # type: List[int] self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Logical Volume Integrity Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Integrity Descriptor already initialized') (tag_unused, recording_date, self.integrity_type, next_integrity_extent, logical_volume_contents_use, self.num_partitions, self.length_impl_use, end) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.recording_date = UDFTimestamp() self.recording_date.parse(recording_date) if self.integrity_type not in (0, 1): raise pycdlibexception.PyCdlibInvalidISO('Logical Volume Integrity Type not 0 or 1') self.next_integrity_extent = UDFExtentAD() self.next_integrity_extent.parse(next_integrity_extent) self.logical_volume_contents_use = UDFLogicalVolumeHeaderDescriptor() self.logical_volume_contents_use.parse(logical_volume_contents_use) end_offset = 0 for part_unused in range(0, self.num_partitions): free_space, = struct.unpack_from(' bytes ''' Generate the string representing this UDF Logical Volume Integrity Descriptor. Parameters: None. Returns: A string representing this UDF Logical Volume Integrity Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Integrity Descriptor not initialized') end = b'' for table in self.free_space_tables: end += struct.pack(' int ''' Get the extent location of this UDF Logical Volume Integrity Descriptor. Parameters: None. Returns: Integer extent location of this UDF Logical Volume Integrity Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Integrity Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF Logical Volume Integrity Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Integrity Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(9) # FIXME: let the user set serial_number self.recording_date = UDFTimestamp() self.recording_date.new() self.integrity_type = 1 # FIXME: let the user set this self.length_impl_use = 46 self.free_space_tables = [0] # FIXME: let the user set this self.size_tables = [3] # FIXME: let the user set this self.num_partitions = 1 # FIXME: let the user set this self.next_integrity_extent = UDFExtentAD() self.next_integrity_extent.new(0, 0) # FIXME: let the user set this self.logical_volume_contents_use = UDFLogicalVolumeHeaderDescriptor() self.logical_volume_contents_use.new() self.logical_volume_impl_use = UDFLogicalVolumeImplementationUse() self.logical_volume_impl_use.new() self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the location of this UDF Logical Volume Integrity Descriptor. Parameters: new_location - The new extent this UDF Logical Volume Integrity Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Logical Volume Integrity Descriptor not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = new_location class UDFFileSetDescriptor(object): ''' A class representing a UDF File Set Descriptor (ECMA-167, Part 4, 14.1). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'file_set_num', 'log_vol_char_set', 'log_vol_ident', 'file_set_char_set', 'file_set_ident', 'copyright_file_ident', 'abstract_file_ident', 'desc_tag', 'recording_date', 'domain_ident', 'root_dir_icb', 'next_extent', 'system_stream_dir_icb') FMT = '<16s12sHHLLLL64s128s64s32s32s32s16s32s16s16s32s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF File Set Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Set Descriptor already initialized') (tag_unused, recording_date, interchange_level, max_interchange_level, char_set_list, max_char_set_list, self.file_set_num, file_set_desc_num, log_vol_char_set, self.log_vol_ident, file_set_char_set, self.file_set_ident, self.copyright_file_ident, self.abstract_file_ident, root_dir_icb, domain_ident, next_extent, system_stream_dir_icb, reserved_unused) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.recording_date = UDFTimestamp() self.recording_date.parse(recording_date) if interchange_level != 3: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if max_interchange_level != 3: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if char_set_list != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if max_char_set_list != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') if file_set_desc_num != 0: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-Only disks are supported') self.log_vol_char_set = UDFCharspec() self.log_vol_char_set.parse(log_vol_char_set) self.file_set_char_set = UDFCharspec() self.file_set_char_set.parse(file_set_char_set) self.domain_ident = UDFEntityID() self.domain_ident.parse(domain_ident) if self.domain_ident.identifier[:19] != b'*OSTA UDF Compliant': raise pycdlibexception.PyCdlibInvalidISO("File Set Descriptor Identifier not '*OSTA UDF Compliant'") self.root_dir_icb = UDFLongAD() self.root_dir_icb.parse(root_dir_icb) self.next_extent = UDFLongAD() self.next_extent.parse(next_extent) self.system_stream_dir_icb = UDFLongAD() self.system_stream_dir_icb.parse(system_stream_dir_icb) self.orig_extent_loc = extent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF File Set Descriptor. Parameters: None. Returns: A string representing this UDF File Set Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Set Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.recording_date.record(), 3, 3, 1, 1, self.file_set_num, 0, self.log_vol_char_set.record(), self.log_vol_ident, self.file_set_char_set.record(), self.file_set_ident, self.copyright_file_ident, self.abstract_file_ident, self.root_dir_icb.record(), self.domain_ident.record(), self.next_extent.record(), self.system_stream_dir_icb.record(), b'\x00' * 32)[16:] return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF File Set Descriptor. Parameters: None. Returns: Integer extent location of this UDF File Set Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Set Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self): # type: () -> None ''' Create a new UDF File Set Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Set Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(256) # FIXME: let the user set serial_number self.recording_date = UDFTimestamp() self.recording_date.new() self.domain_ident = UDFEntityID() self.domain_ident.new(0, b'*OSTA UDF Compliant', b'\x02\x01\x03') self.root_dir_icb = UDFLongAD() self.root_dir_icb.new(2048, 2) self.file_set_num = 0 self.log_vol_char_set = UDFCharspec() self.log_vol_char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.log_vol_ident = _ostaunicode_zero_pad('CDROM', 128) self.file_set_char_set = UDFCharspec() self.file_set_char_set.new(0, b'OSTA Compressed Unicode') # FIXME: let the user set this self.file_set_ident = _ostaunicode_zero_pad('CDROM', 32) self.copyright_file_ident = b'\x00' * 32 # FIXME: let the user set this self.abstract_file_ident = b'\x00' * 32 # FIXME: let the user set this self.next_extent = UDFLongAD() self.next_extent.new(0, 0) self.system_stream_dir_icb = UDFLongAD() self.system_stream_dir_icb.new(0, 0) self._initialized = True def set_extent_location(self, new_location): # type: (int) -> None ''' Set the location of this UDF File Set Descriptor. Parameters: new_location - The new extent this UDF File Set Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Set Descriptor not initialized') self.new_extent_loc = new_location class UDFLBAddr(object): ''' A class reprenting a UDF lb_addr (ECMA-167, Part 4, 7.1). ''' __slots__ = ('_initialized', 'logical_block_num', 'part_ref_num') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF lb_addr. Parameters: data - The data to parse Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF LBAddr already initialized') (self.logical_block_num, self.part_ref_num) = struct.unpack_from(self.FMT, data, 0) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF LBAddr. Parameters: None. Returns: A string representing this UDF LBAddr. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF LBAddr not initialized') return struct.pack(self.FMT, self.logical_block_num, self.part_ref_num) def new(self, logical_block_num): # type: (int) -> None ''' Create a new UDF LBAddr. Parameters: logical_block_num - The logical block number to assign. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF LBAddr already initialized') self.logical_block_num = logical_block_num self.part_ref_num = 0 self._initialized = True class UDFICBTag(object): ''' A class representing a UDF ICB Tag (ECMA-167, Part 4, 14.6). ''' __slots__ = ('_initialized', 'prior_num_direct_entries', 'strategy_type', 'strategy_param', 'max_num_entries', 'file_type', 'parent_icb', 'flags') FMT = ' None self._initialized = False def parse(self, data): # type: (bytes) -> None ''' Parse the passed in data into a UDF ICB Tag. Parameters: data - The data to parse. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF ICB Tag already initialized') (self.prior_num_direct_entries, self.strategy_type, self.strategy_param, self.max_num_entries, reserved, self.file_type, parent_icb, self.flags) = struct.unpack_from(self.FMT, data, 0) if self.strategy_type not in (4, 4096): raise pycdlibexception.PyCdlibInvalidISO('UDF ICB Tag invalid strategy type') if reserved != 0: raise pycdlibexception.PyCdlibInvalidISO('UDF ICB Tag reserved not 0') self.parent_icb = UDFLBAddr() self.parent_icb.parse(parent_icb) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF ICB Tag. Parameters: None. Returns: A string representing this UDF ICB Tag. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF ICB Tag not initialized') return struct.pack(self.FMT, self.prior_num_direct_entries, self.strategy_type, self.strategy_param, self.max_num_entries, 0, self.file_type, self.parent_icb.record(), self.flags) def new(self, file_type): # type: (str) -> None ''' Create a new UDF ICB Tag. Parameters: file_type - What file type this represents, one of 'dir', 'file', or 'symlink'. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF ICB Tag already initialized') self.prior_num_direct_entries = 0 # FIXME: let the user set this self.strategy_type = 4 self.strategy_param = 0 # FIXME: let the user set this self.max_num_entries = 1 if file_type == 'dir': self.file_type = 4 elif file_type == 'file': self.file_type = 5 elif file_type == 'symlink': self.file_type = 12 else: raise pycdlibexception.PyCdlibInternalError("Invalid file type for ICB; must be one of 'dir', 'file', or 'symlink'") self.parent_icb = UDFLBAddr() self.parent_icb.new(0) self.flags = 560 # hex 0x230 == binary 0010 0011 0000 self._initialized = True class UDFFileEntry(object): ''' A class representing a UDF File Entry (ECMA-167, Part 4, 14.9). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'uid', 'gid', 'perms', 'file_link_count', 'info_len', 'hidden', 'log_block_recorded', 'unique_id', 'len_extended_attrs', 'desc_tag', 'icb_tag', 'alloc_descs', 'fi_descs', 'parent', 'access_time', 'mod_time', 'attr_time', 'extended_attr_icb', 'impl_ident', 'extended_attrs', 'file_ident', 'inode') FMT = '<16s20sLLLHBBLQQ12s12s12sL16s32sQLL' def __init__(self): # type: () -> None self.alloc_descs = [] # type: List[Union[UDFShortAD, UDFLongAD, UDFInlineAD]] self.fi_descs = [] # type: List[UDFFileIdentifierDescriptor] self._initialized = False self.parent = None # type: Optional[UDFFileEntry] self.hidden = False self.file_ident = None # type: Optional[UDFFileIdentifierDescriptor] self.inode = None # type: Optional[inode.Inode] self.new_extent_loc = -1 def parse(self, data, extent, parent, desc_tag): # type: (bytes, int, Optional[UDFFileEntry], UDFTag) -> None ''' Parse the passed in data into a UDF File Entry. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. parent - The parent File Entry for this file (may be None). desc_tag - A UDFTag object that represents the Descriptor Tag. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry already initialized') (tag_unused, icb_tag, self.uid, self.gid, self.perms, self.file_link_count, record_format, record_display_attrs, record_len, self.info_len, self.log_block_recorded, access_time, mod_time, attr_time, checkpoint, extended_attr_icb, impl_ident, self.unique_id, self.len_extended_attrs, len_alloc_descs) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) if record_format != 0: raise pycdlibexception.PyCdlibInvalidISO('File Entry record format is not 0') if record_display_attrs != 0: raise pycdlibexception.PyCdlibInvalidISO('File Entry record display attributes is not 0') if record_len != 0: raise pycdlibexception.PyCdlibInvalidISO('File Entry record length is not 0') self.access_time = UDFTimestamp() self.access_time.parse(access_time) self.mod_time = UDFTimestamp() self.mod_time.parse(mod_time) self.attr_time = UDFTimestamp() self.attr_time.parse(attr_time) if checkpoint != 1: raise pycdlibexception.PyCdlibInvalidISO('Only DVD Read-only disks supported') self.extended_attr_icb = UDFLongAD() self.extended_attr_icb.parse(extended_attr_icb) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) offset = struct.calcsize(self.FMT) self.extended_attrs = data[offset:offset + self.len_extended_attrs] offset += self.len_extended_attrs self.alloc_descs = _parse_allocation_descriptors(self.icb_tag.flags, data[offset:], len_alloc_descs, offset, extent) self.orig_extent_loc = extent self.parent = parent self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF File Entry. Parameters: None. Returns: A string representing this UDF File Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') len_alloc_descs = 0 for desc in self.alloc_descs: len_alloc_descs += desc.length() rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record(), self.uid, self.gid, self.perms, self.file_link_count, 0, 0, 0, self.info_len, self.log_block_recorded, self.access_time.record(), self.mod_time.record(), self.attr_time.record(), 1, self.extended_attr_icb.record(), self.impl_ident.record(), self.unique_id, self.len_extended_attrs, len_alloc_descs)[16:] rec += self.extended_attrs for desc in self.alloc_descs: rec += desc.record() return self.desc_tag.record(rec) + rec def extent_location(self): # type: () -> int ''' Get the extent location of this UDF File Entry. Parameters: None. Returns: Integer extent location of this UDF File Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self, length, file_type, parent, log_block_size): # type: (int, str, Optional[UDFFileEntry], int) -> None ''' Create a new UDF File Entry. Parameters: length - The (starting) length of this UDF File Entry; this is ignored if this is a symlink. file_type - The type that this UDF File entry represents; one of 'dir', 'file', or 'symlink'. parent - The parent UDF File Entry for this UDF File Entry. log_block_size - The logical block size for extents. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry already initialized') if file_type not in ('dir', 'file', 'symlink'): raise pycdlibexception.PyCdlibInternalError("UDF File Entry file type must be one of 'dir', 'file', or 'symlink'") self.desc_tag = UDFTag() self.desc_tag.new(261) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self.uid = 4294967295 # Really -1, which means unset self.gid = 4294967295 # Really -1, which means unset if file_type == 'dir': self.perms = 5285 self.file_link_count = 0 self.info_len = 0 self.log_block_recorded = 1 # The position is bogus, but will get set # properly once reshuffle_extents is called. short_ad = UDFShortAD() short_ad.new(length) self.alloc_descs.append(short_ad) else: self.perms = 4228 self.file_link_count = 1 self.info_len = length self.log_block_recorded = utils.ceiling_div(length, log_block_size) len_left = length while len_left > 0: # According to Ecma-167 14.14.1.1, the least-significant 30 bits # of the allocation descriptor length field specify the length # (the most significant two bits are properties which we don't # currently support). In theory we should then split files # into 2^30 = 0x40000000, but all implementations I've seen # split it into smaller. cdrkit/cdrtools uses 0x3ffff800, and # Windows uses 0x3ff00000. To be more compatible with cdrkit, # we'll choose their number of 0x3ffff800. alloc_len = min(len_left, 0x3ffff800) # The position is bogus, but will get set # properly once reshuffle_extents is called. short_ad = UDFShortAD() short_ad.new(alloc_len) self.alloc_descs.append(short_ad) len_left -= alloc_len self.access_time = UDFTimestamp() self.access_time.new() self.mod_time = UDFTimestamp() self.mod_time.new() self.attr_time = UDFTimestamp() self.attr_time.new() self.extended_attr_icb = UDFLongAD() self.extended_attr_icb.new(0, 0) self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib') self.unique_id = 0 # this will get set later self.len_extended_attrs = 0 # FIXME: let the user set this self.extended_attrs = b'' self.parent = parent self._initialized = True def set_extent_location(self, new_location, tag_location): # type: (int, int) -> None ''' Set the location of this UDF File Entry. Parameters: new_location - The new extent this UDF File Entry should be located at. tag_location - The new relative extent this UDF File Entry should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = tag_location self.unique_id = new_location def add_file_ident_desc(self, new_fi_desc, logical_block_size): # type: (UDFFileIdentifierDescriptor, int) -> int ''' Add a new UDF File Identifier Descriptor to this UDF File Entry. Parameters: new_fi_desc - The new UDF File Identifier Descriptor to add. logical_block_size - The logical block size to use. Returns: The number of extents added due to adding this File Identifier Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') if self.icb_tag.file_type != 4: raise pycdlibexception.PyCdlibInvalidInput('Can only add a UDF File Identifier to a directory') self.fi_descs.append(new_fi_desc) num_bytes_to_add = UDFFileIdentifierDescriptor.length(len(new_fi_desc.fi)) old_num_extents = 0 # If info_len is 0, then this is a brand-new File Entry, and thus the # number of extents it is using is 0. if self.info_len > 0: old_num_extents = utils.ceiling_div(self.info_len, logical_block_size) self.info_len += num_bytes_to_add new_num_extents = utils.ceiling_div(self.info_len, logical_block_size) self.log_block_recorded = new_num_extents self.alloc_descs[0].extent_length = self.info_len if new_fi_desc.is_dir(): self.file_link_count += 1 return new_num_extents - old_num_extents def remove_file_ident_desc_by_name(self, name, logical_block_size): # type: (bytes, int) -> int ''' Remove a UDF File Identifier Descriptor from this UDF File Entry. Parameters: name - The name of the UDF File Identifier Descriptor to remove. logical_block_size - The logical block size to use. Returns: The number of extents removed due to removing this File Identifier Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') tmp_fi_desc = UDFFileIdentifierDescriptor() tmp_fi_desc.isparent = False tmp_fi_desc.fi = name # If flags bit 3 is set, the entries are sorted. desc_index = len(self.fi_descs) for index, fi_desc in enumerate(self.fi_descs): if fi_desc.fi == name: desc_index = index break if desc_index == len(self.fi_descs) or self.fi_descs[desc_index].fi != name: raise pycdlibexception.PyCdlibInvalidInput('Cannot find file to remove') this_desc = self.fi_descs[desc_index] if this_desc.is_dir(): if this_desc.file_entry is None: raise pycdlibexception.PyCdlibInternalError('No UDF File Entry for UDF File Descriptor') if len(this_desc.file_entry.fi_descs) > 1: raise pycdlibexception.PyCdlibInvalidInput('Directory must be empty to use rm_directory') self.file_link_count -= 1 old_num_extents = utils.ceiling_div(self.info_len, logical_block_size) self.info_len -= UDFFileIdentifierDescriptor.length(len(this_desc.fi)) new_num_extents = utils.ceiling_div(self.info_len, logical_block_size) self.alloc_descs[0].extent_length = self.info_len del self.fi_descs[desc_index] return old_num_extents - new_num_extents def set_data_location(self, current_extent, start_extent): # pylint: disable=unused-argument # type: (int, int) -> None ''' Set the location of the data that this UDF File Entry points to. Parameters: current_extent - Unused start_extent - The starting extent for this data location. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') current_assignment = start_extent for index, desc_unused in enumerate(self.alloc_descs): self.alloc_descs[index].log_block_num = current_assignment current_assignment += 1 def get_data_length(self): # type: () -> int ''' Get the length of the data that this UDF File Entry points to. Parameters: None. Returns: The length of the data that this UDF File Entry points to. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return self.info_len def set_data_length(self, length): # type: (int) -> None ''' Set the length of the data that this UDF File Entry points to. Parameters: length - The new length for the data. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') len_diff = length - self.info_len if len_diff > 0: # If we are increasing the length, update the last alloc_desc up # to the max of 0x3ffff800, and throw an exception if we overflow. new_len = self.alloc_descs[-1].extent_length + len_diff if new_len > 0x3ffff800: raise pycdlibexception.PyCdlibInvalidInput('Cannot increase the size of a UDF file beyond the current descriptor') self.alloc_descs[-1].extent_length = new_len elif len_diff < 0: # We are decreasing the length. It's possible we are removing one # or more alloc_descs, so run through the list updating all of the # descriptors and remove any we no longer need. len_left = length alloc_descs_needed = 0 index = 0 while len_left > 0: this_len = min(len_left, 0x3ffff800) alloc_descs_needed += 1 self.alloc_descs[index].extent_length = this_len index += 1 len_left -= this_len self.alloc_descs = self.alloc_descs[:alloc_descs_needed] self.info_len = length def is_file(self): # type: () -> bool ''' Determine whether this UDF File Entry points to a file. Parameters: None. Returns: True if this UDF File Entry points to a file, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return self.icb_tag.file_type == 5 def is_symlink(self): # type: () -> bool ''' Determine whether this UDF File Entry points to a symlink. Parameters: None. Returns: True if this UDF File Entry points to a symlink, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return self.icb_tag.file_type == 12 def is_dir(self): # type: () -> bool ''' Determine whether this UDF File Entry points to a directory. Parameters: None. Returns: True if this UDF File Entry points to a directory, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return self.icb_tag.file_type == 4 def file_identifier(self): # type: () -> bytes ''' Get the name of this UDF File Entry as a byte string. Parameters: None. Returns: The UDF File Entry as a byte string. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') if self.file_ident is None: return b'/' return self.file_ident.fi def find_file_ident_desc_by_name(self, currpath): # type: (bytes) -> UDFFileIdentifierDescriptor ''' Find a UDF File Identifier descriptor by its name. Parameters: currpath - The UTF-8 encoded name to look up. Returns: The UDF File Identifier descriptor corresponding to the passed in name. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') # If this is a directory or it is an empty directory, just skip # all work. if self.icb_tag.file_type != 4 or not self.fi_descs: raise pycdlibexception.PyCdlibInvalidInput('Could not find path') tmp = currpath.decode('utf-8') try: latin1_currpath = tmp.encode('latin-1') except (UnicodeDecodeError, UnicodeEncodeError): latin1_currpath = b'' ucs2_currpath = tmp.encode('utf-16_be') child = None for fi_desc in self.fi_descs: if latin1_currpath and fi_desc.encoding == 'latin-1': eq = fi_desc.fi == latin1_currpath else: eq = fi_desc.fi == ucs2_currpath if eq: child = fi_desc break if child is None: raise pycdlibexception.PyCdlibInvalidInput('Could not find path') return child def track_file_ident_desc(self, file_ident): # type: (UDFFileIdentifierDescriptor) -> None ''' Start tracking a UDF File Identifier descriptor in this UDF File Entry. Both 'tracking' and 'addition' add the identifier to the list of file identifiers, but tracking doees not expand or otherwise modify the UDF File Entry. Parameters: file_ident - The UDF File Identifier Descriptor to start tracking. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') self.fi_descs.append(file_ident) def is_dot(self): # type: () -> bool ''' A dummy method to determine whether this is a 'dot' entry. Since this concept doesn't exist in UDF, it always returns False. Parameters: None. Returns: False. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return False def is_dotdot(self): # type: () -> bool ''' A dummy method to determine whether this is a 'dotdot' entry. While UDF has the concept of 'parent' identifiers that are roughly equivalent, pycdlib doesn't attach UDF File Entries to them, so this method always return False. Parameters: None. Returns: False. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Entry not initialized') return False class UDFFileIdentifierDescriptor(object): ''' A class representing a UDF File Identifier Descriptor (ECMA-167, Part 4, 14.4). ''' __slots__ = ('_initialized', 'orig_extent_loc', 'new_extent_loc', 'desc_tag', 'file_characteristics', 'len_fi', 'len_impl_use', 'fi', 'isdir', 'isparent', 'icb', 'impl_use', 'file_entry', 'encoding', 'parent') FMT = '<16sHBB16sH' def __init__(self): # type: () -> None self.file_entry = None # type: Optional[UDFFileEntry] self._initialized = False self.fi = b'' self.encoding = '' self.isparent = False self.isdir = False self.parent = None # type: Optional[UDFFileEntry] self.new_extent_loc = -1 @classmethod def length(cls, namelen): # type: (Type[UDFFileIdentifierDescriptor], int) -> int ''' A class method to calculate the size this UDFFileIdentifierDescriptor would take up. Parameters: cls - The class to use (always UDFFileIdentifierDescriptor). namelen - The length of the name. Returns: The length that the UDFFileIdentifierDescriptor would take up. ''' if namelen > 0: namelen += 1 to_add = struct.calcsize(cls.FMT) + namelen return to_add + UDFFileIdentifierDescriptor.pad(to_add) @staticmethod def pad(val): # type: (int) -> int ''' A static method to calculate the amount of padding necessary for this UDF File Identifer Descriptor. Parameters: val - The amount of non-padded space this UDF File Identifier Descriptor uses. Returns: The amount of padding necessary to make this a compliant UDF File Identifier Descriptor. ''' return (4 * ((val + 3) // 4)) - val def parse(self, data, extent, desc_tag, parent): # type: (bytes, int, UDFTag, UDFFileEntry) -> int ''' Parse the passed in data into a UDF File Identifier Descriptor. Parameters: data - The data to parse. extent - The extent that this descriptor currently lives at. desc_tag - A UDFTag object that represents the Descriptor Tag. parent - The UDF File Entry representing the parent. Returns: The number of bytes this descriptor consumed. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier Descriptor already initialized') (tag_unused, file_version_num, self.file_characteristics, self.len_fi, icb, self.len_impl_use) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag if file_version_num != 1: raise pycdlibexception.PyCdlibInvalidISO('File Identifier Descriptor file version number not 1') if self.file_characteristics & 0x2: self.isdir = True if self.file_characteristics & 0x8: self.isparent = True self.icb = UDFLongAD() self.icb.parse(icb) start = struct.calcsize(self.FMT) end = start + self.len_impl_use self.impl_use = data[start:end] start = end end = start + self.len_fi # The very first byte of the File Identifier describes whether this is # an 8-bit or 16-bit encoded string; this corresponds to whether we # encode with 'latin-1' or with 'utf-16_be'. We save that off because we have # to write the correct thing out when we record. if not self.isparent: encoding = bytes(bytearray([data[start]])) if encoding == b'\x08': self.encoding = 'latin-1' elif encoding == b'\x10': self.encoding = 'utf-16_be' else: raise pycdlibexception.PyCdlibInvalidISO('Only UDF File Identifier Descriptor Encodings 8 or 16 are supported') start += 1 self.fi = data[start:end] self.orig_extent_loc = extent self.parent = parent self._initialized = True return end + UDFFileIdentifierDescriptor.pad(end) def is_dir(self): # type: () -> bool ''' Determine if this File Identifier represents a directory. Parameters: None. Returns: True if this File Identifier represents a directory, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier Descriptor not initialized') return self.isdir def is_parent(self): # type: () -> bool ''' Determine if this File Identifier is a 'parent' (essentially ..). Parameters: None. Returns: True if this File Identifier is a parent, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier Descriptor not initialized') return self.isparent def record(self): # type: () -> bytes ''' Generate the string representing this UDF File Identifier Descriptor. Parameters: None. Returns: A string representing this UDF File Identifier Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier Descriptor not initialized') if self.len_fi > 0: if self.encoding == 'latin-1': prefix = b'\x08' elif self.encoding == 'utf-16_be': prefix = b'\x10' else: raise pycdlibexception.PyCdlibInternalError('Invalid UDF encoding; this should not happen') fi = prefix + self.fi else: fi = b'' rec = struct.pack(self.FMT, b'\x00' * 16, 1, self.file_characteristics, self.len_fi, self.icb.record(), self.len_impl_use) + self.impl_use + fi + b'\x00' * UDFFileIdentifierDescriptor.pad(struct.calcsize(self.FMT) + self.len_impl_use + self.len_fi) return self.desc_tag.record(rec[16:]) + rec[16:] def extent_location(self): # type: () -> int ''' Get the extent location of this UDF File Identifier. Parameters: None. Returns: Integer extent location of this UDF File Identifier. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def new(self, isdir, isparent, name, parent): # type: (bool, bool, bytes, Optional[UDFFileEntry]) -> None ''' Create a new UDF File Identifier. Parameters: isdir - Whether this File Identifier is a directory. isparent - Whether this File Identifier is a parent (..). name - The name for this File Identifier. parent - The UDF File Entry representing the parent. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier already initialized') self.desc_tag = UDFTag() self.desc_tag.new(257) # FIXME: let the user set serial_number self.icb = UDFLongAD() self.icb.new(2048, 2) self.isdir = isdir self.isparent = isparent self.file_characteristics = 0 if self.isdir: self.file_characteristics |= 0x2 if self.isparent: self.file_characteristics |= 0x8 self.len_impl_use = 0 # FIXME: let the user set this self.impl_use = b'' self.len_fi = 0 if not isparent: bytename = name.decode('utf-8') try: self.fi = bytename.encode('latin-1') self.encoding = 'latin-1' except UnicodeEncodeError: self.fi = bytename.encode('utf-16_be') self.encoding = 'utf-16_be' self.len_fi = len(self.fi) + 1 self.parent = parent self._initialized = True def set_extent_location(self, new_location, tag_location): # type: (int, int) -> None ''' Set the location of this UDF File Identifier Descriptor. Note that many UDF File Identifier Descriptors may have the same starting extent. Parameters: new_location - The new extent this UDF File Identifier Descriptor should be located at. tag_location - The new relative extent this UDF File Identifier Descriptor should be located at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier not initialized') self.new_extent_loc = new_location self.desc_tag.tag_location = tag_location def set_icb(self, new_location, tag_location): # type: (int, int) -> None ''' Set the location of the data that this UDF File Identifier Descriptor points at. The data can either be for a directory or for a file. Parameters: new_location - The new extent this UDF File Identifier Descriptor data lives at. tag_location - The new relative extent this UDF File Identifier Descriptor data lives at. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF File Identifier not initialized') self.icb.set_extent_location(new_location, tag_location) def __lt__(self, other): # type: (UDFFileIdentifierDescriptor) -> bool if self.isparent: if other.isparent: return False return True if other.isparent: return False return self.fi < other.fi def __eq__(self, other): # type: (object) -> bool if not isinstance(other, UDFFileIdentifierDescriptor): return NotImplemented if self.isparent: if other.isparent: return True return False if other.isparent: return False return self.fi == other.fi class UDFSpaceBitmapDescriptor(object): ''' A class representing a UDF Space Bitmap Descriptor. ''' __slots__ = ('_initialized', 'num_bits', 'num_bytes', 'bitmap', 'new_extent_loc', 'orig_extent_loc', 'desc_tag') FMT = '<16sLL24s' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent_loc, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Space Bitmap Descriptor. Parameters: data - The data to parse. extent_loc - The extent location this UDF Space Bitmap Descriptor lives at. desc_tag - The UDFTag describing this UDF Space Bitmap Descriptor. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Space Bitmap Descriptor already initialized') (tag_unused, self.num_bits, self.num_bytes, self.bitmap) = struct.unpack_from(self.FMT, data, 0) self.orig_extent_loc = extent_loc self.desc_tag = desc_tag self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Space Bitmap Descriptor. Parameters: None. Returns: A string representing this UDF Space Bitmap Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Space Bitmap Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.num_bits, self.num_bytes, self.bitmap)[16:] return self.desc_tag.record(rec) + rec def new(self): # type: () -> None ''' Create a new UDF Space Bitmap Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Space Bitmap Descriptor already initialized') self.num_bits = 0 self.num_bytes = 0 self.bitmap = b'' self.desc_tag = UDFTag() self.desc_tag.new(264) self._initialized = True def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Space Bitmap Descriptor. Parameters: None. Returns: Integer extent location of this UDF Space Bitmap Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Space Bitmap Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF Space Bitmap Descriptor. Parameters: extent - The new extent location to set for this UDF Space Bitmap Descriptor. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Space Bitmap Descriptor not initialized') self.new_extent_loc = extent class UDFAllocationExtentDescriptor(object): ''' A class representing a UDF Space Bitmap Descriptor (ECMA-167, Part 4, 14.5). ''' __slots__ = ('_initialized', 'prev_allocation_extent_loc', 'len_allocation_descs', 'new_extent_loc', 'orig_extent_loc', 'desc_tag') FMT = '<16sLL' def __init__(self): # type: () -> None self.new_extent_loc = -1 self._initialized = False def parse(self, data, extent_loc, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Allocation Extent Descriptor. Parameters: data - The data to parse. extent_loc - The extent location that this UDF Allocation Extent Descriptor lives at. desc_tag - The UDF Tag associated with this UDF Allocation Extent Descriptor. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Extent Descriptor already initialized') (tag_unused, self.prev_allocation_extent_loc, self.len_allocation_descs) = struct.unpack_from(self.FMT, data, 0) self.orig_extent_loc = extent_loc self.desc_tag = desc_tag self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Allocation Extent Descriptor. Parameters: None. Returns: A string representing this UDF Allocation Extent Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Extent Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.prev_allocation_extent_loc, self.len_allocation_descs)[16:] return self.desc_tag.record(rec) + rec def new(self): # type: () -> None ''' Create a new UDF Allocation Extent Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Extent Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(258) self.prev_allocation_extent_loc = 0 # FIXME: allow these to be set self.len_allocation_descs = 0 # FIXME: allow these to be set self._initialized = True def extent_location(self): # type: () -> int ''' Get the extent location of this UDF Allocation Extent Descriptor. Parameters: None. Returns: Integer extent location of this UDF Allocation Extent Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Extent Descriptor not initialized') if self.new_extent_loc < 0: return self.orig_extent_loc return self.new_extent_loc def set_extent_location(self, extent): # type: (int) -> None ''' Set the new location for this UDF Allocation Extent Descriptor. Parameters: extent - The new extent location to set for this UDF Allocation Extent Descriptor. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Extent Descriptor not initialized') self.new_extent_loc = extent class UDFIndirectEntry(object): ''' A class representing a UDF Indirect Entry (ECMA-167, Part 4, 14.7). ''' __slots__ = ('_initialized', 'icb_tag', 'indirect_icb', 'desc_tag') FMT = '=16s20s16s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data, desc_tag): # type: (bytes, UDFTag) -> None ''' Parse the passed in data into a UDF Indirect Entry. Parameters: data - The data to parse. desc_tag - The UDF Tag associated with this UDF Allocation Extent Descriptor. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Indirect Entry already initialized') (tag_unused, icb_tag, indirect_icb) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) self.indirect_icb = UDFLongAD() self.indirect_icb.parse(indirect_icb) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Indirect Entry. Parameters: None. Returns: A string representing this UDF Indirect Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Indirect Entry not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record(), self.indirect_icb.record())[16:] return self.desc_tag.record(rec) + rec def new(self, file_type): # type: (str) -> None ''' Create a new UDF Indirect Entry. Parameters: file_type - The type that this UDF File entry represents; one of 'dir', 'file', or 'symlink'. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Indirect Entry already initialized') self.desc_tag = UDFTag() self.desc_tag.new(259) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self.indirect_icb = UDFLongAD() self.indirect_icb.new(0, 0) self._initialized = True class UDFTerminalEntry(object): ''' A class representing a UDF Terminal Entry (ECMA-167, Part 4, 14.8). ''' __slots__ = ('_initialized', 'icb_tag', 'desc_tag') FMT = '=16s20s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data, tag): # type: (bytes, UDFTag) -> None ''' Parse the passed in data into a UDF Terminal Entry. Parameters: data - The data to parse. desc_tag - The UDF Tag associated with this UDF Terminal Entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminal Entry already initialized') (tag_unused, icb_tag) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Terminal Entry. Parameters: None. Returns: A string representing this UDF Terminal Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminal Entry not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record())[16:] return self.desc_tag.record(rec) + rec def new(self, file_type): # type: (str) -> None ''' Create a new UDF Terminal Entry. Parameters: file_type - The type that this UDF Terminal entry represents; one of 'dir', 'file', or 'symlink'. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Terminal Entry already initialized') self.desc_tag = UDFTag() self.desc_tag.new(260) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self._initialized = True class UDFExtendedAttributeHeaderDescriptor(object): ''' A class representing a UDF Extended Attribute Header Descriptor (ECMA-167, Part 4, 14.10.1). ''' __slots__ = ('_initialized', 'impl_attr_loc', 'app_attr_loc', 'icb_tag', 'desc_tag') FMT = '<16sLL' def __init__(self): # type: () -> None self._initialized = False def parse(self, data, desc_tag): # type: (bytes, UDFTag) -> None ''' Parse the passed in data into a UDF Extended Attribute Header Descriptor. Parameters: data - The data to parse. desc_tag - The UDF Tag associated with this UDF Extended Attribute Header Descriptor. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Attribute Header Descriptor already initialized') (tag_unused, self.impl_attr_loc, self.app_attr_loc) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Extended Attribute Header Descriptor. Parameters: None. Returns: A string representing this UDF Extended Attribute Header Descriptor. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Attribute Header Descriptor not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.impl_attr_loc, self.app_attr_loc)[16:] return self.desc_tag.record(rec) + rec def new(self): # type: () -> None ''' Create a new UDF Extended Attribute Header Descriptor. Parameters: None. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended Attribute Header Descriptor already initialized') self.desc_tag = UDFTag() self.desc_tag.new(262) # FIXME: let the user set serial_number self.impl_attr_loc = 0 self.app_attr_loc = 0 self._initialized = True class UDFUnallocatedSpaceEntry(object): ''' A class representing a UDF Unallocated Space Entry (ECMA-167, Part 4, 14.11). ''' __slots__ = ('_initialized', 'alloc_descs', 'icb_tag', 'desc_tag') FMT = '<16s20sL' def __init__(self): # type: () -> None self.alloc_descs = [] # type: List[Union[UDFShortAD, UDFLongAD, UDFInlineAD]] self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Unallocated Space Entry. Parameters: data - The data to parse. extent - The extent associated with this UDF Unallocated Space Entry. desc_tag - The UDF Tag associated with this UDF Unallocated Space Entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Entry already initialized') (tag_unused, icb_tag, len_alloc_descs) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) offset = 16 + 20 + 4 self.alloc_descs = _parse_allocation_descriptors(self.icb_tag.flags, data[offset:], len_alloc_descs, offset, extent) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Unallocated Space Entry. Parameters: None. Returns: A string representing this UDF Unallocated Space Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Entry not initialized') len_alloc_descs = 0 for desc in self.alloc_descs: len_alloc_descs += desc.length() rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record(), len_alloc_descs)[16:] for desc in self.alloc_descs: rec += desc.record() return self.desc_tag.record(rec) + rec def new(self, file_type): # type: (str) -> None ''' Create a new UDF Unallocated Space Entry. Parameters: file_type - The type that this UDF Space Entry represents; one of 'dir', 'file', or 'symlink'. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Unallocated Space Entry already initialized') self.desc_tag = UDFTag() self.desc_tag.new(263) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self._initialized = True class UDFPartitionIntegrityEntry(object): ''' A class representing a UDF Partition Integrity Entry (ECMA-167, Part 4, 14.13). ''' __slots__ = ('_initialized', 'integrity_type', 'timestamp', 'impl_ident', 'impl_use', 'icb_tag', 'desc_tag') FMT = '=16s20s12sB175s32s256s' def __init__(self): # type: () -> None self._initialized = False def parse(self, data, desc_tag): # type: (bytes, UDFTag) -> None ''' Parse the passed in data into a UDF Partition Integrity Entry. Parameters: data - The data to parse. desc_tag - The UDF Tag associated with this UDF Partition Integrity Entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Integrity Entry already initialized') (tag_unused, icb_tag, record_date, self.integrity_type, reserved_unused, impl_ident, self.impl_use) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) self.timestamp = UDFTimestamp() self.timestamp.parse(record_date) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Partition Integrity Entry. Parameters: None. Returns: A string representing this UDF Partition Integrity Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Integrity Entry not initialized') rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record(), self.timestamp.record(), self.integrity_type, b'\x00' * 175, self.impl_ident.record(), self.impl_use)[16:] return self.desc_tag.record(rec) + rec def new(self, file_type): # type: (str) -> None ''' Create a new UDF Partition Integrity Entry. Parameters: file_type - The type that this UDF Space Entry represents; one of 'dir', 'file', or 'symlink'. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Partition Integrity Entry already initialized') self.desc_tag = UDFTag() self.desc_tag.new(265) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self.integrity_type = 1 self.timestamp = UDFTimestamp() self.timestamp.new() self.impl_ident = UDFEntityID() self.impl_ident.new(0) self.impl_use = b'\x00' * 256 self._initialized = True class UDFExtendedFileEntry(object): ''' A class representing a UDF Extended File Entry (ECMA-167, Part 4, 14.17). ''' __slots__ = ('_initialized', 'uid', 'gid', 'permissions', 'file_link_count', 'record_format', 'record_display_attrs', 'record_len', 'info_len', 'obj_size', 'log_blocks_recorded', 'access_time', 'mod_time', 'creation_time', 'impl_ident', 'attr_time', 'checkpoint', 'extended_attr_icb', 'stream_icb', 'extended_attrs', 'unique_id', 'alloc_descs', 'len_extended_attrs', 'icb_tag', 'desc_tag') FMT = '<16s20sLLLHBBLQQQ12s12s12s12sL4s16s16s32sQLL' def __init__(self): # type: () -> None self.alloc_descs = [] # type: List[Union[UDFShortAD, UDFLongAD, UDFInlineAD]] self._initialized = False def parse(self, data, extent, desc_tag): # type: (bytes, int, UDFTag) -> None ''' Parse the passed in data into a UDF Extended File Entry. Parameters: data - The data to parse. extent - The extent this Extended File Entry lives at. desc_tag - The UDF Tag associated with this UDF Extended File Entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended File Entry already initialized') (tag_unused, icb_tag, self.uid, self.gid, self.permissions, self.file_link_count, self.record_format, self.record_display_attrs, self.record_len, self.info_len, self.obj_size, self.log_blocks_recorded, access_time, mod_time, creation_time, attr_time, self.checkpoint, reserved_unused, extended_attr_icb, stream_icb, impl_ident, self.unique_id, self.len_extended_attrs, len_alloc_descs) = struct.unpack_from(self.FMT, data, 0) self.desc_tag = desc_tag self.icb_tag = UDFICBTag() self.icb_tag.parse(icb_tag) self.access_time = UDFTimestamp() self.access_time.parse(access_time) self.mod_time = UDFTimestamp() self.mod_time.parse(mod_time) self.creation_time = UDFTimestamp() self.creation_time.parse(creation_time) self.attr_time = UDFTimestamp() self.attr_time.parse(attr_time) self.extended_attr_icb = UDFLongAD() self.extended_attr_icb.parse(extended_attr_icb) self.stream_icb = UDFLongAD() self.stream_icb.parse(stream_icb) self.impl_ident = UDFEntityID() self.impl_ident.parse(impl_ident) offset = struct.calcsize(self.FMT) self.extended_attrs = data[offset:offset + self.len_extended_attrs] offset += self.len_extended_attrs self.alloc_descs = _parse_allocation_descriptors(self.icb_tag.flags, data[offset:], len_alloc_descs, offset, extent) self._initialized = True def record(self): # type: () -> bytes ''' Generate the string representing this UDF Extended File Entry. Parameters: None. Returns: A string representing this UDF Extended File Entry. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended File Entry not initialized') len_alloc_descs = 0 for desc in self.alloc_descs: len_alloc_descs += desc.length() rec = struct.pack(self.FMT, b'\x00' * 16, self.icb_tag.record(), self.uid, self.gid, self.permissions, self.file_link_count, self.record_format, self.record_display_attrs, self.record_len, self.info_len, self.obj_size, self.log_blocks_recorded, self.access_time.record(), self.mod_time.record(), self.creation_time.record(), self.attr_time.record(), self.checkpoint, b'\x00' * 4, self.extended_attr_icb.record(), self.stream_icb.record(), self.impl_ident.record(), self.unique_id, self.len_extended_attrs, len_alloc_descs)[16:] rec += self.extended_attrs for desc in self.alloc_descs: rec += desc.record() return self.desc_tag.record(rec) + rec def new(self, file_type, length, log_block_size): # type: (str, int, int) -> None ''' Create a new UDF Extended File Entry. Parameters: file_type - The type that this UDF Space Entry represents; one of 'dir', 'file', or 'symlink'. length - The (starting) length of this UDF File Entry; this is ignored if this is a symlink. log_block_size - The logical block size for extents. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('UDF Extended File Entry already initialized') self.desc_tag = UDFTag() self.desc_tag.new(266) # FIXME: let the user set serial_number self.icb_tag = UDFICBTag() self.icb_tag.new(file_type) self.uid = 4294967295 # Really -1, which means unset self.gid = 4294967295 # Really -1, which means unset if file_type == 'dir': self.permissions = 5285 self.file_link_count = 0 self.info_len = 0 self.log_blocks_recorded = 1 # The position is bogus, but will get set # properly once reshuffle_extents is called. short_ad = UDFShortAD() short_ad.new(length) self.alloc_descs.append(short_ad) else: self.permissions = 4228 self.file_link_count = 1 self.info_len = length self.log_blocks_recorded = utils.ceiling_div(length, log_block_size) len_left = length while len_left > 0: # According to Ecma-167 14.14.1.1, the least-significant 30 bits # of the allocation descriptor length field specify the length # (the most significant two bits are properties which we don't # currently support). In theory we should then split files # into 2^30 = 0x40000000, but all implementations I've seen # split it into smaller. cdrkit/cdrtools uses 0x3ffff800, and # Windows uses 0x3ff00000. To be more compatible with cdrkit, # we'll choose their number of 0x3ffff800. alloc_len = min(len_left, 0x3ffff800) # The position is bogus, but will get set # properly once reshuffle_extents is called. short_ad = UDFShortAD() short_ad.new(alloc_len) self.alloc_descs.append(short_ad) len_left -= alloc_len self.access_time = UDFTimestamp() self.access_time.new() self.mod_time = UDFTimestamp() self.mod_time.new() self.attr_time = UDFTimestamp() self.attr_time.new() self.extended_attr_icb = UDFLongAD() self.extended_attr_icb.new(0, 0) self.impl_ident = UDFEntityID() self.impl_ident.new(0, b'*pycdlib') self.unique_id = 0 # this will get set later self.len_extended_attrs = 0 # FIXME: let the user set this self.extended_attrs = b'' self._initialized = True def symlink_to_bytes(symlink_target): # type: (str) -> bytes ''' Generate UDF symlink data from a Unix-like path. Parameters: symlink_target - The Unix-like path that is the symlink. Returns: The UDF data corresponding to the symlink. ''' symlink_data = bytearray() for comp in symlink_target.split('/'): if comp == '': # If comp is empty, then we know this is the leading slash # and we should make an absolute entry (double slashes and # such are weeded out by the earlier utils.normpath). symlink_data.extend(b'\x02\x00\x00\x00') elif comp == '.': symlink_data.extend(b'\x04\x00\x00\x00') elif comp == '..': symlink_data.extend(b'\x03\x00\x00\x00') else: symlink_data.extend(b'\x05') ostaname = _ostaunicode(comp) symlink_data.append(len(ostaname)) symlink_data.extend(b'\x00\x00') symlink_data.extend(ostaname) return symlink_data def _parse_allocation_descriptors(flags, data, length, start_offset, extent): # type: (int, bytes, int, int, int) -> List[Union[UDFShortAD, UDFLongAD, UDFInlineAD]] ''' Generate a list of allocation descriptors from the data. Parameters: flags - The flags describing which kind of allocation descriptors are in the data. data - The data to parse. length - The length of the data. start_offset - The start offset of the allocation descriptor data in the overall data. extent - The extent at which the data lives. Returns: The list of allocation descriptors. ''' alloc_descs = [] # type: List[Union[UDFShortAD, UDFLongAD, UDFInlineAD]] offset = 0 # Now we need to create the allocation descriptors. How they are # represented changes depending on bits 0-2 of the icb_tag.flags field: # 0 = short_ad # 1 = long_ad # 2 = extended_ad # 3 = single descriptor spanning entire length of the Allocation # Descriptors field of this File Entry. if (flags & 0x7) == 0: while offset < length: short_ad = UDFShortAD() short_ad.parse(data[offset:]) alloc_descs.append(short_ad) offset += short_ad.length() elif (flags & 0x7) == 1: while offset < length: long_ad = UDFLongAD() long_ad.parse(data[offset:]) alloc_descs.append(long_ad) offset += long_ad.length() elif (flags & 0x7) == 2: raise pycdlibexception.PyCdlibInternalError('UDF Allocation Descriptor of type 2 (Extended) not yet supported') elif (flags & 0x7) == 3: inline_ad = UDFInlineAD() inline_ad.parse(length, extent, start_offset) alloc_descs.append(inline_ad) else: raise pycdlibexception.PyCdlibInvalidISO('UDF Allocation Descriptor type invalid') return alloc_descs ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4941056 pycdlib-1.11.0/pycdlib/utils.py0000664000175000017500000003362400000000000021153 0ustar00clalancetteclalancette00000000000000# Copyright (C) 2015-2020 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' Various utilities for PyCdlib. ''' from __future__ import absolute_import try: import cStringIO # pylint: disable=import-error except ImportError: pass import io import os import re import sys import time from pycdlib import pycdlibexception # For mypy annotations if False: # pylint: disable=using-constant-test from typing import BinaryIO, List, Tuple # NOQA pylint: disable=unused-import def swab_32bit(x): # type: (int) -> int ''' A function to swab a 32-bit integer. Parameters: x - The 32-bit integer to swab. Returns: The swabbed version of the 32-bit integer. ''' if x > (((1 << 32) - 1) & 0xFFFFFFFF) or x < 0: raise pycdlibexception.PyCdlibInternalError('Invalid integer passed to swab; must be unsigned 32-bits!') return ((x << 24) & 0xFF000000) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | ((x >> 24) & 0x000000FF) def swab_16bit(x): # type: (int) -> int ''' A function to swab a 16-bit integer. Parameters: x - The 16-bit integer to swab. Returns: The swabbed version of the 16-bit integer. ''' if x > (((1 << 16) - 1) & 0xFFFFFFFF) or x < 0: raise pycdlibexception.PyCdlibInternalError('Invalid integer passed to swab; must be unsigned 16-bits!') return ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF) def ceiling_div(numer, denom): # type: (int, int) -> int ''' A function to do ceiling division; that is, dividing numerator by denominator and taking the ceiling. Parameters: numer - The numerator for the division. denom - The denominator for the division. Returns: The ceiling after dividing numerator by denominator. ''' # Doing division and then getting the ceiling is tricky; we do upside-down # floor division to make this happen. # See https://stackoverflow.com/questions/14822184/is-there-a-ceiling-equivalent-of-operator-in-python. return -(-numer // denom) def copy_data(data_length, blocksize, infp, outfp): # type: (int, int, BinaryIO, BinaryIO) -> None ''' A utility function to copy data from the input file object to the output file object. Parameters: data_length - The amount of data to copy. blocksize - How much data to copy per iteration. infp - The file object to copy data from. outfp - The file object to copy data to. Returns: Nothing. ''' left = data_length readsize = blocksize while left > 0: if left < readsize: readsize = left data = infp.read(readsize) # We have seen ISOs in the wild (Tribes Vengeance 1of4.iso) that # lie about the size of their files, causing reads to fail (since # we hit EOF before the supposed end of the file). If we got less data # than we asked for, abort the loop silently. data_len = len(data) if data_len != readsize: data_len = left outfp.write(data) left -= data_len def encode_space_pad(instr, length, encoding): # type: (bytes, int, str) -> bytes ''' A function to pad out an input string with spaces to the length specified. The space is first encoded into the specified encoding, then appended to the input string until the length is reached. Parameters: instr - The input string to encode and pad. length - The length to pad the input string to. encoding - The encoding to use. Returns: The input string encoded in the encoding and padded with encoded spaces. ''' output = instr.decode('utf-8').encode(encoding) if len(output) > length: raise pycdlibexception.PyCdlibInvalidInput('Input string too long!') encoded_space = ' '.encode(encoding) left = length - len(output) while left > 0: output += encoded_space left -= len(encoded_space) if left < 0: output = output[:left] return output def normpath(path): # type: (str) -> bytes ''' Normalize the given path, eliminating double slashes, etc. This function is a copy of the built-in python normpath, except we do *not* allow double slashes at the start. Parameters: path - The path to normalize. Returns: The normalized path. ''' sep = '/' empty = '' dot = '.' dotdot = '..' if path == empty: return dot.encode('utf-8') initial_slashes = path.startswith(sep) comps = path.split(sep) new_comps = [] # type: List[str] for comp in comps: if comp in (empty, dot): continue if comp != dotdot or (not initial_slashes and not new_comps) or (new_comps and new_comps[-1] == dotdot): new_comps.append(comp) elif new_comps: new_comps.pop() newpath = sep * initial_slashes + sep.join(new_comps) if sys.version_info >= (3, 0): newpath_bytes = newpath.encode('utf-8') else: newpath_bytes = newpath.decode('utf-8').encode('utf-8') if not starts_with_slash(newpath_bytes): raise pycdlibexception.PyCdlibInvalidInput('Must be a path starting with /') return newpath_bytes def gmtoffset_from_tm(tm, local): # type: (float, time.struct_time) -> int ''' A function to compute the GMT offset from the time in seconds since the epoch and the local time object. Parameters: tm - The time in seconds since the epoch. local - The struct_time object representing the local time. Returns: The gmtoffset. ''' gmtime = time.gmtime(tm) tmpyear = gmtime.tm_year - local.tm_year tmpyday = gmtime.tm_yday - local.tm_yday tmphour = gmtime.tm_hour - local.tm_hour tmpmin = gmtime.tm_min - local.tm_min if tmpyday < 0: tmpyday = -1 else: if tmpyear > 0: tmpyday = 1 return -(tmpmin + 60 * (tmphour + 24 * tmpyday)) // 15 def zero_pad(fp, data_size, pad_size): # type: (BinaryIO, int, int) -> None ''' A function to write padding out from data_size up to pad_size efficiently. Parameters: fp - The file object to use to write padding out to. data_size - The current size of the data. pad_size - The boundary size of data to pad out to. Returns: Nothing. ''' padbytes = pad_size - (data_size % pad_size) if padbytes == pad_size: # Nothing to pad, get out. return fp.seek(padbytes - 1, os.SEEK_CUR) fp.write(b'\x00') def starts_with_slash(path): # type: (bytes) -> bool ''' A function to determine if a path starts with a slash. This is somewhat difficult to do portably between Python2 and Python3 and with performance, so we have a dedicated function for it. Parameters: path - The path to determine if it starts with a slash Returns: Whether the path starts with a slash. ''' return bytearray(path)[0] == 47 def split_path(iso_path): # type: (bytes) -> List[bytes] ''' A function to take a fully-qualified iso path and split it into components. Parameters: iso_path - The path to split. Returns: The components of the path as a list. ''' if not starts_with_slash(iso_path): raise pycdlibexception.PyCdlibInvalidInput('Must be a path starting with /') # Split the path along the slashes. Since our paths are always absolute, # the front is blank. return iso_path.split(b'/')[1:] def file_object_supports_binary(fp): # type: (BinaryIO) -> bool ''' A function to check whether a file-like object supports binary mode. Parameters: fp - The file-like object to check for binary mode support. Returns: True if the file-like object supports binary mode, False otherwise. ''' if hasattr(fp, 'mode'): return 'b' in fp.mode # Python 3 if sys.version_info >= (3, 0): return isinstance(fp, (io.RawIOBase, io.BufferedIOBase)) # Python 2 return isinstance(fp, (cStringIO.OutputType, cStringIO.InputType, io.RawIOBase, io.BufferedIOBase)) def truncate_basename(basename, iso_level, is_dir): # type: (str, int, bool) -> str ''' A function to truncate a basename and make it conformant to the passed-in ISO interchange level. Parameters: basename - The initial basename to truncate and translate iso_level - The ISO interchange level to follow when truncating/translating is_dir - Whether this is a directory or a file Returns: The truncated and translated name suitable for the ISO interchange level specified. ''' if iso_level == 4: # ISO level 4 allows "anything", so just return the original. return basename if iso_level == 1: maxlen = 8 else: maxlen = 31 if is_dir else 30 # For performance reasons, we first truncate the string to the length # allowed. Second, ISO9660 Levels 1, 2, and 3 require all uppercase names, # so we uppercase it. valid_base = basename[:maxlen].upper() # Finally, ISO9660 requires only uppercase letters, 0-9, and underscore. # Translate any non-compliant characters to underscore and return that. return re.sub('[^A-Z0-9_]{1}', r'_', valid_base) def mangle_file_for_iso9660(orig, iso_level): # type: (str, int) -> Tuple[str, str] ''' A function to take a regular Unix-style filename (including extension) and produce a tuple consisting of an ISO9660-valid basename and an ISO9660-valid extension. Parameters: orig - The original filename iso_level - The ISO interchange level to conform to Returns: A tuple where the first entry is the ISO9660-compliant basename and where the second entry is the ISO9660-compliant extension. ''' # ISO9660 has a lot of restrictions on what valid names are. Here, we mangle # the names to conform to those rules. In particular, the rules for # filenames are: # 1. Filenames can only consist of d-characters or d1-characters; these are # defined in the Appendix as: 0-9A-Z_ # 2. Filenames look like: # - zero or more d-characters (filename) # - separator 1 (.) # - zero or more d-characters (extension) # - separate 2 (;) # - version, between 0 and 32767 # If the filename contains zero characters, then the extension must contain # at least one character, and vice versa. # 3. If this is iso level one, then the length of the filename cannot # exceed 8 and the length of the extension cannot exceed 3. In levels 2 # and 3, the length of the filename+extension cannot exceed 30. # # This function takes any valid Unix filename and converts it into one that # is allowed by the above rules. It does this by substituting _ for any # invalid characters in the filename, and by shortening the name to a form # of aaa_xxxx.eee;1 (if necessary). The aaa is always the first three # characters of the original filename; the xxxx is the next number in a # sequence starting from 0. valid_ext = '' splitter = orig.split('.') if iso_level == 4: # A level 4 ISO allows 'anything', so just return the original. if len(splitter) == 1: return orig, valid_ext ext = splitter[-1] return orig[:len(orig) - len(ext) - 1], ext if len(splitter) == 1: # No extension specified, leave ext empty basename = orig else: ext = splitter[-1] basename = orig[:len(orig) - len(ext) - 1] # If the extension is empty, too long (> 3), or contains any illegal # characters, we treat it as part of the basename instead extlen = len(ext) if extlen == 0 or extlen > 3: valid_ext = '' basename = orig else: tmpext = ext.upper() valid_ext, numsub = re.subn('[^A-Z0-9_]{1}', r'_', tmpext) if numsub > 0: valid_ext = '' basename = orig # All right, now we have the basename of the file, and (optionally) an # extension. return truncate_basename(basename, iso_level, False), valid_ext + ';1' def mangle_dir_for_iso9660(orig, iso_level): # type: (str, int) -> str ''' A function to take a regular Unix-style directory name and produce an ISO9660-valid directory name. Parameters: orig - The original filename iso_level - The ISO interchange level to conform to Returns: An ISO9660-compliant directory name. ''' # ISO9660 has a lot of restrictions on what valid directory names are. # Here, we mangle the names to conform to those rules. In particular, the # rules for dirnames are: # 1. Filenames can only consist of d-characters or d1-characters; these are # defined in the Appendix as: 0-9A-Z_ # 2. If this is ISO level one, then directory names consist of no more than # 8 characters # This function takes any valid Unix directory name and converts it into one # that is allowed by the above rules. It does this by substituting _ for # any invalid character in the directory name, and by shortening the name to # a form of aaaaxxx (if necessary). The aaa is always the first three # characters of the original filename; the xxxx is the next number in a # sequence starting from 0. return truncate_basename(orig, iso_level, True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574543086.0957515 pycdlib-1.11.0/pytest.ini0000664000175000017500000000013400000000000020032 0ustar00clalancetteclalancette00000000000000[pytest] markers = slow: marks tests as slow (deselect with '-m "not slow"') serial ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6491113 pycdlib-1.11.0/setup.cfg0000664000175000017500000000037500000000000017631 0ustar00clalancetteclalancette00000000000000[bdist_wheel] # This flag says that the code is written to work on both Python 2 and Python # 3. If at all possible, it is good practice to do this. If you cannot, you # will need to generate wheels for each Python version that you support. universal=1 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602122815.1794562 pycdlib-1.11.0/setup.py0000664000175000017500000000532000000000000017515 0ustar00clalancetteclalancette00000000000000import io import setuptools from distutils.command.sdist import sdist as _sdist import subprocess import time VERSION='1.11.0' RELEASE='1' class sdist(_sdist): ''' custom sdist command, to prep pycdlib.spec file for inclusion ''' def run(self): global VERSION global RELEASE # If development release, include date+githash in %{release} if RELEASE.startswith('0'): # Create a development release string for later use git_head = subprocess.Popen("git log -1 --pretty=format:%h", shell=True, stdout=subprocess.PIPE).communicate()[0].strip() date = time.strftime("%Y%m%d%H%M%S", time.gmtime()) git_release = "%sgit%s" % (date, git_head.decode('utf-8')) RELEASE += '.' + git_release # Expand macros in pycdlib.spec.in and create pycdlib.spec with open('python-pycdlib.spec.in', 'r') as spec_in: with open('python-pycdlib.spec', 'w') as spec_out: for line in spec_in: if "@VERSION@" in line: line = line.replace("@VERSION@", VERSION) elif "@RELEASE@" in line: line = line.replace("@RELEASE@", RELEASE) spec_out.write(line) # Run parent constructor _sdist.run(self) setuptools.setup(name='pycdlib', version=VERSION, description='Pure python ISO manipulation library', long_description=io.open('README.md', encoding='UTF-8').read(), url='http://github.com/clalancette/pycdlib', author='Chris Lalancette', author_email='clalancette@gmail.com', license='LGPLv2', classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', 'Natural Language :: English', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', ], keywords='iso9660 iso ecma119 rockridge joliet eltorito udf', packages=['pycdlib'], package_data={'': ['examples/*.py']}, cmdclass={'sdist': sdist}, data_files=[('share/man/man1', ['man/pycdlib-explorer.1', 'man/pycdlib-extract-files.1', 'man/pycdlib-genisoimage.1'])], scripts=['tools/pycdlib-explorer', 'tools/pycdlib-extract-files', 'tools/pycdlib-genisoimage'], ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0734355 pycdlib-1.11.0/tests/0000775000175000017500000000000000000000000017145 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1588550351.0746977 pycdlib-1.11.0/tests/conftest.py0000664000175000017500000000122000000000000021337 0ustar00clalancetteclalancette00000000000000import pytest def pytest_addoption(parser): parser.addoption('--runslow', action='store_true', default=False, help='run slow tests') def pytest_collection_modifyitems(config, items): if config.getoption('--runslow'): # --runslow given in cli: do not skip slow tests return skip_slow = pytest.mark.skip(reason='need --runslow option to run') for item in items: if 'slow' in item.keywords: item.add_marker(skip_slow) def pytest_runtest_teardown(item): if 'tmpdir' in item.funcargs: tmpdir = item.funcargs['tmpdir'] if tmpdir.check(): tmpdir.remove() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0734355 pycdlib-1.11.0/tests/integration/0000775000175000017500000000000000000000000021470 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1599880065.583995 pycdlib-1.11.0/tests/integration/test_common.py0000664000175000017500000122206600000000000024402 0ustar00clalancetteclalancette00000000000000# -*- coding: utf-8 -*- try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import pytest import os import sys import struct sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pycdlib # Technically, Rock Ridge doesn't impose a length limitation on NM (alternate # name) or SL (symlinks). However, in practice, the Linux kernel (at least # ext4) doesn't support any names longer than 255, and the ISO driver doesn't # support any names longer than 248. Thus we stick to 248 for our tests. RR_MAX_FILENAME_LENGTH = 248 def find_executable(executable): paths = os.environ['PATH'].split(os.pathsep) if os.path.isfile(executable): return executable else: for p in paths: f = os.path.join(p, executable) if os.path.isfile(f): return f return None ################################ INTERNAL HELPERS ############################# def internal_check_pvd(pvd, extent, size, ptbl_size, ptbl_location_le, ptbl_location_be): # The length of the system identifer should always be 32. assert(len(pvd.system_identifier) == 32) # The length of the volume identifer should always be 32. assert(len(pvd.volume_identifier) == 32) # The amount of space the ISO takes depends on the files and directories # on the ISO. assert(pvd.space_size == size) # The set size should always be one for these tests. assert(pvd.set_size == 1) # genisoimage only supports setting the sequence number to 1 assert(pvd.seqnum == 1) # genisoimage always produces ISOs with 2048-byte sized logical blocks. assert(pvd.log_block_size == 2048) # The path table size depends on how many directories there are on the ISO. assert(pvd.path_tbl_size == ptbl_size) # The little endian version of the path table should start at the location # passed in (this changes based on how many volume descriptors there are, # e.g. Joliet). assert(pvd.path_table_location_le == ptbl_location_le) # The optional path table location should always be zero. assert(pvd.optional_path_table_location_le == 0) # The big endian version of the path table changes depending on how many # directories there are on the ISO. assert(pvd.path_table_location_be == ptbl_location_be) # The optional path table location should always be zero. assert(pvd.optional_path_table_location_be == 0) # The length of the volume set identifer should always be 128. assert(len(pvd.volume_set_identifier) == 128) # The volume set identifier is always blank here. assert(pvd.volume_set_identifier == b' '*128) # The publisher identifier text should be blank. assert(pvd.publisher_identifier.text == b' '*128) # The preparer identifier text should be blank. assert(pvd.preparer_identifier.text == b' '*128) # The copyright file identifier should be blank. assert(pvd.copyright_file_identifier == b' '*37) # The abstract file identifier should be blank. assert(pvd.abstract_file_identifier == b' '*37) # The bibliographic file identifier should be blank. assert(pvd.bibliographic_file_identifier == b' '*37) # The primary volume descriptor should always have a file structure version # of 1. assert(pvd.file_structure_version == 1) # The length of the application use string should always be 512. assert(len(pvd.application_use) == 512) # The PVD should be where we want it. assert(pvd.extent_location() == extent) def internal_check_enhanced_vd(en_vd, size, ptbl_size, ptbl_location_le, ptbl_location_be): assert(en_vd.version == 2) assert(en_vd.flags == 0) # The length of the system identifer should always be 32. assert(len(en_vd.system_identifier) == 32) # The length of the volume identifer should always be 32. assert(len(en_vd.volume_identifier) == 32) # The amount of space the ISO takes depends on the files and directories # on the ISO. assert(en_vd.space_size == size) assert(en_vd.escape_sequences == b'\x00'*32) assert(en_vd.set_size == 1) assert(en_vd.seqnum == 1) assert(en_vd.log_block_size == 2048) assert(en_vd.path_tbl_size == ptbl_size) # The little endian version of the path table should start at the location # passed in (this changes based on how many volume descriptors there are, # e.g. Joliet). assert(en_vd.path_table_location_le == ptbl_location_le) # The optional path table location should always be zero. assert(en_vd.optional_path_table_location_le == 0) # The big endian version of the path table changes depending on how many # directories there are on the ISO. assert(en_vd.path_table_location_be == ptbl_location_be) # The optional path table location should always be zero. assert(en_vd.optional_path_table_location_be == 0) # The length of the volume set identifer should always be 128. assert(len(en_vd.volume_set_identifier) == 128) # The volume set identifier is always blank here. assert(en_vd.volume_set_identifier == b' '*128) # The publisher identifier text should be blank. assert(en_vd.publisher_identifier.text == b' '*128) # The preparer identifier text should be blank. assert(en_vd.preparer_identifier.text == b' '*128) # The copyright file identifier should be blank. assert(en_vd.copyright_file_identifier == b' '*37) # The abstract file identifier should be blank. assert(en_vd.abstract_file_identifier == b' '*37) # The bibliographic file identifier should be blank. assert(en_vd.bibliographic_file_identifier == b' '*37) # The primary volume descriptor should always have a file structure version # of 1. assert(en_vd.file_structure_version == 2) def internal_check_eltorito(iso, boot_catalog_extent, load_rba, media_type, system_type, bootable, platform_id): # Now check the Eltorito Boot Record. # We support only one boot record for now. assert(len(iso.brs) == 1) eltorito = iso.brs[0] # The boot_system_identifier for El Torito should always be a space-padded # version of "EL TORITO SPECIFICATION". assert(eltorito.boot_system_identifier == b'EL TORITO SPECIFICATION'.ljust(32, b'\x00')) # The boot identifier should always be 32 zeros. assert(eltorito.boot_identifier == b'\x00'*32) # The boot_system_use field should always contain the boot catalog extent # encoded as a string. assert(eltorito.boot_system_use[:4] == struct.pack(' 0: assert(ptr.parent_directory_num == parent) assert(ptr.directory_identifier == name) def internal_check_empty_directory(dirrecord, name, dr_len, extent, rr, hidden): internal_check_dir_record(dirrecord, num_children=2, name=name, dr_len=dr_len, extent_location=extent, rr=rr, rr_name=b'dir1', rr_links=2, xa=False, hidden=hidden, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dirrecord.children[1], rr=rr, rr_nlinks=3, xa=False, rr_onetwelve=False) def internal_check_file(dirrecord, name, dr_len, loc, datalen, hidden, multi_extent=False): assert(len(dirrecord.children) == 0) assert(dirrecord.isdir == False) assert(dirrecord.is_root == False) assert(dirrecord.file_ident == name) if dr_len is not None: assert(dirrecord.dr_len == dr_len) if loc is not None: assert(dirrecord.extent_location() == loc) if hidden: assert(dirrecord.file_flags == 1) elif multi_extent: assert(dirrecord.file_flags == 128) else: assert(dirrecord.file_flags == 0) assert(dirrecord.get_data_length() == datalen) def internal_generate_inorder_names(numdirs): tmp = [] for i in range(1, 1+numdirs): tmp.append(b'DIR' + bytes(str(i).encode('ascii'))) names = sorted(tmp) names.insert(0, None) names.insert(0, None) return names def internal_generate_joliet_inorder_names(numdirs): tmp = [] for i in range(1, 1+numdirs): name = 'dir' + str(i) tmp.append(bytes(name.encode('utf-16_be'))) names = sorted(tmp) names.insert(0, None) names.insert(0, None) return names def internal_generate_udf_inorder_names(numdirs): tmp = [] for i in range(1, 1+numdirs): tmp.append(b'dir' + bytes(str(i).encode('ascii'))) names = sorted(tmp) names.insert(0, None) return names def internal_check_dir_record(dir_record, num_children, name, dr_len, extent_location, rr, rr_name, rr_links, xa, hidden, is_cl_record, datalen, relocated): # The directory should have the number of children passed in. assert(len(dir_record.children) == num_children) # The directory should be a directory. if is_cl_record: assert(dir_record.isdir == False) else: assert(dir_record.isdir == True) # The directory should not be the root. assert(dir_record.is_root == False) # The directory should have an ISO9660 mangled name the same as passed in. assert(dir_record.file_ident == name) # The directory record should have a dr_len as passed in. if dr_len is not None: assert(dir_record.dr_len == dr_len) # The 'dir1' directory record should be at the extent passed in. if extent_location is not None: assert(dir_record.extent_location() == extent_location) if is_cl_record: assert(dir_record.file_flags == 0) else: if hidden: assert(dir_record.file_flags == 3) else: assert(dir_record.file_flags == 2) if rr: assert(dir_record.rock_ridge.dr_entries.sp_record == None) assert(dir_record.rock_ridge.dr_entries.rr_record != None) if is_cl_record: assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0x99) elif relocated: assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0xC9) else: assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0x89) px_record = None if dir_record.rock_ridge.dr_entries.px_record is not None: px_record = dir_record.rock_ridge.dr_entries.px_record elif dir_record.rock_ridge.ce_entries.px_record is not None: px_record = dir_record.rock_ridge.ce_entries.px_record assert(px_record is not None) assert(px_record.posix_file_mode == 0o040555) assert(px_record.posix_file_links == rr_links) assert(px_record.posix_user_id == 0) assert(px_record.posix_group_id == 0) assert(px_record.posix_serial_number == 0) assert(dir_record.rock_ridge.dr_entries.er_record == None) assert(dir_record.rock_ridge.dr_entries.es_records == []) assert(dir_record.rock_ridge.dr_entries.pn_record == None) assert(dir_record.rock_ridge.dr_entries.sl_records == []) assert(len(dir_record.rock_ridge.dr_entries.nm_records) > 0) assert(dir_record.rock_ridge.name() == rr_name) if is_cl_record: assert(dir_record.rock_ridge.dr_entries.cl_record != None) else: assert(dir_record.rock_ridge.dr_entries.cl_record == None) assert(dir_record.rock_ridge.dr_entries.pl_record == None) if dir_record.rock_ridge.dr_entries.tf_record is not None: tf_record = dir_record.rock_ridge.dr_entries.tf_record elif dir_record.rock_ridge.ce_entries.tf_record is not None: tf_record = dir_record.rock_ridge.ce_entries.tf_record assert(tf_record is not None) assert(tf_record.creation_time == None) assert(type(tf_record.access_time) == pycdlib.dates.DirectoryRecordDate) assert(type(tf_record.modification_time) == pycdlib.dates.DirectoryRecordDate) assert(type(tf_record.attribute_change_time) == pycdlib.dates.DirectoryRecordDate) assert(tf_record.backup_time == None) assert(tf_record.expiration_time == None) assert(tf_record.effective_time == None) assert(dir_record.rock_ridge.dr_entries.sf_record == None) if relocated: assert(dir_record.rock_ridge.dr_entries.re_record != None) else: assert(dir_record.rock_ridge.dr_entries.re_record == None) # The 'dir1' directory record should have a valid 'dot' record. if num_children > 0: internal_check_dot_dir_record(dir_record.children[0], rr=rr, rr_nlinks=rr_links, first_dot=False, xa=xa, datalen=datalen, rr_onetwelve=False) def internal_check_joliet_root_dir_record(jroot_dir_record, num_children, data_length, extent_location): # The jroot_dir_record directory record length should be exactly 34. assert(jroot_dir_record.dr_len == 34) # We don't support xattrs at the moment, so it should always be 0. assert(jroot_dir_record.xattr_len == 0) # Make sure the root directory record starts at the extent we expect. assert(jroot_dir_record.extent_location() == extent_location) # We don't check the extent_location_le or extent_location_be, since I # don't really understand the algorithm by which genisoimage generates them. # The length of the root directory record depends on the number of entries # there are at the top level. assert(jroot_dir_record.get_data_length() == data_length) # We skip checking the date since it changes all of the time. # The file flags for the root dir record should always be 0x2 (DIRECTORY bit). assert(jroot_dir_record.file_flags == 2) # The file unit size should always be zero. assert(jroot_dir_record.file_unit_size == 0) # The interleave gap size should always be zero. assert(jroot_dir_record.interleave_gap_size == 0) # The sequence number should always be one. assert(jroot_dir_record.seqnum == 1) # The len_fi should always be one. assert(jroot_dir_record.len_fi == 1) # Everything after here is derived data. # The root directory should be the, erm, root. assert(jroot_dir_record.is_root == True) # The root directory record should also be a directory. assert(jroot_dir_record.isdir == True) # The root directory record should have a name of the byte 0. assert(jroot_dir_record.file_ident == b'\x00') assert(jroot_dir_record.parent == None) assert(jroot_dir_record.rock_ridge == None) # The number of children the root directory record has depends on the number # of files+directories there are at the top level. assert(len(jroot_dir_record.children) == num_children) # Now check the 'dot' directory record. internal_check_dot_dir_record(jroot_dir_record.children[0], rr=False, rr_nlinks=0, first_dot=False, xa=False, datalen=2048, rr_onetwelve=False) # Now check the 'dotdot' directory record. internal_check_dotdot_dir_record(jroot_dir_record.children[1], rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) def internal_check_rr_longname(iso, dir_record, extent, letter): internal_check_file(dir_record, name=letter.upper()*8+b'.;1', dr_len=None, loc=extent, datalen=3, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/'+letter.decode('ascii').upper()*8+'.;1', contents=letter*2+b'\n', which='iso_path') # Now check rock ridge extensions. assert(dir_record.rock_ridge.dr_entries.sp_record == None) assert(dir_record.rock_ridge.dr_entries.rr_record != None) assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0x89) assert(dir_record.rock_ridge.dr_entries.ce_record != None) assert(dir_record.rock_ridge.ce_entries.sp_record == None) assert(dir_record.rock_ridge.ce_entries.rr_record == None) assert(dir_record.rock_ridge.ce_entries.ce_record == None) assert(dir_record.rock_ridge.ce_entries.px_record != None) assert(dir_record.rock_ridge.ce_entries.px_record.posix_file_mode == 0o0100444) assert(dir_record.rock_ridge.ce_entries.px_record.posix_file_links == 1) assert(dir_record.rock_ridge.ce_entries.px_record.posix_user_id == 0) assert(dir_record.rock_ridge.ce_entries.px_record.posix_group_id == 0) assert(dir_record.rock_ridge.ce_entries.px_record.posix_serial_number == 0) assert(dir_record.rock_ridge.ce_entries.er_record == None) assert(dir_record.rock_ridge.ce_entries.es_records == []) assert(dir_record.rock_ridge.ce_entries.pn_record == None) assert(dir_record.rock_ridge.ce_entries.sl_records == []) assert(len(dir_record.rock_ridge.ce_entries.nm_records) > 0) assert(dir_record.rock_ridge.ce_entries.nm_records[0].posix_name_flags == 0) assert(dir_record.rock_ridge.ce_entries.cl_record == None) assert(dir_record.rock_ridge.ce_entries.pl_record == None) assert(dir_record.rock_ridge.ce_entries.tf_record != None) assert(type(dir_record.rock_ridge.ce_entries.tf_record.access_time) == pycdlib.dates.DirectoryRecordDate) assert(type(dir_record.rock_ridge.ce_entries.tf_record.modification_time) == pycdlib.dates.DirectoryRecordDate) assert(type(dir_record.rock_ridge.ce_entries.tf_record.attribute_change_time) == pycdlib.dates.DirectoryRecordDate) assert(dir_record.rock_ridge.ce_entries.sf_record == None) assert(dir_record.rock_ridge.ce_entries.re_record == None) assert(dir_record.rock_ridge.dr_entries.px_record == None) assert(dir_record.rock_ridge.dr_entries.er_record == None) assert(dir_record.rock_ridge.dr_entries.es_records == []) assert(dir_record.rock_ridge.dr_entries.pn_record == None) assert(dir_record.rock_ridge.dr_entries.sl_records == []) assert(len(dir_record.rock_ridge.dr_entries.nm_records) > 0) assert(dir_record.rock_ridge.dr_entries.nm_records[0].posix_name_flags == 1) assert(dir_record.rock_ridge.name() == letter*RR_MAX_FILENAME_LENGTH) assert(dir_record.rock_ridge.dr_entries.cl_record == None) assert(dir_record.rock_ridge.dr_entries.pl_record == None) assert(dir_record.rock_ridge.dr_entries.tf_record == None) assert(dir_record.rock_ridge.dr_entries.sf_record == None) assert(dir_record.rock_ridge.dr_entries.re_record == None) internal_check_file_contents(iso, path='/'+letter.decode('ascii')*RR_MAX_FILENAME_LENGTH, contents=letter*2+b'\n', which='rr_path') def internal_check_rr_file(dir_record, name): assert(dir_record.rock_ridge._initialized == True) assert(dir_record.rock_ridge.dr_entries.sp_record == None) assert(dir_record.rock_ridge.dr_entries.rr_record != None) assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0x89) assert(dir_record.rock_ridge.dr_entries.ce_record == None) assert(dir_record.rock_ridge.dr_entries.px_record != None) assert(dir_record.rock_ridge.dr_entries.px_record.posix_file_mode == 0o0100444) assert(dir_record.rock_ridge.dr_entries.px_record.posix_file_links == 1) assert(dir_record.rock_ridge.dr_entries.px_record.posix_user_id == 0) assert(dir_record.rock_ridge.dr_entries.px_record.posix_group_id == 0) assert(dir_record.rock_ridge.dr_entries.px_record.posix_serial_number == 0) assert(dir_record.rock_ridge.dr_entries.er_record == None) assert(dir_record.rock_ridge.dr_entries.es_records == []) assert(dir_record.rock_ridge.dr_entries.pn_record == None) assert(dir_record.rock_ridge.dr_entries.sl_records == []) assert(len(dir_record.rock_ridge.dr_entries.nm_records) > 0) assert(dir_record.rock_ridge.dr_entries.nm_records[0].posix_name_flags == 0) assert(dir_record.rock_ridge.dr_entries.nm_records[0].posix_name == name) assert(dir_record.rock_ridge.dr_entries.cl_record == None) assert(dir_record.rock_ridge.dr_entries.pl_record == None) assert(dir_record.rock_ridge.dr_entries.tf_record != None) assert(dir_record.rock_ridge.dr_entries.tf_record.creation_time == None) assert(type(dir_record.rock_ridge.dr_entries.tf_record.access_time) == pycdlib.dates.DirectoryRecordDate) assert(type(dir_record.rock_ridge.dr_entries.tf_record.modification_time) == pycdlib.dates.DirectoryRecordDate) assert(type(dir_record.rock_ridge.dr_entries.tf_record.attribute_change_time) == pycdlib.dates.DirectoryRecordDate) assert(dir_record.rock_ridge.dr_entries.tf_record.backup_time == None) assert(dir_record.rock_ridge.dr_entries.tf_record.expiration_time == None) assert(dir_record.rock_ridge.dr_entries.tf_record.effective_time == None) assert(dir_record.rock_ridge.dr_entries.sf_record == None) assert(dir_record.rock_ridge.dr_entries.re_record == None) def internal_check_rr_symlink(dir_record, name, dr_len, comps): # The 'sym' file should not have any children. assert(len(dir_record.children) == 0) # The 'sym' file should not be a directory. assert(dir_record.isdir == False) # The 'sym' file should not be the root. assert(dir_record.is_root == False) # The 'sym' file should have an ISO9660 mangled name of 'SYM.;1'. assert(dir_record.file_ident == name) # The 'sym' directory record should have a length of 126. assert(dir_record.dr_len == dr_len) assert(dir_record.file_flags == 0) # Now check rock ridge extensions. assert(dir_record.rock_ridge._initialized == True) assert(dir_record.rock_ridge.dr_entries.sp_record == None) assert(dir_record.rock_ridge.dr_entries.rr_record != None) assert(dir_record.rock_ridge.dr_entries.rr_record.rr_flags == 0x8d) assert(dir_record.rock_ridge.dr_entries.px_record != None) assert(dir_record.rock_ridge.dr_entries.px_record.posix_file_mode == 0o0120555) assert(dir_record.rock_ridge.dr_entries.px_record.posix_file_links == 1) assert(dir_record.rock_ridge.dr_entries.px_record.posix_user_id == 0) assert(dir_record.rock_ridge.dr_entries.px_record.posix_group_id == 0) assert(dir_record.rock_ridge.dr_entries.px_record.posix_serial_number == 0) assert(dir_record.rock_ridge.dr_entries.er_record == None) assert(dir_record.rock_ridge.dr_entries.es_records == []) assert(dir_record.rock_ridge.dr_entries.pn_record == None) assert(dir_record.rock_ridge.is_symlink() == True) split = dir_record.rock_ridge.symlink_path().split(b'/') assert(len(split) == len(comps)) for index,comp in enumerate(comps): assert(comps[index] == split[index]) assert(len(dir_record.rock_ridge.dr_entries.nm_records) > 0) assert(dir_record.rock_ridge.dr_entries.nm_records[0].posix_name_flags == 0) assert(dir_record.rock_ridge.dr_entries.nm_records[0].posix_name == b'sym') assert(dir_record.rock_ridge.dr_entries.cl_record == None) assert(dir_record.rock_ridge.dr_entries.pl_record == None) tf_record = None if dir_record.rock_ridge.dr_entries.tf_record is not None: tf_record = dir_record.rock_ridge.dr_entries.tf_record elif dir_record.rock_ridge.ce_entries.tf_record is not None: tf_record = dir_record.rock_ridge.ce_entries.tf_record assert(tf_record != None) assert(tf_record.creation_time == None) assert(type(tf_record.access_time) == pycdlib.dates.DirectoryRecordDate) assert(type(tf_record.modification_time) == pycdlib.dates.DirectoryRecordDate) assert(type(tf_record.attribute_change_time) == pycdlib.dates.DirectoryRecordDate) assert(tf_record.backup_time == None) assert(tf_record.expiration_time == None) assert(tf_record.effective_time == None) assert(dir_record.rock_ridge.dr_entries.sf_record == None) assert(dir_record.rock_ridge.dr_entries.re_record == None) def internal_check_udf_tag(tag, ident, location): assert(tag.tag_ident == ident) assert(tag.desc_version == 2) assert(tag.tag_serial_number == 0) if location is not None: assert(tag.tag_location == location) def internal_check_udf_anchor(anchor, location): assert(anchor.extent_location() == location) internal_check_udf_tag(anchor.desc_tag, ident=2, location=location) assert(anchor.main_vd.extent_length == 32768) assert(anchor.main_vd.extent_location == 32) assert(anchor.reserve_vd.extent_length == 32768) assert(anchor.reserve_vd.extent_location == 48) def internal_check_udf_entity(entity, flags, ident, suffix): assert(entity.flags == flags) if ident is not None: full = ident + b'\x00' * (23 - len(ident)) assert(entity.identifier == full) full = suffix + b'\x00' * (8 - len(suffix)) assert(entity.suffix == full) def internal_check_udf_pvd(pvd, location): assert(pvd.extent_location() == location) internal_check_udf_tag(pvd.desc_tag, ident=1, location=location) assert(pvd.vol_desc_seqnum == 0) assert(pvd.desc_num == 0) assert(pvd.vol_ident == b'\x08CDROM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06') assert(pvd.desc_char_set.set_type == 0) assert(pvd.desc_char_set.set_information == b'OSTA Compressed Unicode\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(pvd.explanatory_char_set.set_type == 0) assert(pvd.explanatory_char_set.set_information == b'OSTA Compressed Unicode\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(pvd.vol_abstract.extent_length == 0) assert(pvd.vol_abstract.extent_location == 0) assert(pvd.vol_copyright.extent_length == 0) assert(pvd.vol_copyright.extent_location == 0) internal_check_udf_entity(pvd.app_ident, 0, b'\x00', b'') internal_check_udf_entity(pvd.impl_ident, 0, None, b'') assert(pvd.implementation_use == b'\x00' * 64) assert(pvd.predecessor_vol_desc_location == 0) def internal_check_udf_impl_use(impl_use, location): assert(impl_use.extent_location() == location) internal_check_udf_tag(impl_use.desc_tag, ident=4, location=location) assert(impl_use.vol_desc_seqnum == 1) assert(impl_use.impl_ident.identifier == b'*UDF LV Info\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(impl_use.impl_use.char_set.set_type == 0) assert(impl_use.impl_use.char_set.set_information == b'OSTA Compressed Unicode' + b'\x00' * 40) assert(impl_use.impl_use.log_vol_ident == b'\x08CDROM' + b'\x00' * 121 + b'\x06') assert(impl_use.impl_use.lv_info1 == b'\x00' * 36) assert(impl_use.impl_use.lv_info2 == b'\x00' * 36) assert(impl_use.impl_use.lv_info3 == b'\x00' * 36) internal_check_udf_entity(impl_use.impl_ident, 0, b'*UDF LV Info', b'\x02\x01') assert(impl_use.impl_use.impl_use == b'\x00' * 128) def internal_check_udf_partition(partition, location, length): assert(partition.extent_location() == location) internal_check_udf_tag(partition.desc_tag, ident=5, location=location) assert(partition.vol_desc_seqnum == 2) assert(partition.part_flags == 1) assert(partition.part_num == 0) assert(partition.part_contents.flags == 2) internal_check_udf_entity(partition.part_contents, 2, b'+NSR02', b'') assert(partition.access_type == 1) assert(partition.part_start_location == 257) assert(partition.part_length == length) internal_check_udf_entity(partition.impl_ident, 0, None, b'') assert(partition.implementation_use == b'\x00' * 128) def internal_check_udf_longad(longad, size, blocknum, abs_blocknum): assert(longad.extent_length == size) if blocknum is not None: assert(longad.log_block_num == blocknum) assert(longad.part_ref_num == 0) if abs_blocknum is not None: assert(longad.impl_use == b'\x00\x00' + struct.pack(' 0: namelen += 1 assert(fi_desc.len_fi == namelen) internal_check_udf_longad(fi_desc.icb, size=2048, blocknum=blocknum, abs_blocknum=abs_blocknum) assert(fi_desc.len_impl_use == 0) assert(fi_desc.impl_use == b'') assert(fi_desc.fi == name) if isparent: assert(fi_desc.file_entry is None) else: assert(fi_desc.file_entry is not None) assert(fi_desc.isdir == isdir) assert(fi_desc.isparent == isparent) def internal_check_boot_info_table(bi_table, vd_extent, inode_extent, orig_len, csum): assert(bi_table is not None) assert(bi_table.vd.extent_location() == vd_extent) assert(bi_table.inode.extent_location() == inode_extent) assert(bi_table.orig_len == orig_len) assert(bi_table.csum == csum) ######################## EXTERNAL CHECKERS ##################################### def check_nofiles(iso, filesize): assert(filesize == 49152) internal_check_pvd(iso.pvd, extent=16, size=24, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) assert(not iso.has_rock_ridge()) assert(not iso.has_joliet()) assert(not iso.has_udf()) # Check to make sure accessing a missing file results in an exception. with pytest.raises(pycdlib.pycdlibexception.PyCdlibException): iso.get_file_from_iso_fp(BytesIO(), iso_path='/FOO.;1') def check_onefile(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_onedir(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=24, rr=False, hidden=False) def check_twofiles(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'bar\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_twodirs(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=30, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) aa_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(aa_record.ptr, name=b'AA', len_di=2, loc=None, parent=1) internal_check_empty_directory(aa_record, name=b'AA', dr_len=36, extent=None, rr=False, hidden=False) bb_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(bb_record.ptr, name=b'BB', len_di=2, loc=None, parent=1) internal_check_empty_directory(bb_record, name=b'BB', dr_len=36, extent=None, rr=False, hidden=False) def check_onefileonedir(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=24, rr=False, hidden=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_onefile_onedirwithfile(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=24, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(dir1_record.children[2], name=b'BAR.;1', dr_len=40, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/BAR.;1', contents=b'bar\n', which='iso_path') def check_twoextentfile(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) # Now check the file at the root. It should have a name of BIGFILE.;1, it # should have a directory record length of 44, it should start at extent 24, # and its contents should be the bytes 0x0-0xff, repeating 8 times plus one. outstr = b'' for j in range(0, 8): for i in range(0, 256): outstr += struct.pack('=B', i) outstr += struct.pack('=B', 0) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BIGFILE.;1', dr_len=44, loc=24, datalen=2049, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BIGFILE.;1', contents=outstr, which='iso_path') def check_twoleveldeepdir(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=38, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=24, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) subdir1_record = dir1_record.children[2] internal_check_ptr(subdir1_record.ptr, name=b'SUBDIR1', len_di=7, loc=25, parent=2) internal_check_empty_directory(subdir1_record, name=b'SUBDIR1', dr_len=40, extent=25, rr=False, hidden=False) def check_tendirs(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=132, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=12, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) # The rest of the path table records will be checked by the loop below. names = internal_generate_inorder_names(10) for index in range(2, 2+10): dir_record = iso.pvd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) internal_check_empty_directory(dir_record, name=names[index], dr_len=38, extent=None, rr=False, hidden=False) def check_dirs_overflow_ptr_extent(iso, filesize): assert(filesize == 671744) internal_check_pvd(iso.pvd, extent=16, size=328, ptbl_size=4122, ptbl_location_le=19, ptbl_location_be=23) internal_check_terminator(iso.vdsts, extent=17) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=297, data_length=12288, extent_location=27, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=27, parent=1) # The rest of the path table records will be checked by the loop below. names = internal_generate_inorder_names(295) for index in range(2, 2+295): dir_record = iso.pvd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) internal_check_empty_directory(dir_record, name=names[index], dr_len=33 + len(names[index]) + (1 - (len(names[index]) % 2)), extent=None, rr=False, hidden=False) def check_dirs_just_short_ptr_extent(iso, filesize): assert(filesize == 659456) internal_check_pvd(iso.pvd, extent=16, size=322, ptbl_size=4094, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=295, data_length=12288, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) # The rest of the path table records will be checked by the loop below. names = internal_generate_inorder_names(293) for index in range(2, 2+293): dir_record = iso.pvd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) internal_check_empty_directory(dir_record, name=names[index], dr_len=33 + len(names[index]) + (1 - (len(names[index]) % 2)), extent=None, rr=False, hidden=False) def check_twoleveldeepfile(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=38, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=24, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) subdir1_record = dir1_record.children[2] internal_check_ptr(subdir1_record.ptr, name=b'SUBDIR1', len_di=7, loc=25, parent=2) internal_check_dir_record(subdir1_record, num_children=3, name=b'SUBDIR1', dr_len=40, extent_location=25, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(subdir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(subdir1_record.children[2], name=b'FOO.;1', dr_len=40, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/SUBDIR1/FOO.;1', contents=b'foo\n', which='iso_path') def check_joliet_nofiles(iso, filesize): assert(filesize == 61440) internal_check_pvd(iso.pvd, extent=16, size=30, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=30, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=29) assert(not iso.has_rock_ridge()) assert(iso.has_joliet()) assert(not iso.has_udf()) def check_joliet_onedir(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=26, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=29, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=29, rr=False, hidden=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=31, parent=1) internal_check_empty_directory(joliet_dir1_record, name='dir1'.encode('utf-16_be'), dr_len=42, extent=31, rr=False, hidden=False) def check_joliet_onefile(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_joliet_onefileonedir(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=26, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=29, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=29, rr=False, hidden=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=31, parent=1) internal_check_empty_directory(joliet_dir1_record, name='dir1'.encode('utf-16_be'), dr_len=42, extent=31, rr=False, hidden=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=32, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='foo'.encode('utf-16_be'), dr_len=40, loc=32, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_eltorito_nofiles(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_eltorito_twofile(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[4], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[2], name=b'AA.;1', dr_len=38, loc=27, datalen=3, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/AA.;1', contents=b'aa\n', which='iso_path') def check_rr_nofiles(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) assert(iso.has_rock_ridge()) assert(not iso.has_joliet()) assert(not iso.has_udf()) def check_rr_onefile(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) foo_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') def check_rr_twofile(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) bar_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(bar_dir_record, name=b'BAR.;1', dr_len=116, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'bar\n', which='iso_path') internal_check_rr_file(bar_dir_record, name=b'bar') internal_check_file_contents(iso, path='/bar', contents=b'bar\n', which='rr_path') foo_dir_record = iso.pvd.root_dir_record.children[3] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') def check_rr_onefileonedir(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=114, extent=24, rr=True, hidden=False) foo_dir_record = iso.pvd.root_dir_record.children[3] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') def check_rr_onefileonedirwithfile(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=114, extent_location=24, rr=True, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) foo_dir_record = iso.pvd.root_dir_record.children[3] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') bar_dir_record = dir1_record.children[2] internal_check_file(bar_dir_record, name=b'BAR.;1', dr_len=116, loc=27, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/BAR.;1', contents=b'bar\n', which='iso_path') internal_check_rr_file(bar_dir_record, name=b'bar') internal_check_file_contents(iso, path='/dir1/bar', contents=b'bar\n', which='rr_path') def check_rr_symlink(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) foo_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=126, comps=[b'foo']) with pytest.raises(pycdlib.pycdlibexception.PyCdlibException): internal_check_file_contents(iso, path='/sym', contents=b'foo\n', which='iso_path') def check_rr_symlink2(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=114, extent_location=24, rr=True, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) foo_dir_record = dir1_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=26, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/dir1/foo', contents=b'foo\n', which='rr_path') # Now check the rock ridge symlink. It should have a directory record # length of 132, and the symlink components should be 'dir1' and 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=132, comps=[b'dir1', b'foo']) def check_rr_symlink_dot(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) # Now check the rock ridge symlink. It should have a directory record # length of 132, and the symlink components should be 'dir1' and 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=122, comps=[b'.']) def check_rr_symlink_dotdot(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) # Now check the rock ridge symlink. It should have a directory record # length of 132, and the symlink components should be 'dir1' and 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=122, comps=[b'..']) def check_rr_symlink_broken(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) # Now check the rock ridge symlink. It should have a directory record # length of 132, and the symlink components should be 'dir1' and 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=126, comps=[b'foo']) def check_alternating_subdir(iso, filesize): assert(filesize == 61440) internal_check_pvd(iso.pvd, extent=16, size=30, ptbl_size=30, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=6, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) aa_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(aa_record.ptr, name=b'AA', len_di=2, loc=None, parent=1) internal_check_dir_record(aa_record, num_children=3, name=b'AA', dr_len=36, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(aa_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) bb_dir_record = iso.pvd.root_dir_record.children[3] internal_check_file(bb_dir_record, name=b'BB.;1', dr_len=38, loc=26, datalen=3, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BB.;1', contents=b'bb\n', which='iso_path') cc_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(cc_record.ptr, name=b'CC', len_di=2, loc=None, parent=1) internal_check_dir_record(cc_record, num_children=3, name=b'CC', dr_len=36, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(cc_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) dd_dir_record = iso.pvd.root_dir_record.children[5] internal_check_file(dd_dir_record, name=b'DD.;1', dr_len=38, loc=27, datalen=3, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DD.;1', contents=b'dd\n', which='iso_path') sub1_dir_record = aa_record.children[2] internal_check_file(sub1_dir_record, name=b'SUB1.;1', dr_len=40, loc=None, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/AA/SUB1.;1', contents=b'sub1\n', which='iso_path') sub2_dir_record = cc_record.children[2] internal_check_file(sub2_dir_record, name=b'SUB2.;1', dr_len=40, loc=None, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/CC/SUB2.;1', contents=b'sub2\n', which='iso_path') def check_rr_verylongname(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_rr_longname(iso, dir_record=iso.pvd.root_dir_record.children[2], extent=26, letter=b'a') def check_rr_verylongname_joliet(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) internal_check_rr_longname(iso, dir_record=iso.pvd.root_dir_record.children[2], extent=32, letter=b'a') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name=('a'*64).encode('utf-16_be'), dr_len=162, loc=32, datalen=3, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/'+'a'*64, contents=b'aa\n', which='joliet_path') def check_rr_manylongname(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=9, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) aa_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_longname(iso, dir_record=aa_dir_record, extent=26, letter=b'a') bb_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_longname(iso, dir_record=bb_dir_record, extent=27, letter=b'b') cc_dir_record = iso.pvd.root_dir_record.children[4] internal_check_rr_longname(iso, dir_record=cc_dir_record, extent=28, letter=b'c') dd_dir_record = iso.pvd.root_dir_record.children[5] internal_check_rr_longname(iso, dir_record=dd_dir_record, extent=29, letter=b'd') ee_dir_record = iso.pvd.root_dir_record.children[6] internal_check_rr_longname(iso, dir_record=ee_dir_record, extent=30, letter=b'e') ff_dir_record = iso.pvd.root_dir_record.children[7] internal_check_rr_longname(iso, dir_record=ff_dir_record, extent=31, letter=b'f') gg_dir_record = iso.pvd.root_dir_record.children[8] internal_check_rr_longname(iso, dir_record=gg_dir_record, extent=32, letter=b'g') def check_rr_manylongname2(iso, filesize): assert(filesize == 71680) internal_check_pvd(iso.pvd, extent=16, size=35, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=10, data_length=4096, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) aa_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_longname(iso, dir_record=aa_dir_record, extent=27, letter=b'a') bb_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_longname(iso, dir_record=bb_dir_record, extent=28, letter=b'b') cc_dir_record = iso.pvd.root_dir_record.children[4] internal_check_rr_longname(iso, dir_record=cc_dir_record, extent=29, letter=b'c') dd_dir_record = iso.pvd.root_dir_record.children[5] internal_check_rr_longname(iso, dir_record=dd_dir_record, extent=30, letter=b'd') ee_dir_record = iso.pvd.root_dir_record.children[6] internal_check_rr_longname(iso, dir_record=ee_dir_record, extent=31, letter=b'e') ff_dir_record = iso.pvd.root_dir_record.children[7] internal_check_rr_longname(iso, dir_record=ff_dir_record, extent=32, letter=b'f') gg_dir_record = iso.pvd.root_dir_record.children[8] internal_check_rr_longname(iso, dir_record=gg_dir_record, extent=33, letter=b'g') hh_dir_record = iso.pvd.root_dir_record.children[9] internal_check_rr_longname(iso, dir_record=hh_dir_record, extent=34, letter=b'h') def check_rr_verylongnameandsymlink(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_rr_longname(iso, dir_record=iso.pvd.root_dir_record.children[2], extent=26, letter=b'a') def check_joliet_and_rr_nofiles(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=29) def check_joliet_and_rr_onefile(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=116, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_joliet_and_rr_onedir(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=26, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=29, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=114, extent_location=29, rr=True, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=31, parent=1) internal_check_dir_record(joliet_dir1_record, num_children=2, name='dir1'.encode('utf-16_be'), dr_len=42, extent_location=31, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(joliet_dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) def check_rr_and_eltorito_nofiles(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_rr_and_eltorito_onefile(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=24, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'FOO.;1', dr_len=116, loc=28, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_rr_and_eltorito_onedir(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=27, load_rba=28, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=24, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=25, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=114, extent_location=25, rr=True, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=27, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=28, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_joliet_and_eltorito_nofiles(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=31, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=31, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_isohybrid(iso, filesize): assert(filesize == 1048576) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) # Now check out the isohybrid stuff. assert(iso.isohybrid_mbr.geometry_heads == 64) assert(iso.isohybrid_mbr.geometry_sectors == 32) assert(iso.isohybrid_mbr.bhead == 0) assert(iso.isohybrid_mbr.bsect == 1) assert(iso.isohybrid_mbr.bcyle == 0) assert(iso.isohybrid_mbr.ptype == 23) assert(iso.isohybrid_mbr.ehead == 63) assert(iso.isohybrid_mbr.part_offset == 0) assert(not iso.isohybrid_mbr.efi) assert(not iso.isohybrid_mbr.mac) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'ISOLINUX.BIN;1', dr_len=48, loc=26, datalen=68, hidden=False, multi_extent=False) def check_isohybrid_uefi(iso, filesize): assert(filesize == 1048576) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=None, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) # Now check out the isohybrid stuff. assert(iso.isohybrid_mbr.geometry_heads == 64) assert(iso.isohybrid_mbr.geometry_sectors == 32) assert(iso.isohybrid_mbr.bhead == 0) assert(iso.isohybrid_mbr.bsect == 1) assert(iso.isohybrid_mbr.bcyle == 0) assert(iso.isohybrid_mbr.ptype == 0) assert(iso.isohybrid_mbr.ehead == 63) assert(iso.isohybrid_mbr.part_offset == 0) assert(iso.isohybrid_mbr.efi) assert(iso.isohybrid_mbr.efi_lba == 26) assert(iso.isohybrid_mbr.efi_count == 4) assert(iso.isohybrid_mbr.primary_gpt.is_primary) assert(iso.isohybrid_mbr.primary_gpt.header.current_lba == 1) assert(iso.isohybrid_mbr.primary_gpt.header.backup_lba == 2047) assert(iso.isohybrid_mbr.primary_gpt.header.first_usable_lba == 34) assert(iso.isohybrid_mbr.primary_gpt.header.last_usable_lba == 2014) assert(iso.isohybrid_mbr.primary_gpt.header.partition_entries_lba == 2) assert(iso.isohybrid_mbr.primary_gpt.header.num_parts == 128) assert(iso.isohybrid_mbr.primary_gpt.header.size_of_partition_entries == 128) assert(len(iso.isohybrid_mbr.primary_gpt.parts) == 2) assert(iso.isohybrid_mbr.primary_gpt.parts[0].first_lba == 0) assert(iso.isohybrid_mbr.primary_gpt.parts[0].last_lba == 111) assert(iso.isohybrid_mbr.primary_gpt.parts[0].attributes == b'\x00'*8) assert(iso.isohybrid_mbr.primary_gpt.parts[0].name == 'ISOHybrid ISO') assert(iso.isohybrid_mbr.primary_gpt.parts[1].first_lba == 104) assert(iso.isohybrid_mbr.primary_gpt.parts[1].last_lba == 107) assert(iso.isohybrid_mbr.primary_gpt.parts[1].attributes == b'\x00'*8) assert(iso.isohybrid_mbr.primary_gpt.parts[1].name == 'ISOHybrid') assert(not iso.isohybrid_mbr.mac) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'EFIBOOT.IMG;1', dr_len=46, loc=None, datalen=1, hidden=False, multi_extent=False) def check_isohybrid_mac_uefi(iso, filesize): assert(filesize == 1048576) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=None, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=6, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) # Now check out the isohybrid stuff. assert(iso.isohybrid_mbr.geometry_heads == 64) assert(iso.isohybrid_mbr.geometry_sectors == 32) assert(iso.isohybrid_mbr.bhead == 0) assert(iso.isohybrid_mbr.bsect == 1) assert(iso.isohybrid_mbr.bcyle == 0) assert(iso.isohybrid_mbr.ptype == 0) assert(iso.isohybrid_mbr.ehead == 63) assert(iso.isohybrid_mbr.part_offset == 0) assert(iso.isohybrid_mbr.efi) assert(iso.isohybrid_mbr.efi_lba == 26) assert(iso.isohybrid_mbr.efi_count == 4) assert(iso.isohybrid_mbr.primary_gpt.is_primary) assert(iso.isohybrid_mbr.primary_gpt.header.current_lba == 1) assert(iso.isohybrid_mbr.primary_gpt.header.backup_lba == 2047) assert(iso.isohybrid_mbr.primary_gpt.header.first_usable_lba == 48) assert(iso.isohybrid_mbr.primary_gpt.header.last_usable_lba == 2014) assert(iso.isohybrid_mbr.primary_gpt.header.partition_entries_lba == 16) assert(iso.isohybrid_mbr.primary_gpt.header.num_parts == 128) assert(iso.isohybrid_mbr.primary_gpt.header.size_of_partition_entries == 128) assert(len(iso.isohybrid_mbr.primary_gpt.parts) == 3) assert(iso.isohybrid_mbr.primary_gpt.parts[0].first_lba == 0) assert(iso.isohybrid_mbr.primary_gpt.parts[0].last_lba == 115) assert(iso.isohybrid_mbr.primary_gpt.parts[0].attributes == b'\x00'*8) assert(iso.isohybrid_mbr.primary_gpt.parts[0].name == 'ISOHybrid ISO') assert(iso.isohybrid_mbr.primary_gpt.parts[1].first_lba == 104) assert(iso.isohybrid_mbr.primary_gpt.parts[1].last_lba == 107) assert(iso.isohybrid_mbr.primary_gpt.parts[1].attributes == b'\x00'*8) assert(iso.isohybrid_mbr.primary_gpt.parts[1].name == 'ISOHybrid') assert(iso.isohybrid_mbr.primary_gpt.parts[2].first_lba == 112) assert(iso.isohybrid_mbr.primary_gpt.parts[2].last_lba == 115) assert(iso.isohybrid_mbr.primary_gpt.parts[2].attributes == b'\x00'*8) assert(iso.isohybrid_mbr.primary_gpt.parts[2].name == 'ISOHybrid') assert(iso.isohybrid_mbr.mac) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'EFIBOOT.IMG;1', dr_len=46, loc=None, datalen=1, hidden=False, multi_extent=False) def check_joliet_and_eltorito_onefile(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=34, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=5, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=31, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'FOO.;1', dr_len=40, loc=33, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') internal_check_file(iso.joliet_vd.root_dir_record.children[4], name='foo'.encode('utf-16_be'), dr_len=40, loc=33, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_joliet_and_eltorito_onedir(iso, filesize): assert(filesize == 71680) internal_check_pvd(iso.pvd, extent=16, size=35, ptbl_size=22, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=35, path_tbl_size=26, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=33, load_rba=34, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=31, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=5, data_length=2048, extent_location=31) dir1_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=30, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=38, extent_location=30, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=33, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=34, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') joliet_dir1_record = iso.joliet_vd.root_dir_record.children[4] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=32, parent=1) internal_check_dir_record(joliet_dir1_record, num_children=2, name='dir1'.encode('utf-16_be'), dr_len=42, extent_location=32, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(joliet_dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=33, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=34, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_joliet_rr_and_eltorito_nofiles(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=34, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=32, load_rba=33, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=32, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=33, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=32, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=33, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_joliet_rr_and_eltorito_onefile(iso, filesize): assert(filesize == 71680) internal_check_pvd(iso.pvd, extent=16, size=35, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=35, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=32, load_rba=33, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=29, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=5, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=32, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=33, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'FOO.;1', dr_len=116, loc=34, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=32, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=33, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') internal_check_file(iso.joliet_vd.root_dir_record.children[4], name='foo'.encode('utf-16_be'), dr_len=40, loc=34, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_joliet_rr_and_eltorito_onedir(iso, filesize): assert(filesize == 73728) internal_check_pvd(iso.pvd, extent=16, size=36, ptbl_size=22, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=34, load_rba=35, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_jolietvd(iso.svds[0], space_size=36, path_tbl_size=26, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=31, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=29, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=5, data_length=2048, extent_location=31) dir1_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=30, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=114, extent_location=30, rr=True, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=124, loc=34, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=116, loc=35, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') joliet_dir1_record = iso.joliet_vd.root_dir_record.children[4] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=32, parent=1) internal_check_dir_record(joliet_dir1_record, num_children=2, name='dir1'.encode('utf-16_be'), dr_len=42, extent_location=32, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(joliet_dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=34, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=35, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_rr_deep_dir(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=122, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=None, parent=1) rr_moved_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(rr_moved_record.ptr, name=b'RR_MOVED', len_di=8, loc=None, parent=1) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=None, parent=2) dir8_record = rr_moved_record.children[2] internal_check_ptr(dir8_record.ptr, name=b'DIR8', len_di=4, loc=None, parent=3) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'DIR3', len_di=4, loc=None, parent=4) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'DIR4', len_di=4, loc=None, parent=6) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'DIR5', len_di=4, loc=None, parent=7) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'DIR6', len_di=4, loc=None, parent=8) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'DIR7', len_di=4, loc=None, parent=9) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) def check_rr_deep(iso, filesize): assert(filesize == 71680) internal_check_pvd(iso.pvd, extent=16, size=35, ptbl_size=122, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) internal_check_file_contents(iso, path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/foo', contents=b'foo\n', which='rr_path') def check_rr_deep2(iso, filesize): assert(filesize == 73728) internal_check_pvd(iso.pvd, extent=16, size=36, ptbl_size=134, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) internal_check_file_contents(iso, path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/foo', contents=b'foo\n', which='rr_path') def check_xa_nofiles(iso, filesize): assert(filesize == 49152) internal_check_pvd(iso.pvd, extent=16, size=24, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) def check_xa_onefile(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=54, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_xa_onedir(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=52, extent_location=24, rr=False, rr_name=None, rr_links=0, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=True, rr_onetwelve=False) def check_sevendeepdirs(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=94, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=114, extent_location=24, rr=True, rr_name=b'dir1', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=25, parent=2) internal_check_dir_record(dir2_record, num_children=3, name=b'DIR2', dr_len=114, extent_location=25, rr=True, rr_name=b'dir2', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir2_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'DIR3', len_di=4, loc=26, parent=3) internal_check_dir_record(dir3_record, num_children=3, name=b'DIR3', dr_len=114, extent_location=26, rr=True, rr_name=b'dir3', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir3_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'DIR4', len_di=4, loc=27, parent=4) internal_check_dir_record(dir4_record, num_children=3, name=b'DIR4', dr_len=114, extent_location=27, rr=True, rr_name=b'dir4', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir4_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'DIR5', len_di=4, loc=28, parent=5) internal_check_dir_record(dir5_record, num_children=3, name=b'DIR5', dr_len=114, extent_location=28, rr=True, rr_name=b'dir5', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir5_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'DIR6', len_di=4, loc=29, parent=6) internal_check_dir_record(dir6_record, num_children=3, name=b'DIR6', dr_len=114, extent_location=29, rr=True, rr_name=b'dir6', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir6_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'DIR7', len_di=4, loc=30, parent=7) internal_check_dir_record(dir7_record, num_children=2, name=b'DIR7', dr_len=114, extent_location=30, rr=True, rr_name=b'dir7', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir7_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) def check_xa_joliet_nofiles(iso, filesize): assert(filesize == 61440) internal_check_pvd(iso.pvd, extent=16, size=30, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_jolietvd(iso.svds[0], space_size=30, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) assert(iso.joliet_vd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=29) def check_xa_joliet_onefile(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) assert(iso.joliet_vd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=54, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_xa_joliet_onedir(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=26, path_tbl_loc_le=24, path_tbl_loc_be=26) assert(iso.joliet_vd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=True, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=29, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=52, extent_location=29, rr=False, rr_name=None, rr_links=0, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(iso.pvd.root_dir_record.children[2].children[1], rr=False, rr_nlinks=3, xa=True, rr_onetwelve=True) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=31, parent=1) internal_check_dir_record(joliet_dir1_record, num_children=2, name='dir1'.encode('utf-16_be'), dr_len=42, extent_location=31, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(joliet_dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) def check_isolevel4_nofiles(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_enhanced_vd(iso.enhanced_vd, size=25, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) def check_isolevel4_onefile(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_enhanced_vd(iso.enhanced_vd, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'foo', dr_len=36, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='iso_path') def check_isolevel4_onedir(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_enhanced_vd(iso.enhanced_vd, size=26, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'dir1', len_di=4, loc=25, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'dir1', dr_len=38, extent_location=25, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) def check_isolevel4_eltorito(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_enhanced_vd(iso.enhanced_vd, size=28, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'boot', dr_len=38, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') def check_everything(iso, filesize): assert(filesize == 108544) internal_check_pvd(iso.pvd, extent=16, size=53, ptbl_size=106, ptbl_location_le=22, ptbl_location_be=24) internal_check_eltorito(iso, boot_catalog_extent=49, load_rba=50, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_enhanced_vd(iso.enhanced_vd, size=53, ptbl_size=106, ptbl_location_le=22, ptbl_location_be=24) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_jolietvd(iso.svds[1], space_size=53, path_tbl_size=138, path_tbl_loc_le=26, path_tbl_loc_be=28) assert(iso.joliet_vd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=20) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=39, parent=1) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[4] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=40, parent=1) joliet_dir2_record = joliet_dir1_record.children[2] internal_check_ptr(joliet_dir2_record.ptr, name='dir2'.encode('utf-16_be'), len_di=8, loc=41, parent=2) joliet_dir3_record = joliet_dir2_record.children[2] internal_check_ptr(joliet_dir3_record.ptr, name='dir3'.encode('utf-16_be'), len_di=8, loc=42, parent=3) joliet_dir4_record = joliet_dir3_record.children[2] internal_check_ptr(joliet_dir4_record.ptr, name='dir4'.encode('utf-16_be'), len_di=8, loc=43, parent=4) joliet_dir5_record = joliet_dir4_record.children[2] internal_check_ptr(joliet_dir5_record.ptr, name='dir5'.encode('utf-16_be'), len_di=8, loc=44, parent=5) joliet_dir6_record = joliet_dir5_record.children[2] internal_check_ptr(joliet_dir6_record.ptr, name='dir6'.encode('utf-16_be'), len_di=8, loc=45, parent=6) joliet_dir7_record = joliet_dir6_record.children[2] internal_check_ptr(joliet_dir7_record.ptr, name='dir7'.encode('utf-16_be'), len_di=8, loc=46, parent=7) joliet_dir8_record = joliet_dir7_record.children[2] internal_check_ptr(joliet_dir8_record.ptr, name='dir8'.encode('utf-16_be'), len_di=8, loc=47, parent=8) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=7, data_length=2048, extent_location=30, rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=7, data_length=2048, extent_location=39) boot_rec = iso.pvd.root_dir_record.children[2] internal_check_file(boot_rec, name=b'boot', dr_len=128, loc=50, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') internal_check_boot_info_table(boot_rec.inode.boot_info_table, vd_extent=16, inode_extent=50, orig_len=5, csum=0) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=136, loc=49, datalen=2048, hidden=False, multi_extent=False) dir1_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(dir1_record.ptr, name=b'dir1', len_di=4, loc=31, parent=1) internal_check_dir_record(dir1_record, num_children=4, name=b'dir1', dr_len=128, extent_location=31, rr=True, rr_name=b'dir1', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) internal_check_file(dir1_record.children[3], name=b'foo', dr_len=126, loc=51, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/dir1/foo', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[5], name=b'foo', dr_len=126, loc=51, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='iso_path') # Now check the rock ridge symlink. It should have a directory record # length of 132, and the symlink components should be 'dir1' and 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[6] internal_check_rr_symlink(sym_dir_record, name=b'sym', dr_len=136, comps=[b'foo']) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'dir2', len_di=4, loc=32, parent=2) internal_check_dir_record(dir2_record, num_children=3, name=b'dir2', dr_len=128, extent_location=32, rr=True, rr_name=b'dir2', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir2_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'dir3', len_di=4, loc=33, parent=3) internal_check_dir_record(dir3_record, num_children=3, name=b'dir3', dr_len=128, extent_location=33, rr=True, rr_name=b'dir3', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir3_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'dir4', len_di=4, loc=34, parent=4) internal_check_dir_record(dir4_record, num_children=3, name=b'dir4', dr_len=128, extent_location=34, rr=True, rr_name=b'dir4', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir4_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'dir5', len_di=4, loc=35, parent=5) internal_check_dir_record(dir5_record, num_children=3, name=b'dir5', dr_len=128, extent_location=35, rr=True, rr_name=b'dir5', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir5_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'dir6', len_di=4, loc=36, parent=6) internal_check_dir_record(dir6_record, num_children=3, name=b'dir6', dr_len=128, extent_location=36, rr=True, rr_name=b'dir6', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir6_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'dir7', len_di=4, loc=37, parent=7) internal_check_dir_record(dir7_record, num_children=3, name=b'dir7', dr_len=128, extent_location=37, rr=True, rr_name=b'dir7', rr_links=3, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir7_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir8_record = dir7_record.children[2] internal_check_ptr(dir8_record.ptr, name=b'dir8', len_di=4, loc=38, parent=8) internal_check_dir_record(dir8_record, num_children=3, name=b'dir8', dr_len=128, extent_location=38, rr=True, rr_name=b'dir8', rr_links=2, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir8_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) internal_check_file(dir8_record.children[2], name=b'bar', dr_len=126, loc=52, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/bar', contents=b'bar\n', which='iso_path') def check_rr_xa_nofiles(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=True, rr_onetwelve=False) def check_rr_xa_onefile(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=True, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=130, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') def check_rr_xa_onedir(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) assert(iso.pvd.application_use[141:149] == b'CD-XA001') internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=2, name=b'DIR1', dr_len=128, extent_location=24, rr=True, rr_name=b'dir1', rr_links=2, xa=True, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=3, xa=True, rr_onetwelve=False) def check_rr_joliet_symlink(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=29) foo_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=126, comps=[b'foo']) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') assert(iso.has_rock_ridge()) assert(iso.has_joliet()) assert(not iso.has_udf()) def check_rr_joliet_deep(iso, filesize): assert(filesize == 98304) internal_check_pvd(iso.pvd, extent=16, size=48, ptbl_size=122, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=None, parent=1) rr_moved_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(rr_moved_record.ptr, name=b'RR_MOVED', len_di=8, loc=None, parent=1) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=None, parent=2) dir8_record = rr_moved_record.children[2] internal_check_ptr(dir8_record.ptr, name=b'DIR8', len_di=4, loc=None, parent=3) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'DIR3', len_di=4, loc=None, parent=4) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'DIR4', len_di=4, loc=None, parent=6) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'DIR5', len_di=4, loc=None, parent=7) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'DIR6', len_di=4, loc=None, parent=8) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'DIR7', len_di=4, loc=None, parent=9) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=None, parent=1) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=None, parent=1) joliet_dir2_record = joliet_dir1_record.children[2] internal_check_ptr(joliet_dir2_record.ptr, name='dir2'.encode('utf-16_be'), len_di=8, loc=None, parent=2) joliet_dir3_record = joliet_dir2_record.children[2] internal_check_ptr(joliet_dir3_record.ptr, name='dir3'.encode('utf-16_be'), len_di=8, loc=None, parent=3) joliet_dir4_record = joliet_dir3_record.children[2] internal_check_ptr(joliet_dir4_record.ptr, name='dir4'.encode('utf-16_be'), len_di=8, loc=None, parent=4) joliet_dir5_record = joliet_dir4_record.children[2] internal_check_ptr(joliet_dir5_record.ptr, name='dir5'.encode('utf-16_be'), len_di=8, loc=None, parent=5) joliet_dir6_record = joliet_dir5_record.children[2] internal_check_ptr(joliet_dir6_record.ptr, name='dir6'.encode('utf-16_be'), len_di=8, loc=None, parent=6) joliet_dir7_record = joliet_dir6_record.children[2] internal_check_ptr(joliet_dir7_record.ptr, name='dir7'.encode('utf-16_be'), len_di=8, loc=None, parent=7) joliet_dir8_record = joliet_dir7_record.children[2] internal_check_ptr(joliet_dir8_record.ptr, name='dir8'.encode('utf-16_be'), len_di=8, loc=None, parent=8) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=28, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=38) def check_eltorito_multi_boot(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) assert(len(iso.eltorito_boot_catalog.sections) == 1) sec = iso.eltorito_boot_catalog.sections[0] assert(sec.header_indicator == 0x91) assert(sec.platform_id == 0) assert(sec.num_section_entries == 1) assert(sec.id_string == b'\x00'*28) assert(len(sec.section_entries) == 1) entry = sec.section_entries[0] assert(entry.boot_indicator == 0x88) assert(entry.boot_media_type == 0x0) assert(entry.load_segment == 0x0) assert(entry.system_type == 0) assert(entry.sector_count == 4) assert(entry.load_rba == 28) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'boot', dr_len=38, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'boot2', dr_len=38, loc=28, datalen=6, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot2', contents=b'boot2\n', which='iso_path') def check_eltorito_multi_boot_hard_link(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) assert(len(iso.eltorito_boot_catalog.sections) == 1) sec = iso.eltorito_boot_catalog.sections[0] assert(sec.header_indicator == 0x91) assert(sec.platform_id == 0) assert(sec.num_section_entries == 1) assert(sec.id_string == b'\x00'*28) assert(len(sec.section_entries) == 1) entry = sec.section_entries[0] assert(entry.boot_indicator == 0x88) assert(entry.boot_media_type == 0x0) assert(entry.load_segment == 0x0) assert(entry.system_type == 0) assert(entry.sector_count == 4) assert(entry.load_rba == 28) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=6, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'boot', dr_len=38, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'boot2', dr_len=38, loc=28, datalen=6, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot2', contents=b'boot2\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[5], name=b'bootlink', dr_len=42, loc=28, datalen=6, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/bootlink', contents=b'boot2\n', which='iso_path') def check_eltorito_boot_info_table(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) boot_rec = iso.pvd.root_dir_record.children[2] internal_check_file(boot_rec, name=b'boot', dr_len=38, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') internal_check_boot_info_table(boot_rec.inode.boot_info_table, vd_extent=16, inode_extent=27, orig_len=5, csum=0) def check_eltorito_boot_info_table_large(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) boot_rec = iso.pvd.root_dir_record.children[2] internal_check_file(boot_rec, name=b'boot', dr_len=38, loc=27, datalen=80, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'bootboot\x10\x00\x00\x00\x1b\x00\x00\x00P\x00\x00\x00\x88\xbd\xbd\xd1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bootbootbootboot', which='iso_path') internal_check_boot_info_table(boot_rec.inode.boot_info_table, vd_extent=16, inode_extent=27, orig_len=80, csum=0xd1bdbd88) def check_hard_link(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=24, rr=False, rr_name=b'', rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(dir1_record.children[2], name=b'FOO.;1', dr_len=40, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/FOO.;1', contents=b'foo\n', which='iso_path') def check_same_dirname_different_parent(iso, filesize): assert(filesize == 79872) internal_check_pvd(iso.pvd, extent=16, size=39, ptbl_size=58, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=39, path_tbl_size=74, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=33, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=28, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=None, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=114, extent_location=None, rr=True, rr_name=b'dir1', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) dir2_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=None, parent=1) internal_check_dir_record(dir2_record, num_children=3, name=b'DIR2', dr_len=114, extent_location=None, rr=True, rr_name=b'dir2', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir2_record.children[1], rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) boot1_record = dir1_record.children[2] internal_check_ptr(boot1_record.ptr, name=b'BOOT', len_di=4, loc=None, parent=2) internal_check_dir_record(boot1_record, num_children=2, name=b'BOOT', dr_len=114, extent_location=None, rr=True, rr_name=b'boot', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(boot1_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) boot2_record = dir2_record.children[2] internal_check_ptr(boot2_record.ptr, name=b'BOOT', len_di=4, loc=None, parent=3) internal_check_dir_record(boot2_record, num_children=2, name=b'BOOT', dr_len=114, extent_location=None, rr=True, rr_name=b'boot', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(boot2_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=33) dir1_joliet_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(dir1_joliet_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=None, parent=1) internal_check_dir_record(dir1_joliet_record, num_children=3, name='dir1'.encode('utf-16_be'), dr_len=42, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_joliet_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) dir2_joliet_record = iso.joliet_vd.root_dir_record.children[3] internal_check_ptr(dir2_joliet_record.ptr, name='dir2'.encode('utf-16_be'), len_di=8, loc=None, parent=1) internal_check_dir_record(dir2_joliet_record, num_children=3, name='dir2'.encode('utf-16_be'), dr_len=42, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir2_joliet_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) boot1_joliet_record = dir1_joliet_record.children[2] internal_check_ptr(boot1_joliet_record.ptr, name='boot'.encode('utf-16_be'), len_di=8, loc=None, parent=2) internal_check_dir_record(boot1_joliet_record, num_children=2, name='boot'.encode('utf-16_be'), dr_len=42, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(boot1_joliet_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) boot2_joliet_record = dir2_joliet_record.children[2] internal_check_ptr(boot2_joliet_record.ptr, name='boot'.encode('utf-16_be'), len_di=8, loc=None, parent=3) internal_check_dir_record(boot2_joliet_record, num_children=2, name='boot'.encode('utf-16_be'), dr_len=42, extent_location=None, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(boot2_joliet_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) def check_joliet_isolevel4(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=22, ptbl_location_le=21, ptbl_location_be=23) internal_check_enhanced_vd(iso.enhanced_vd, size=34, ptbl_size=22, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.joliet_vd, space_size=34, path_tbl_size=26, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=31, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'dir1', len_di=4, loc=30, parent=1) internal_check_empty_directory(dir1_record, name=b'dir1', dr_len=38, extent=30, rr=False, hidden=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=31) joliet_dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(joliet_dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=32, parent=1) internal_check_empty_directory(joliet_dir1_record, name='dir1'.encode('utf-16_be'), dr_len=42, extent=32, rr=False, hidden=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'foo', dr_len=36, loc=33, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='foo'.encode('utf-16_be'), dr_len=40, loc=33, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='iso_path') def check_eltorito_nofiles_hide(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_joliet_and_eltorito_nofiles_hide(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_joliet_and_eltorito_nofiles_hide_only(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=31, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_joliet_and_eltorito_nofiles_hide_iso_only(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=None, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_hard_link_reshuffle(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'foo\n', which='iso_path') def check_rr_deeper_dir(iso, filesize): assert(filesize == 86016) internal_check_pvd(iso.pvd, extent=16, size=42, ptbl_size=202, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) a1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(a1_record.ptr, name=b'A1', len_di=2, loc=None, parent=1) dir1_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=None, parent=1) rr_moved_record = iso.pvd.root_dir_record.children[4] internal_check_ptr(rr_moved_record.ptr, name=b'RR_MOVED', len_di=8, loc=None, parent=1) a2_record = a1_record.children[2] internal_check_ptr(a2_record.ptr, name=b'A2', len_di=2, loc=None, parent=2) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=None, parent=3) a8_record = rr_moved_record.children[2] internal_check_ptr(a8_record.ptr, name=b'A8', len_di=2, loc=None, parent=4) dir8_record = rr_moved_record.children[3] internal_check_ptr(dir8_record.ptr, name=b'DIR8', len_di=4, loc=None, parent=4) a3_record = a2_record.children[2] internal_check_ptr(a3_record.ptr, name=b'A3', len_di=2, loc=None, parent=5) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'DIR3', len_di=4, loc=None, parent=6) a4_record = a3_record.children[2] internal_check_ptr(a4_record.ptr, name=b'A4', len_di=2, loc=None, parent=9) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'DIR4', len_di=4, loc=None, parent=10) a5_record = a4_record.children[2] internal_check_ptr(a5_record.ptr, name=b'A5', len_di=2, loc=None, parent=11) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'DIR5', len_di=4, loc=None, parent=12) a6_record = a5_record.children[2] internal_check_ptr(a6_record.ptr, name=b'A6', len_di=2, loc=None, parent=13) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'DIR6', len_di=4, loc=None, parent=14) a7_record = a6_record.children[2] internal_check_ptr(a7_record.ptr, name=b'A7', len_di=2, loc=None, parent=15) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'DIR7', len_di=4, loc=None, parent=16) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=23, rr=True, rr_nlinks=5, xa=False, rr_onetwelve=False) def check_eltorito_boot_info_table_large_odd(iso, filesize): assert(filesize == 57344) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) boot_rec = iso.pvd.root_dir_record.children[2] internal_check_file(boot_rec, name=b'boot', dr_len=38, loc=27, datalen=81, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'booboobo\x10\x00\x00\x00\x1b\x00\x00\x00\x51\x00\x00\x00\x1e\xb1\xa3\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ooboobooboobooboo', which='iso_path') internal_check_boot_info_table(boot_rec.inode.boot_info_table, vd_extent=16, inode_extent=27, orig_len=81, csum=0xb0a3b11e) def check_joliet_large_directory(iso, filesize): assert(filesize == 264192) internal_check_pvd(iso.pvd, extent=16, size=129, ptbl_size=678, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=129, path_tbl_size=874, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) # FIXME: this test should probably be more comprehensive def check_zero_byte_file(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'bar\n', which='iso_path') def check_eltorito_hide_boot(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) # Here, the initial entry is hidden, so we check it out by manually looking # for it in the raw output. To do that in the current framework, we need # to re-write the iso into a string, then search the string. initial_entry_offset = iso.eltorito_boot_catalog.initial_entry.get_rba() # Re-render the output into a string. myout = BytesIO() iso.write_fp(myout) # Now seek within the string to the right location. myout.seek(initial_entry_offset * 2048) val = myout.read(5) assert(val == b'boot\n') def check_modify_in_place_spillover(iso, filesize): assert(filesize == 151552) internal_check_pvd(iso.pvd, extent=16, size=74, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_dir_record(dir1_record, num_children=50, name=b'DIR1', dr_len=38, extent_location=24, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=4096, relocated=False) def check_duplicate_pvd(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_pvd(iso.pvds[1], extent=17, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) def check_eltorito_multi_multi_boot(iso, filesize): assert(filesize == 61440) internal_check_pvd(iso.pvd, extent=16, size=30, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=27, media_type=0, system_type=0, bootable=True, platform_id=0) assert(len(iso.eltorito_boot_catalog.sections) == 2) sec = iso.eltorito_boot_catalog.sections[0] assert(sec.header_indicator == 0x90) assert(sec.platform_id == 0) assert(sec.num_section_entries == 1) assert(sec.id_string == b'\x00'*28) assert(len(sec.section_entries) == 1) entry = sec.section_entries[0] assert(entry.boot_indicator == 0x88) assert(entry.boot_media_type == 0x0) assert(entry.load_segment == 0x0) assert(entry.system_type == 0) assert(entry.sector_count == 4) assert(entry.load_rba == 28) sec = iso.eltorito_boot_catalog.sections[1] assert(sec.header_indicator == 0x91) assert(sec.platform_id == 0) assert(sec.num_section_entries == 1) assert(sec.id_string == b'\x00'*28) assert(len(sec.section_entries) == 1) entry = sec.section_entries[0] assert(entry.boot_indicator == 0x88) assert(entry.boot_media_type == 0x0) assert(entry.load_segment == 0x0) assert(entry.system_type == 0) assert(entry.sector_count == 4) assert(entry.load_rba == 29) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=6, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'boot', dr_len=38, loc=27, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'boot2', dr_len=38, loc=28, datalen=6, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot2', contents=b'boot2\n', which='iso_path') def check_hidden_file(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'AAAAAAAA.;1', dr_len=44, loc=24, datalen=3, hidden=True, multi_extent=False) internal_check_file_contents(iso, path='/AAAAAAAA.;1', contents=b'aa\n', which='iso_path') def check_hidden_dir(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=24, rr=False, hidden=True) def check_eltorito_hd_emul(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=4, system_type=2, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=512, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa', which='iso_path') def check_eltorito_hd_emul_bad_sec(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=4, system_type=2, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=512, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*446 + b'\x00\x00\x00\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa', which='iso_path') def check_eltorito_hd_emul_invalid_geometry(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=4, system_type=2, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=512, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*446 + b'\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa', which='iso_path') def check_eltorito_hd_emul_not_bootable(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=4, system_type=2, bootable=False, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=512, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa', which='iso_path') def check_eltorito_floppy12(iso, filesize): assert(filesize == 1282048) internal_check_pvd(iso.pvd, extent=16, size=626, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=1, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=1228800, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*(2400*512), which='iso_path') def check_eltorito_floppy144(iso, filesize): assert(filesize == 1527808) internal_check_pvd(iso.pvd, extent=16, size=746, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=2, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=1474560, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*(2880*512), which='iso_path') def check_eltorito_floppy288(iso, filesize): assert(filesize == 3002368) internal_check_pvd(iso.pvd, extent=16, size=1466, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=3, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=2949120, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'\x00'*(5760*512), which='iso_path') def check_eltorito_multi_hidden(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=26, load_rba=28, media_type=0, system_type=0, bootable=True, platform_id=0) assert(len(iso.eltorito_boot_catalog.sections) == 1) sec = iso.eltorito_boot_catalog.sections[0] assert(sec.header_indicator == 0x91) assert(sec.platform_id == 0) assert(sec.num_section_entries == 1) assert(sec.id_string == b'\x00'*28) assert(len(sec.section_entries) == 1) entry = sec.section_entries[0] assert(entry.boot_indicator == 0x88) assert(entry.boot_media_type == 0x0) assert(entry.load_segment == 0x0) assert(entry.system_type == 0) assert(entry.sector_count == 4) assert(entry.load_rba == 27) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'boot.cat', dr_len=42, loc=26, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'boot', dr_len=38, loc=28, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='iso_path') def check_onefile_with_semicolon(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO;1.;1', dr_len=42, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO;1.;1', contents=b'foo\n', which='iso_path') def check_bad_eltorito_ident(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) # Because this is a bad eltorito ident, we expect the len(brs) to be > 0, # but no eltorito catalog available assert(len(iso.brs) == 1) assert(iso.eltorito_boot_catalog is None) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_rr_two_dirs_same_level(iso, filesize): assert(filesize == 77824) # For two relocated directories at the same level with the same name, # genisoimage seems to pad the second entry in the PTR with three zeros (000), the # third one with 001, etc. pycdlib does not do this, so the sizes do not match. # Hence, for now, we disable this check. #internal_check_pvd(iso.pvd, extent=16, size=38, ptbl_size=128, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) a_dir_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(a_dir_record.ptr, name=b'A', len_di=1, loc=None, parent=1) internal_check_dir_record(a_dir_record, num_children=3, name=b'A', dr_len=108, extent_location=None, rr=True, rr_name=b'A', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(a_dir_record.children[1], rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) b_dir_record = a_dir_record.children[2] internal_check_dir_record(b_dir_record, num_children=3, name=b'B', dr_len=108, extent_location=None, rr=True, rr_name=b'B', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(b_dir_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) c_dir_record = b_dir_record.children[2] internal_check_dir_record(c_dir_record, num_children=3, name=b'C', dr_len=108, extent_location=29, rr=True, rr_name=b'C', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(c_dir_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) d_dir_record = c_dir_record.children[2] internal_check_dir_record(d_dir_record, num_children=3, name=b'D', dr_len=108, extent_location=30, rr=True, rr_name=b'D', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(d_dir_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) e_dir_record = d_dir_record.children[2] internal_check_dir_record(e_dir_record, num_children=3, name=b'E', dr_len=108, extent_location=31, rr=True, rr_name=b'E', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(e_dir_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) f_dir_record = e_dir_record.children[2] internal_check_dir_record(f_dir_record, num_children=4, name=b'F', dr_len=108, extent_location=32, rr=True, rr_name=b'F', rr_links=4, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(f_dir_record.children[1], rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) g_dir_record = f_dir_record.children[2] internal_check_dir_record(g_dir_record, num_children=3, name=b'G', dr_len=108, extent_location=None, rr=True, rr_name=b'G', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(g_dir_record.children[1], rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) # This is the first of the two relocated entries. one_dir_record = g_dir_record.children[2] internal_check_dir_record(one_dir_record, num_children=0, name=b'1', dr_len=120, extent_location=None, rr=True, rr_name=b'1', rr_links=2, xa=False, hidden=False, is_cl_record=True, datalen=2048, relocated=False) h_dir_record = f_dir_record.children[3] internal_check_dir_record(h_dir_record, num_children=3, name=b'H', dr_len=108, extent_location=None, rr=True, rr_name=b'H', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(g_dir_record.children[1], rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) # This is the second of the two relocated entries. one_dir_record = h_dir_record.children[2] internal_check_dir_record(one_dir_record, num_children=0, name=b'1', dr_len=120, extent_location=None, rr=True, rr_name=b'1', rr_links=2, xa=False, hidden=False, is_cl_record=True, datalen=2048, relocated=False) # Now check the foo file. It should have a name of FOO.;1, it should # have a directory record length of 116, it should start at extent 26, and # its contents should be 'foo\n'. internal_check_file_contents(iso, path='/A/B/C/D/E/F/G/1/FIRST.;1', contents=b'first\n', which='iso_path') internal_check_file_contents(iso, path='/A/B/C/D/E/F/H/1/SECOND.;1', contents=b'second\n', which='iso_path') def check_eltorito_rr_verylongname(iso, filesize): assert(filesize == 59392) internal_check_pvd(iso.pvd, extent=16, size=29, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=27, load_rba=28, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'AAAAAAAA.;1', dr_len=None, loc=27, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.;1', dr_len=116, loc=28, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_isohybrid_file_before(iso, filesize): assert(filesize == 1048576) internal_check_pvd(iso.pvd, extent=16, size=28, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) # Now check out the isohybrid stuff. assert(iso.isohybrid_mbr.geometry_heads == 64) assert(iso.isohybrid_mbr.geometry_sectors == 32) assert(iso.isohybrid_mbr.rba != 0) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=27, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'ISOLINUX.BIN;1', dr_len=48, loc=26, datalen=68, hidden=False, multi_extent=False) def check_eltorito_rr_joliet_verylongname(iso, filesize): assert(filesize == 71680) internal_check_pvd(iso.pvd, extent=16, size=35, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_eltorito(iso, boot_catalog_extent=33, load_rba=34, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_jolietvd(iso.svds[0], space_size=35, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=31, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=31) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'AAAAAAAA.;1', dr_len=None, loc=33, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.;1', dr_len=116, loc=34, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') joliet_name = 'a'*64 internal_check_file(iso.joliet_vd.root_dir_record.children[2], name=joliet_name.encode('utf-16_be'), dr_len=162, loc=33, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot'.encode('utf-16_be'), dr_len=42, loc=34, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_joliet_dirs_overflow_ptr_extent(iso, filesize): assert(filesize == 970752) internal_check_pvd(iso.pvd, extent=16, size=474, ptbl_size=3016, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=474, path_tbl_size=4114, path_tbl_loc_le=24, path_tbl_loc_be=28) internal_check_terminator(iso.vdsts, extent=18) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=216+2, data_length=10240, extent_location=32, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=32, parent=1) # The rest of the path table records will be checked by the loop below. internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=253, parent=1) names = internal_generate_joliet_inorder_names(216) for index in range(2, 2+216): joliet_dir_record = iso.joliet_vd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(joliet_dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) def check_joliet_dirs_just_short_ptr_extent(iso, filesize): assert(filesize == 958464) internal_check_pvd(iso.pvd, extent=16, size=468, ptbl_size=3002, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=468, path_tbl_size=4094, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=215+2, data_length=10240, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) # The rest of the path table records will be checked by the loop below. internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=248, parent=1) names = internal_generate_joliet_inorder_names(215) for index in range(2, 2+215): joliet_dir_record = iso.joliet_vd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(joliet_dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) def check_joliet_dirs_add_ptr_extent(iso, filesize): assert(filesize == 1308672) internal_check_pvd(iso.pvd, extent=16, size=639, ptbl_size=4122, ptbl_location_le=20, ptbl_location_be=24) internal_check_jolietvd(iso.svds[0], space_size=639, path_tbl_size=5694, path_tbl_loc_le=28, path_tbl_loc_be=32) internal_check_terminator(iso.vdsts, extent=18) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=295+2, data_length=12288, extent_location=36, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=36, parent=1) # The rest of the path table records will be checked by the loop below. internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=337, parent=1) names = internal_generate_joliet_inorder_names(295) for index in range(2, 2+295): joliet_dir_record = iso.joliet_vd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(joliet_dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) def check_joliet_dirs_rm_ptr_extent(iso, filesize): assert(filesize == 1292288) internal_check_pvd(iso.pvd, extent=16, size=631, ptbl_size=4094, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=631, path_tbl_size=5654, path_tbl_loc_le=24, path_tbl_loc_be=28) internal_check_terminator(iso.vdsts, extent=18) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=293+2, data_length=12288, extent_location=32, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=32, parent=1) # The rest of the path table records will be checked by the loop below. internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=331, parent=1) names = internal_generate_joliet_inorder_names(293) for index in range(2, 2+293): joliet_dir_record = iso.joliet_vd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(joliet_dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) def check_long_directory_name(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=28, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) directory1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(directory1_record.ptr, name=b'DIRECTORY1', len_di=10, loc=24, parent=1) internal_check_empty_directory(directory1_record, name=b'DIRECTORY1', dr_len=44, extent=24, rr=False, hidden=False) def check_long_file_name(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOOBARBAZ1.;1', dr_len=46, loc=24, datalen=11, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOOBARBAZ1.;1', contents=b'foobarbaz1\n', which='iso_path') def check_overflow_root_dir_record(iso, filesize): assert(filesize == 94208) internal_check_pvd(iso.pvd, extent=16, size=46, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=16, data_length=4096, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_dot_dir_record(iso.pvd.root_dir_record.children[0], rr=True, rr_nlinks=2, first_dot=True, xa=False, datalen=4096, rr_onetwelve=False) def check_overflow_correct_extents(iso, filesize): assert(filesize == 102400) internal_check_pvd(iso.pvd, extent=16, size=50, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=18, data_length=6144, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_dot_dir_record(iso.pvd.root_dir_record.children[0], rr=True, rr_nlinks=2, first_dot=True, xa=False, datalen=6144, rr_onetwelve=False) def check_duplicate_deep_dir(iso, filesize): assert(filesize == 135168) internal_check_pvd(iso.pvd, extent=16, size=66, ptbl_size=216, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) books_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(books_record.ptr, name=b'BOOKS', len_di=5, loc=None, parent=1) rr_moved_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(rr_moved_record.ptr, name=b'RR_MOVED', len_di=8, loc=None, parent=1) lkhg_record = books_record.children[2] internal_check_ptr(lkhg_record.ptr, name=b'LKHG', len_di=4, loc=None, parent=2) first_one_record = rr_moved_record.children[2] internal_check_ptr(first_one_record.ptr, name=b'1', len_di=1, loc=None, parent=3) one_thousand_record = rr_moved_record.children[3] internal_check_ptr(one_thousand_record.ptr, name=b'1000', len_di=4, loc=None, parent=3) hypernew_record = lkhg_record.children[2] internal_check_ptr(hypernew_record.ptr, name=b'HYPERNEW', len_di=8, loc=None, parent=4) get_record = hypernew_record.children[2] internal_check_ptr(get_record.ptr, name=b'GET', len_di=3, loc=None, parent=7) first_fs_record = get_record.children[2] internal_check_ptr(first_fs_record.ptr, name=b'FS', len_di=2, loc=None, parent=9) khg_record = get_record.children[3] internal_check_ptr(khg_record.ptr, name=b'KHG', len_di=3, loc=None, parent=9) second_fs_record = first_fs_record.children[2] internal_check_ptr(second_fs_record.ptr, name=b'FS', len_di=2, loc=None, parent=11) fourth_one_record = khg_record.children[2] internal_check_ptr(fourth_one_record.ptr, name=b'1', len_di=1, loc=None, parent=12) one_one_seven_record = khg_record.children[3] internal_check_ptr(one_one_seven_record.ptr, name=b'117', len_di=3, loc=None, parent=12) thirty_five_record = khg_record.children[4] internal_check_ptr(thirty_five_record.ptr, name=b'35', len_di=2, loc=None, parent=12) fifth_one_record = second_fs_record.children[2] internal_check_ptr(fifth_one_record.ptr, name=b'1', len_di=1, loc=None, parent=13) sixth_one_record = one_one_seven_record.children[2] internal_check_ptr(sixth_one_record.ptr, name=b'1', len_di=1, loc=None, parent=15) seventh_one_record = thirty_five_record.children[2] internal_check_ptr(seventh_one_record.ptr, name=b'1', len_di=1, loc=None, parent=16) # This is the second of the two relocated entries. rr_moved_dir_record = iso.pvd.root_dir_record.children[3] internal_check_dir_record(rr_moved_dir_record, num_children=4, name=b'RR_MOVED', dr_len=122, extent_location=None, rr=True, rr_name=b'rr_moved', rr_links=4, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # In theory we should check the dir_records underneath rr_moved here. # Unfortunately, which directory gets renamed to 1000 is unstable, # and thus we don't know which record it is. We skip the check for now, # although we could go grubbing through the children to try and find it. def check_onefile_joliet_no_file(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_joliet_isolevel4_nofiles(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.joliet_vd, space_size=31, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_enhanced_vd(iso.enhanced_vd, size=31, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=30) def check_rr_absolute_symlink(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[2] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=140, comps=[b'', b'usr', b'local', b'foo']) def check_deep_rr_symlink(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=94, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] dir2_record = dir1_record.children[2] dir3_record = dir2_record.children[2] dir4_record = dir3_record.children[2] dir5_record = dir4_record.children[2] dir6_record = dir5_record.children[2] dir7_record = dir6_record.children[2] # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = dir7_record.children[2] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=140, comps=[b'', b'usr', b'share', b'foo']) def check_rr_deep_weird_layout(iso, filesize): assert(filesize == 73728) internal_check_pvd(iso.pvd, extent=16, size=36, ptbl_size=146, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) astroid_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(astroid_record.ptr, name=b'ASTROID', len_di=7, loc=None, parent=1) internal_check_dir_record(astroid_record, num_children=3, name=b'ASTROID', dr_len=120, extent_location=None, rr=True, rr_name=b'astroid', rr_links=3, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) rr_moved_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(rr_moved_record.ptr, name=b'RR_MOVED', len_di=8, loc=None, parent=1) astroid2_record = astroid_record.children[2] internal_check_ptr(astroid2_record.ptr, name=b'ASTROID', len_di=7, loc=None, parent=2) sidepack_record = rr_moved_record.children[2] internal_check_ptr(sidepack_record.ptr, name=b'SIDEPACK', len_di=8, loc=None, parent=3) tests_record = astroid2_record.children[2] internal_check_ptr(tests_record.ptr, name=b'TESTS', len_di=5, loc=28, parent=4) testdata_record = tests_record.children[2] internal_check_ptr(testdata_record.ptr, name=b'TESTDATA', len_di=8, loc=29, parent=6) python3_record = testdata_record.children[2] internal_check_ptr(python3_record.ptr, name=b'PYTHON3', len_di=7, loc=30, parent=7) data_record = python3_record.children[2] internal_check_ptr(data_record.ptr, name=b'DATA', len_di=4, loc=31, parent=8) absimp_record = data_record.children[2] internal_check_ptr(absimp_record.ptr, name=b'ABSIMP', len_di=6, loc=32, parent=9) internal_check_file_contents(iso, path='/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP/STRING.PY;1', contents=b'from __future__ import absolute_import, print_functino\nimport string\nprint(string)\n', which='iso_path') internal_check_file_contents(iso, path='/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP/SIDEPACK/__INIT__.PY;1', contents=b'"""a side package with nothing in it\n"""\n', which='iso_path') def check_rr_long_dir_name(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=26, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) aa_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(aa_record.ptr, name=b'AAAAAAAA', len_di=8, loc=None, parent=1) internal_check_dir_record(aa_record, num_children=2, name=b'AAAAAAAA', dr_len=None, extent_location=None, rr=True, rr_name=b'a'*RR_MAX_FILENAME_LENGTH, rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) def check_rr_out_of_order_ce(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=26, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) aa_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(aa_record.ptr, name=b'AAAAAAAA', len_di=8, loc=None, parent=1) internal_check_dir_record(aa_record, num_children=2, name=b'AAAAAAAA', dr_len=None, extent_location=None, rr=True, rr_name=b'a'*RR_MAX_FILENAME_LENGTH, rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=254, comps=[b'a'*RR_MAX_FILENAME_LENGTH, b'b'*RR_MAX_FILENAME_LENGTH, b'c'*RR_MAX_FILENAME_LENGTH, b'd'*RR_MAX_FILENAME_LENGTH, b'e'*RR_MAX_FILENAME_LENGTH]) def check_rr_ce_removal(iso, filesize): assert(filesize == 61440) internal_check_pvd(iso.pvd, extent=16, size=30, ptbl_size=74, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) ee_record = iso.pvd.root_dir_record.children[5] internal_check_ptr(ee_record.ptr, name=b'EEEEEEEE', len_di=8, loc=None, parent=1) internal_check_dir_record(ee_record, num_children=2, name=b'EEEEEEEE', dr_len=None, extent_location=None, rr=True, rr_name=b'e'*RR_MAX_FILENAME_LENGTH, rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) def check_rr_relocated_hidden(iso, filesize): assert(filesize == 73728) internal_check_pvd(iso.pvd, extent=16, size=36, ptbl_size=134, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=None, parent=1) rr_moved_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(rr_moved_record.ptr, name=b'_RR_MOVE', len_di=8, loc=None, parent=1) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'DIR2', len_di=4, loc=None, parent=2) dir8_record = rr_moved_record.children[2] internal_check_ptr(dir8_record.ptr, name=b'DIR8', len_di=4, loc=None, parent=3) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'DIR3', len_di=4, loc=None, parent=4) dir9_record = dir8_record.children[2] internal_check_ptr(dir9_record.ptr, name=b'DIR9', len_di=4, loc=None, parent=5) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'DIR4', len_di=4, loc=None, parent=6) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'DIR5', len_di=4, loc=None, parent=8) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'DIR6', len_di=4, loc=None, parent=9) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'DIR7', len_di=4, loc=None, parent=10) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=4, xa=False, rr_onetwelve=False) internal_check_file_contents(iso, path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9/foo', contents=b'foo\n', which='rr_path') def check_duplicate_pvd_joliet(iso, filesize): assert(filesize == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_pvd(iso.pvds[1], extent=17, size=32, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=30) internal_check_root_dir_record(iso.pvds[1].root_dir_record, num_children=3, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvds[1].root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=31, datalen=4, hidden=False, multi_extent=False) def check_onefile_toolong(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=24, datalen=2048, hidden=False, multi_extent=False) def check_pvd_zero_datetime(iso, filesize): assert(filesize == 49152) internal_check_pvd(iso.pvd, extent=16, size=24, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) assert(iso.pvd.volume_creation_date.year == 0) assert(iso.pvd.volume_creation_date.month == 0) assert(iso.pvd.volume_creation_date.dayofmonth == 0) assert(iso.pvd.volume_creation_date.hour == 0) assert(iso.pvd.volume_creation_date.minute == 0) assert(iso.pvd.volume_creation_date.second == 0) assert(iso.pvd.volume_creation_date.hundredthsofsecond == 0) assert(iso.pvd.volume_creation_date.gmtoffset == 0) def check_joliet_different_names(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=28, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=116, loc=31, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOOJ.;1', dr_len=116, loc=32, datalen=10, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOOJ.;1', contents=b'foojoliet\n', which='iso_path') def check_hidden_joliet_file(iso, size): assert(size == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) def check_hidden_joliet_dir(iso, size): assert(size == 65536) internal_check_pvd(iso.pvd, extent=16, size=32, ptbl_size=22, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=32, path_tbl_size=26, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) dir1_record = iso.joliet_vd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name='dir1'.encode('utf-16_be'), len_di=8, loc=31, parent=1) def check_rr_onefileonedir_hidden(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=22, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=True, rr_nlinks=3, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=24, parent=1) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') foo_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=26, datalen=4, hidden=True, multi_extent=False) internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=114, extent=24, rr=True, hidden=True) def check_rr_onefile_onetwelve(iso, size): assert(size == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=True) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=118, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') def check_joliet_ident_encoding(iso, filesize): assert(filesize == 69632) internal_check_pvd(iso.pvd, extent=16, size=34, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_enhanced_vd(iso.enhanced_vd, size=34, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.joliet_vd, space_size=34, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) assert(iso.joliet_vd.volume_identifier == 'cidata'.ljust(16, ' ').encode('utf-16_be')) assert(iso.joliet_vd.system_identifier == 'LINUX'.ljust(16, ' ').encode('utf-16_be')) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=29, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'meta-data', dr_len=124, loc=32, datalen=25, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'user-data', dr_len=124, loc=33, datalen=78, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='meta-data'.encode('utf-16_be'), dr_len=52, loc=32, datalen=25, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='user-data'.encode('utf-16_be'), dr_len=52, loc=33, datalen=78, hidden=False, multi_extent=False) def check_duplicate_pvd_isolevel4(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_pvd(iso.pvds[1], extent=17, size=27, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_enhanced_vd(iso.enhanced_vd, size=27, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=25, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=25, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) def check_joliet_hidden_iso_file(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') def check_eltorito_bootlink(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOTLINK.;1', dr_len=44, loc=26, datalen=5, hidden=False, multi_extent=False) # Here, the initial entry is hidden, so we check it out by manually looking # for it in the raw output. To do that in the current framework, we need # to re-write the iso into a string, then search the string. initial_entry_offset = iso.eltorito_boot_catalog.initial_entry.get_rba() # Re-render the output into a string. myout = BytesIO() iso.write_fp(myout) # Now seek within the string to the right location. myout.seek(initial_entry_offset * 2048) val = myout.read(5) assert(val == b'boot\n') def check_udf_nofiles(iso, filesize): assert(filesize == 546816) internal_check_pvd(iso.pvd, extent=16, size=267, ptbl_size=10, ptbl_location_le=261, ptbl_location_be=263) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=265, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=265, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=266, part_length=9, unique_id=261, num_dirs=1, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) def check_udf_onedir(iso, filesize): assert(filesize == 552960) internal_check_pvd(iso.pvd, extent=16, size=270, ptbl_size=22, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=268, rr=False, hidden=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=269, part_length=12, unique_id=263, num_dirs=2, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) def check_udf_twodirs(iso, filesize): assert(filesize == 559104) internal_check_pvd(iso.pvd, extent=16, size=273, ptbl_size=34, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=272, part_length=15, unique_id=265, num_dirs=3, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=3, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=None, abs_blocknum=None, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir2_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(dir2_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=None, abs_blocknum=None, name=b'dir2', isparent=False, isdir=True) dir2_file_entry = dir2_file_ident.file_entry internal_check_udf_file_entry(dir2_file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir2_file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) def check_udf_subdir(iso, filesize): assert(filesize == 559104) internal_check_pvd(iso.pvd, extent=16, size=273, ptbl_size=38, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=272, part_length=15, unique_id=265, num_dirs=3, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=2, info_len=88, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) subdir1_file_ident = dir1_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(subdir1_file_ident, extent=262, tag_location=5, characteristics=2, blocknum=6, abs_blocknum=263, name=b'subdir1', isparent=False, isdir=True) subdir1_file_entry = subdir1_file_ident.file_entry internal_check_udf_file_entry(subdir1_file_entry, location=263, tag_location=6, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(subdir1_file_entry.fi_descs[0], extent=264, tag_location=7, characteristics=10, blocknum=None, abs_blocknum=None, name=b'', isparent=True, isdir=True) def check_udf_subdir_odd(iso, filesize): assert(filesize == 559104) internal_check_pvd(iso.pvd, extent=16, size=273, ptbl_size=36, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=272, part_length=15, unique_id=265, num_dirs=3, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=2, info_len=88, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) subdi1_file_ident = dir1_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(subdi1_file_ident, extent=262, tag_location=5, characteristics=2, blocknum=6, abs_blocknum=263, name=b'subdi1', isparent=False, isdir=True) subdi1_file_entry = subdi1_file_ident.file_entry internal_check_udf_file_entry(subdi1_file_entry, location=263, tag_location=6, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(subdi1_file_entry.fi_descs[0], extent=264, tag_location=7, characteristics=10, blocknum=None, abs_blocknum=None, name=b'', isparent=True, isdir=True) def check_udf_onefile(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') def check_udf_onefileonedir(iso, filesize): assert(filesize == 557056) internal_check_pvd(iso.pvd, extent=16, size=272, ptbl_size=22, ptbl_location_le=264, ptbl_location_be=266) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=268, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=268, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_empty_directory(dir1_record, name=b'DIR1', dr_len=38, extent=269, rr=False, hidden=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=270, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=271, part_length=14, unique_id=264, num_dirs=2, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=6, abs_blocknum=263, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=263, tag_location=6, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) def check_udf_dir_spillover(iso, filesize): assert(filesize == 677888) internal_check_pvd(iso.pvd, extent=16, size=331, ptbl_size=346, ptbl_location_le=304, ptbl_location_be=306) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=308, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=23, data_length=2048, extent_location=308, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) letter = ord('A') for child in iso.pvd.root_dir_record.children[2:]: namestr = chr(letter) * 8 namestr = bytes(namestr.encode('utf-8')) # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(child.ptr, name=namestr, len_di=len(namestr), loc=None, parent=1) internal_check_empty_directory(child, name=namestr, dr_len=42, extent=None, rr=False, hidden=False) letter += 1 # Make sure we saw everything we expected. assert(chr(letter) == 'V') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=330, part_length=73, unique_id=304, num_dirs=22, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=22, info_len=2224, num_blocks_recorded=2, num_fi_descs=22, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) letter = ord('a') for file_ident in iso.udf_root.fi_descs[1:]: namestr = chr(letter) * 64 internal_check_udf_file_ident_desc(file_ident, extent=None, tag_location=None, characteristics=2, blocknum=None, abs_blocknum=None, name=bytes(namestr.encode('utf-8')), isparent=False, isdir=True) letter += 1 file_entry = file_ident.file_entry internal_check_udf_file_entry(file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) # Make sure we saw everything we expected. assert(chr(letter) == 'v') def check_udf_dir_oneshort(iso, filesize): assert(filesize == 671744) internal_check_pvd(iso.pvd, extent=16, size=328, ptbl_size=330, ptbl_location_le=302, ptbl_location_be=304) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=306, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=22, data_length=2048, extent_location=306, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) letter = ord('A') for child in iso.pvd.root_dir_record.children[2:]: namestr = chr(letter) * 8 namestr = bytes(namestr.encode('utf-8')) # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(child.ptr, name=namestr, len_di=len(namestr), loc=None, parent=1) internal_check_empty_directory(child, name=namestr, dr_len=42, extent=None, rr=False, hidden=False) letter += 1 # Make sure we saw everything we expected. assert(chr(letter) == 'U') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=327, part_length=70, unique_id=302, num_dirs=21, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=21, info_len=2120, num_blocks_recorded=2, num_fi_descs=21, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) letter = ord('a') for file_ident in iso.udf_root.fi_descs[1:]: namestr = chr(letter) * 64 internal_check_udf_file_ident_desc(file_ident, extent=None, tag_location=None, characteristics=2, blocknum=None, abs_blocknum=None, name=bytes(namestr.encode('utf-8')), isparent=False, isdir=True) letter += 1 file_entry = file_ident.file_entry internal_check_udf_file_entry(file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) # Make sure we saw everything we expected. assert(chr(letter) == 'u') def check_udf_iso_hidden(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') def check_udf_hidden(iso, filesize): assert(filesize == 548864) internal_check_pvd(iso.pvd, extent=16, size=268, ptbl_size=10, ptbl_location_le=261, ptbl_location_be=263) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=265, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=265, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=267, part_length=10, unique_id=261, num_dirs=1, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=266, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_very_largefile(iso, filesize): assert(filesize == 5368758272) internal_check_pvd(iso.pvd, extent=16, size=2621464, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BIGFILE.;1', dr_len=44, loc=24, datalen=4294965248, hidden=False, multi_extent=True) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BIGFILE.;1', dr_len=44, loc=2097175, datalen=1073743872, hidden=False, multi_extent=False) def check_udf_very_large(iso, filesize): assert(filesize == 1074290688) internal_check_pvd(iso.pvd, extent=16, size=524556, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=267, datalen=1073739777, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=524555, part_length=524298, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=1073739777, num_blocks_recorded=524288, num_fi_descs=0, file_type='file', num_alloc_descs=2) def check_joliet_udf_nofiles(iso, filesize): assert(filesize == 557056) internal_check_pvd(iso.pvd, extent=16, size=272, ptbl_size=10, ptbl_location_le=261, ptbl_location_be=263) internal_check_jolietvd(iso.svds[0], space_size=272, path_tbl_size=10, path_tbl_loc_le=265, path_tbl_loc_be=267) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=270, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=2, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=2, data_length=2048, extent_location=270) internal_check_udf_headers(iso, bea_extent=19, end_anchor_extent=271, part_length=14, unique_id=261, num_dirs=1, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) assert(not iso.has_rock_ridge()) assert(iso.has_joliet()) assert(iso.has_udf()) def check_udf_dir_exactly2048(iso, filesize): assert(filesize == 589824) internal_check_pvd(iso.pvd, extent=16, size=288, ptbl_size=122, ptbl_location_le=275, ptbl_location_be=277) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=279, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=9, data_length=2048, extent_location=279, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) letter = ord('A') for child in iso.pvd.root_dir_record.children[2:]: namestr = chr(letter) * 8 namestr = bytes(namestr.encode('utf-8')) internal_check_ptr(child.ptr, name=namestr, len_di=8, loc=None, parent=1) internal_check_empty_directory(child, name=namestr, dr_len=42, extent=None, rr=False, hidden=False) letter += 1 assert(chr(letter) == 'H') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=287, part_length=30, unique_id=275, num_dirs=8, num_files=0) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=8, info_len=2048, num_blocks_recorded=1, num_fi_descs=8, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) letter = ord('a') for file_ident in iso.udf_root.fi_descs[1:-1]: namestr = chr(letter) * 248 internal_check_udf_file_ident_desc(file_ident, extent=None, tag_location=None, characteristics=2, blocknum=None, abs_blocknum=None, name=bytes(namestr.encode('utf-8')), isparent=False, isdir=True) file_entry = file_ident.file_entry internal_check_udf_file_entry(file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) letter += 1 assert(chr(letter) == 'g') namestr = chr(letter) * 240 file_ident = iso.udf_root.fi_descs[-1] internal_check_udf_file_ident_desc(file_ident, extent=None, tag_location=None, characteristics=2, blocknum=None, abs_blocknum=None, name=bytes(namestr.encode('utf-8')), isparent=False, isdir=True) file_entry = file_ident.file_entry internal_check_udf_file_entry(file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) letter += 1 # Make sure we saw everything we expected. assert(chr(letter) == 'h') def check_udf_symlink(iso, filesize): assert(filesize == 555008) internal_check_pvd(iso.pvd, extent=16, size=271, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=268, datalen=4, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=270, part_length=13, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=262, tag_location=5, num_links=1, info_len=8, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_symlink_in_dir(iso, filesize): assert(filesize == 561152) internal_check_pvd(iso.pvd, extent=16, size=274, ptbl_size=22, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) dir1_record = iso.pvd.root_dir_record.children[3] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=270, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=270, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) internal_check_file(dir1_record.children[2], name=b'FOO.;1', dr_len=40, loc=271, datalen=4, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=273, part_length=16, unique_id=265, num_dirs=2, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=6, abs_blocknum=263, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=263, tag_location=6, num_links=1, info_len=17, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_symlink_abs_path(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=261, tag_location=4, num_links=1, info_len=27, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_rr_symlink(iso, filesize): assert(filesize == 557056) internal_check_pvd(iso.pvd, extent=16, size=272, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=267, rr=True, rr_nlinks=2, xa=False, rr_onetwelve=False) foo_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=116, loc=269, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_rr_file(foo_dir_record, name=b'foo') internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='rr_path') # Now check the rock ridge symlink. It should have a directory record # length of 126, and the symlink components should be 'foo'. sym_dir_record = iso.pvd.root_dir_record.children[3] internal_check_rr_symlink(sym_dir_record, name=b'SYM.;1', dr_len=126, comps=[b'foo']) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=271, part_length=14, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) sym_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(sym_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'sym', isparent=False, isdir=False) sym_file_entry = sym_file_ident.file_entry internal_check_udf_file_entry(sym_file_entry, location=262, tag_location=5, num_links=1, info_len=8, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) assert(iso.has_rock_ridge()) assert(not iso.has_joliet()) assert(iso.has_udf()) def check_udf_overflow_dir_extent(iso, filesize): assert(filesize == 831488) internal_check_pvd(iso.pvd, extent=16, size=406, ptbl_size=636, ptbl_location_le=354, ptbl_location_be=356) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=358, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=48, data_length=2048, extent_location=358, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) names = internal_generate_inorder_names(46) for index in range(2, 2+46): dir_record = iso.pvd.root_dir_record.children[index] # We skip checking the path table record extent locations because # genisoimage seems to have a bug assigning the extent locations, and # seems to assign them in reverse order. internal_check_ptr(dir_record.ptr, name=names[index], len_di=len(names[index]), loc=None, parent=1) internal_check_empty_directory(dir_record, name=names[index], dr_len=38, extent=None, rr=False, hidden=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=405, part_length=148, unique_id=354, num_dirs=47, num_files=0) names = internal_generate_udf_inorder_names(46) for index in range(1, 1+46): file_ident = iso.udf_root.fi_descs[index] internal_check_udf_file_ident_desc(file_ident, extent=None, tag_location=None, characteristics=2, blocknum=None, abs_blocknum=None, name=names[index], isparent=False, isdir=True) file_entry = file_ident.file_entry internal_check_udf_file_entry(file_entry, location=None, tag_location=None, num_links=1, info_len=40, num_blocks_recorded=1, num_fi_descs=1, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(file_entry.fi_descs[0], extent=None, tag_location=None, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) def check_udf_hardlink(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) foo_dir_record = iso.pvd.root_dir_record.children[2] internal_check_file(foo_dir_record, name=b'FOO.;1', dr_len=40, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'bar', isparent=False, isdir=False) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/bar', contents=b'foo\n', which='udf_path') foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') def check_multi_hard_link(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=5, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BAZ.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAZ.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[4], name=b'FOO.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') def check_joliet_with_version(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo.;1'.encode('utf-16_be'), dr_len=46, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo.;1', contents=b'foo\n', which='joliet_path') def check_udf_joliet_onefile(iso, filesize): assert(filesize == 561152) internal_check_pvd(iso.pvd, extent=16, size=274, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_jolietvd(iso.svds[0], space_size=274, path_tbl_size=10, path_tbl_loc_le=266, path_tbl_loc_be=268) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=270, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=271, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=270, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=271) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=272, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='foo'.encode('utf-16_be'), dr_len=40, loc=272, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='joliet_path') internal_check_udf_headers(iso, bea_extent=19, end_anchor_extent=273, part_length=16, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') def check_joliet_and_eltorito_joliet_only(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=10, ptbl_location_le=21, ptbl_location_be=23) internal_check_jolietvd(iso.svds[0], space_size=33, path_tbl_size=10, path_tbl_loc_le=25, path_tbl_loc_be=27) internal_check_eltorito(iso, boot_catalog_extent=31, load_rba=32, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=19) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=29, parent=1) internal_check_ptr(iso.joliet_vd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=30, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=29, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=4, data_length=2048, extent_location=30) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[3], name='boot.cat'.encode('utf-16_be'), dr_len=50, loc=31, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.joliet_vd.root_dir_record.children[2], name='boot'.encode('utf-16_be'), dr_len=42, loc=32, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='joliet_path') def check_udf_and_eltorito_udf_only(iso, filesize): assert(filesize == 555008) internal_check_pvd(iso.pvd, extent=16, size=271, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=269, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=19, end_anchor_extent=270, part_length=13, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=132, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) boot_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(boot_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'boot', isparent=False, isdir=False) boot_file_entry = boot_file_ident.file_entry internal_check_udf_file_entry(boot_file_entry, location=261, tag_location=4, num_links=1, info_len=5, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/boot', contents=b'boot\n', which='udf_path') bootcat_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bootcat_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'boot.cat', isparent=False, isdir=False) bootcat_file_entry = bootcat_file_ident.file_entry internal_check_udf_file_entry(bootcat_file_entry, location=262, tag_location=5, num_links=1, info_len=2048, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) def check_udf_onefile_multi_links(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) baz_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(baz_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'baz', isparent=False, isdir=False) baz_file_entry = baz_file_ident.file_entry internal_check_udf_file_entry(baz_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/baz', contents=b'foo\n', which='udf_path') foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') def check_udf_dotdot_symlink(iso, filesize): assert(filesize == 561152) internal_check_pvd(iso.pvd, extent=16, size=274, ptbl_size=22, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'DIR1', len_di=4, loc=270, parent=1) internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=270, rr=False, rr_name=None, rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=None, datalen=4, hidden=False, multi_extent=False) sym_record = dir1_record.children[2] internal_check_file(sym_record, name=b'SYM.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=273, part_length=16, unique_id=265, num_dirs=2, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) # parent internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) foo_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=6, abs_blocknum=263, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=263, tag_location=6, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') sym_file_ident = dir1_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(sym_file_ident, extent=262, tag_location=5, characteristics=0, blocknum=7, abs_blocknum=264, name=b'sym', isparent=False, isdir=False) sym_file_entry = sym_file_ident.file_entry internal_check_udf_file_entry(sym_file_entry, location=264, tag_location=7, num_links=1, info_len=12, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_dot_symlink(iso, filesize): assert(filesize == 555008) internal_check_pvd(iso.pvd, extent=16, size=271, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=None, datalen=4, hidden=False, multi_extent=False) sym_record = iso.pvd.root_dir_record.children[3] internal_check_file(sym_record, name=b'SYM.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=270, part_length=13, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') sym_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(sym_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'sym', isparent=False, isdir=False) sym_file_entry = sym_file_ident.file_entry internal_check_udf_file_entry(sym_file_entry, location=262, tag_location=5, num_links=1, info_len=12, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_zero_byte_file(iso, filesize): assert(filesize == 552960) internal_check_pvd(iso.pvd, extent=16, size=270, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=268, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'bar\n', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=269, part_length=12, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=262, tag_location=5, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/bar', contents=b'bar\n', which='udf_path') foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=0, num_blocks_recorded=0, num_fi_descs=0, file_type='file', num_alloc_descs=0) internal_check_file_contents(iso, path='/foo', contents=b'', which='udf_path') def check_udf_onefile_onedirwithfile(iso, filesize): assert(filesize == 561152) internal_check_pvd(iso.pvd, extent=16, size=274, ptbl_size=22, ptbl_location_le=265, ptbl_location_be=267) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=269, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=269, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_dir_record(dir1_record, num_children=3, name=b'DIR1', dr_len=38, extent_location=270, rr=False, rr_name=b'dir1', rr_links=2, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) # The directory record should have a valid 'dotdot' record. internal_check_dotdot_dir_record(dir1_record.children[1], rr=False, rr_nlinks=3, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=271, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_file(dir1_record.children[2], name=b'BAR.;1', dr_len=40, loc=272, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/DIR1/BAR.;1', contents=b'bar\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=273, part_length=16, unique_id=265, num_dirs=2, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) dir1_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(dir1_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'dir1', isparent=False, isdir=True) dir1_file_entry = dir1_file_ident.file_entry internal_check_udf_file_entry(dir1_file_entry, location=261, tag_location=4, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(dir1_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=6, abs_blocknum=263, name=b'foo', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=263, tag_location=6, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/foo', contents=b'foo\n', which='udf_path') bar_file_ident = dir1_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(bar_file_ident, extent=262, tag_location=5, characteristics=0, blocknum=7, abs_blocknum=264, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=264, tag_location=7, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/dir1/bar', contents=b'bar\n', which='udf_path') def check_zero_byte_hard_link(iso, filesize): assert(filesize == 49152) internal_check_pvd(iso.pvd, extent=16, size=24, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BAR.;1', contents=b'', which='iso_path') internal_check_file(iso.pvd.root_dir_record.children[3], name=b'FOO.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'', which='iso_path') def check_udf_zero_byte_hard_link(iso, filesize): assert(filesize == 548864) internal_check_pvd(iso.pvd, extent=16, size=268, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=267, part_length=10, unique_id=262, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=128, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=261, tag_location=4, num_links=1, info_len=0, num_blocks_recorded=0, num_fi_descs=0, file_type='file', num_alloc_descs=0) internal_check_file_contents(iso, path='/bar', contents=b'', which='udf_path') def check_unicode_name(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F__O.;1', dr_len=40, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F__O.;1', contents=b'foo\n', which='iso_path') def check_unicode_name_isolevel4(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_enhanced_vd(iso.enhanced_vd, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'f\xc3\xb6o', dr_len=38, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/föo', contents=b'foo\n', which='iso_path') def check_unicode_name_joliet(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F__O.;1', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F__O.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name=b'\x00f\x00\xf6\x00o', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/föo', contents=b'foo\n', which='joliet_path') def check_unicode_name_udf(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F__O.;1', dr_len=40, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F__O.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'f\xf6o', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/föo', contents=b'foo\n', which='udf_path') def check_unicode_name_two_byte(iso, filesize): assert(filesize == 51200) internal_check_pvd(iso.pvd, extent=16, size=25, ptbl_size=10, ptbl_location_le=19, ptbl_location_be=21) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=23, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=23, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F___O.;1', dr_len=42, loc=24, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F___O.;1', contents=b'foo\n', which='iso_path') def check_unicode_name_two_byte_isolevel4(iso, filesize): assert(filesize == 53248) internal_check_pvd(iso.pvd, extent=16, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_enhanced_vd(iso.enhanced_vd, size=26, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'f\xe1\xb4\x94o', dr_len=38, loc=25, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/fᴔo', contents=b'foo\n', which='iso_path') def check_unicode_name_two_byte_joliet(iso, filesize): assert(filesize == 63488) internal_check_pvd(iso.pvd, extent=16, size=31, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_jolietvd(iso.svds[0], space_size=31, path_tbl_size=10, path_tbl_loc_le=24, path_tbl_loc_be=26) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=28, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=28, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_joliet_root_dir_record(iso.joliet_vd.root_dir_record, num_children=3, data_length=2048, extent_location=29) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F___O.;1', dr_len=42, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F___O.;1', contents=b'foo\n', which='iso_path') internal_check_file(iso.joliet_vd.root_dir_record.children[2], name=b'\x00f\x1d\x14\x00o', dr_len=40, loc=30, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/fᴔo', contents=b'foo\n', which='joliet_path') def check_unicode_name_two_byte_udf(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'F___O.;1', dr_len=42, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/F___O.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=88, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'\x00f\x1d\x14\x00o', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) def check_udf_unicode_symlink(iso, filesize): assert(filesize == 555008) internal_check_pvd(iso.pvd, extent=16, size=271, ptbl_size=10, ptbl_location_le=263, ptbl_location_be=265) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=267, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=267, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BAR.;1', dr_len=40, loc=None, datalen=0, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'F___O.;1', dr_len=42, loc=None, datalen=4, hidden=False, multi_extent=False) internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=270, part_length=13, unique_id=263, num_dirs=1, num_files=2) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=132, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(foo_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=4, abs_blocknum=261, name=b'\x00f\x1d\x14\x00o', isparent=False, isdir=False) foo_file_entry = foo_file_ident.file_entry internal_check_udf_file_entry(foo_file_entry, location=261, tag_location=4, num_links=1, info_len=4, num_blocks_recorded=1, num_fi_descs=0, file_type='file', num_alloc_descs=1) internal_check_file_contents(iso, path='/fᴔo', contents=b'foo\n', which='udf_path') bar_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(bar_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=5, abs_blocknum=262, name=b'bar', isparent=False, isdir=False) bar_file_entry = bar_file_ident.file_entry internal_check_udf_file_entry(bar_file_entry, location=262, tag_location=5, num_links=1, info_len=11, num_blocks_recorded=1, num_fi_descs=0, file_type='symlink', num_alloc_descs=1) def check_udf_zeroed_file_entry(iso, filesize): assert(filesize == 550912) internal_check_pvd(iso.pvd, extent=16, size=269, ptbl_size=10, ptbl_location_le=262, ptbl_location_be=264) internal_check_terminator(iso.vdsts, extent=17) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=266, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=3, data_length=2048, extent_location=266, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'FOO.;1', dr_len=40, loc=267, datalen=4, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/FOO.;1', contents=b'foo\n', which='iso_path') internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=268, part_length=11, unique_id=262, num_dirs=1, num_files=1) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=1, info_len=84, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) foo_file_ident = iso.udf_root.fi_descs[1] # Essentially equivalent to calling internal_check_udf_file_ident_desc, # but ensures that the file_entry is None assert(foo_file_ident.extent_location() == 260) internal_check_udf_tag(foo_file_ident.desc_tag, ident=257, location=3) assert(foo_file_ident.file_characteristics == 0) assert(foo_file_ident.len_fi == 4) internal_check_udf_longad(foo_file_ident.icb, size=2048, blocknum=4, abs_blocknum=261) assert(foo_file_ident.len_impl_use == 0) assert(foo_file_ident.impl_use == b'') assert(foo_file_ident.fi == b'foo') assert(foo_file_ident.file_entry is None) assert(foo_file_ident.isdir == False) assert(foo_file_ident.isparent == False) def check_udf_unicode(iso, filesize): assert(filesize == 571392) # Check ISO headers internal_check_pvd(iso.pvd, extent=16, size=279, ptbl_size=48, ptbl_location_le=270, ptbl_location_be=272) internal_check_terminator(iso.vdsts, extent=17) # Check PTR internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=274, parent=1) # Check ISO files/directories internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=274, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'TEST.TXT;1', dr_len=44, loc=0, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/TEST.TXT;1', contents=b'', which='iso_path') top_dirrecord = iso.pvd.root_dir_record.children[3] internal_check_dir_record(top_dirrecord, num_children=4, name=b'__', dr_len=36, extent_location=275, rr=False, rr_name=b'', rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) internal_check_dotdot_dir_record(top_dirrecord.children[1], rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) port_dirrecord = top_dirrecord.children[2] internal_check_dir_record(port_dirrecord, num_children=3, name=b'PORT', dr_len=38, extent_location=276, rr=False, rr_name=b'', rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) internal_check_dotdot_dir_record(port_dirrecord.children[1], rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) cyrillic_dirrecord = top_dirrecord.children[3] internal_check_dir_record(cyrillic_dirrecord, num_children=3, name=b'________', dr_len=42, extent_location=277, rr=False, rr_name=b'', rr_links=0, xa=False, hidden=False, is_cl_record=False, datalen=2048, relocated=False) internal_check_dotdot_dir_record(cyrillic_dirrecord.children[1], rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(port_dirrecord.children[2], name=b'________.TXT;1', dr_len=48, loc=0, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/__/PORT/________.TXT;1', contents=b'', which='iso_path') internal_check_file(cyrillic_dirrecord.children[2], name=b'________.TXT;1', dr_len=48, loc=0, datalen=0, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/__/________/________.TXT;1', contents=b'', which='iso_path') # Check UDF headers internal_check_udf_headers(iso, bea_extent=18, end_anchor_extent=278, part_length=21, unique_id=270, num_dirs=4, num_files=3) internal_check_udf_file_entry(iso.udf_root, location=259, tag_location=2, num_links=2, info_len=132, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(iso.udf_root.fi_descs[0], extent=260, tag_location=3, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) test_file_ident = iso.udf_root.fi_descs[1] internal_check_udf_file_ident_desc(test_file_ident, extent=260, tag_location=3, characteristics=0, blocknum=10, abs_blocknum=267, name=b'test.txt', isparent=False, isdir=False) test_file_entry = test_file_ident.file_entry internal_check_udf_file_entry(test_file_entry, location=267, tag_location=10, num_links=1, info_len=0, num_blocks_recorded=0, num_fi_descs=0, file_type='file', num_alloc_descs=0) p3_file_ident = iso.udf_root.fi_descs[2] internal_check_udf_file_ident_desc(p3_file_ident, extent=260, tag_location=3, characteristics=2, blocknum=4, abs_blocknum=261, name=b'\x04 \x04-', isparent=False, isdir=True) p3_file_entry = p3_file_ident.file_entry internal_check_udf_file_entry(p3_file_entry, location=261, tag_location=4, num_links=3, info_len=148, num_blocks_recorded=1, num_fi_descs=3, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(p3_file_entry.fi_descs[0], extent=262, tag_location=5, characteristics=10, blocknum=2, abs_blocknum=0, name=b'', isparent=True, isdir=True) port_file_ident = p3_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(port_file_ident, extent=262, tag_location=5, characteristics=2, blocknum=6, abs_blocknum=263, name=b'Port', isparent=False, isdir=True) port_file_entry = port_file_ident.file_entry internal_check_udf_file_entry(port_file_entry, location=263, tag_location=6, num_links=1, info_len=120, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(port_file_entry.fi_descs[0], extent=264, tag_location=7, characteristics=10, blocknum=None, abs_blocknum=None, name=b'', isparent=True, isdir=True) py_file_ident = p3_file_entry.fi_descs[2] internal_check_udf_file_ident_desc(py_file_ident, extent=262, tag_location=5, characteristics=2, blocknum=8, abs_blocknum=265, name=b'\x04 \x04C\x04:\x04>\x042\x04>\x044\x04A\x04B\x042\x040', isparent=False, isdir=True) py_file_entry = py_file_ident.file_entry internal_check_udf_file_entry(py_file_entry, location=265, tag_location=8, num_links=1, info_len=116, num_blocks_recorded=1, num_fi_descs=2, file_type='dir', num_alloc_descs=1) internal_check_udf_file_ident_desc(py_file_entry.fi_descs[0], extent=266, tag_location=9, characteristics=10, blocknum=None, abs_blocknum=None, name=b'', isparent=True, isdir=True) bn_file_ident = port_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(bn_file_ident, extent=264, tag_location=7, characteristics=0, blocknum=11, abs_blocknum=268, name=b'\x042\x048\x04@\x04B\x04C\x040\x04;\x04L\x04=\x04K\x049\x00 \x04?\x04>\x04@\x04B\x00.\x00t\x00x\x00t', isparent=False, isdir=False) bn_file_entry = bn_file_ident.file_entry internal_check_udf_file_entry(bn_file_entry, location=268, tag_location=11, num_links=1, info_len=0, num_blocks_recorded=0, num_fi_descs=0, file_type='file', num_alloc_descs=0) pyk_file_ident = py_file_entry.fi_descs[1] internal_check_udf_file_ident_desc(pyk_file_ident, extent=266, tag_location=9, characteristics=0, blocknum=12, abs_blocknum=269, name=b'\x04 \x04C\x04:\x04>\x042\x04>\x044\x04A\x04B\x042\x04>\x00 \x04?\x04>\x00.\x00t\x00x\x00t', isparent=False, isdir=False) pyk_file_entry = pyk_file_ident.file_entry internal_check_udf_file_entry(pyk_file_entry, location=269, tag_location=12, num_links=1, info_len=0, num_blocks_recorded=0, num_fi_descs=0, file_type='file', num_alloc_descs=0) def check_eltorito_get_bootcat(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') internal_check_file_contents(iso, path='/BOOT.CAT;1', contents=b'\x01'+b'\x00'*27+b'\xaa\x55\x55\xaa\x88'+b'\x00'*5+b'\x04\x00\x1a'+b'\x00'*2007, which='iso_path') def check_eltorito_uefi(iso, filesize): assert(filesize == 55296) internal_check_pvd(iso.pvd, extent=16, size=27, ptbl_size=10, ptbl_location_le=20, ptbl_location_be=22) internal_check_eltorito(iso, boot_catalog_extent=25, load_rba=26, media_type=0, system_type=0, bootable=True, platform_id=0xef) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) internal_check_root_dir_record(iso.pvd.root_dir_record, num_children=4, data_length=2048, extent_location=24, rr=False, rr_nlinks=0, xa=False, rr_onetwelve=False) internal_check_file(iso.pvd.root_dir_record.children[3], name=b'BOOT.CAT;1', dr_len=44, loc=25, datalen=2048, hidden=False, multi_extent=False) internal_check_file(iso.pvd.root_dir_record.children[2], name=b'BOOT.;1', dr_len=40, loc=26, datalen=5, hidden=False, multi_extent=False) internal_check_file_contents(iso, path='/BOOT.;1', contents=b'boot\n', which='iso_path') def check_isolevel4_deep_directory(iso, filesize): assert(filesize == 67584) internal_check_pvd(iso.pvd, extent=16, size=33, ptbl_size=94, ptbl_location_le=20, ptbl_location_be=22) internal_check_terminator(iso.vdsts, extent=18) internal_check_ptr(iso.pvd.root_dir_record.ptr, name=b'\x00', len_di=1, loc=24, parent=1) assert(len(iso.pvd.root_dir_record.children) == 3) dir1_record = iso.pvd.root_dir_record.children[2] internal_check_ptr(dir1_record.ptr, name=b'dir1', len_di=4, loc=None, parent=1) dir2_record = dir1_record.children[2] internal_check_ptr(dir2_record.ptr, name=b'dir2', len_di=4, loc=None, parent=2) dir3_record = dir2_record.children[2] internal_check_ptr(dir3_record.ptr, name=b'dir3', len_di=4, loc=None, parent=3) dir4_record = dir3_record.children[2] internal_check_ptr(dir4_record.ptr, name=b'dir4', len_di=4, loc=None, parent=4) dir5_record = dir4_record.children[2] internal_check_ptr(dir5_record.ptr, name=b'dir5', len_di=4, loc=None, parent=5) dir6_record = dir5_record.children[2] internal_check_ptr(dir6_record.ptr, name=b'dir6', len_di=4, loc=None, parent=6) dir7_record = dir6_record.children[2] internal_check_ptr(dir7_record.ptr, name=b'dir7', len_di=4, loc=None, parent=7) internal_check_file_contents(iso, path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/foo', contents=b'foo\n', which='iso_path') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1583463366.5840163 pycdlib-1.11.0/tests/integration/test_facade.py0000664000175000017500000005234300000000000024313 0ustar00clalancetteclalancette00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pycdlib from test_common import * def test_facade_iso9660_get_file_from_iso(tmpdir): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') foofile = os.path.join(str(tmpdir), 'foo') facade.get_file_from_iso(foofile, '/FOO.;1') iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'foo\n') def test_facade_iso9660_get_file_from_iso_fp(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() facade.get_file_from_iso_fp(out, '/FOO.;1') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_iso9660_add_fp(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() facade.get_file_from_iso_fp(out, '/FOO.;1') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_iso9660_add_fp_with_rr(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() facade.get_file_from_iso_fp(out, '/FOO.;1') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_iso9660_add_fp_with_rr_bad_name(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_iso9660_facade() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade.add_fp(BytesIO(foostr), len(foostr), 'FOO.;1') assert(str(excinfo.value) == "iso_path must start with '/'") iso.close() def test_facade_iso9660_add_file(tmpdir): iso = pycdlib.PyCdlib() iso.new() testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') facade = iso.get_iso9660_facade() facade.add_file(str(testout), '/FOO.;1') out = BytesIO() facade.get_file_from_iso_fp(out, '/FOO.;1') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_iso9660_add_file_with_rr(tmpdir): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') facade = iso.get_iso9660_facade() facade.add_file(str(testout), '/FOO.;1') out = BytesIO() facade.get_file_from_iso_fp(out, '/FOO.;1') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_iso9660_add_directory(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') rec = facade.get_record('/DIR1') assert(rec.file_identifier() == b'DIR1') iso.close() def test_facade_iso9660_add_directory_with_rr(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') rec = facade.get_record('/DIR1') assert(rec.file_identifier() == b'DIR1') iso.close() def test_facade_iso9660_rm_file(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_file('/FOO.;1') rec = facade.get_record('/') assert(len(rec.children) == 2) # The dot and dotdot records count iso.close() def test_facade_iso9660_rm_directory(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_directory('/DIR1') rec = facade.get_record('/') assert(len(rec.children) == 2) iso.close() def test_facade_iso9660_list_children(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') bootstr = b'boot\n' facade.add_fp(BytesIO(bootstr), len(bootstr), '/DIR1/BOOT.;1') full_path = None for child in facade.list_children('/DIR1'): if child.file_identifier() == b'BOOT.;1': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/DIR1/BOOT.;1') break assert(full_path is not None) iso.close() def test_facade_iso9660_get_record(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') rec = facade.get_record('/DIR1') assert(rec.file_identifier() == b'DIR1') iso.close() def test_facade_iso9660_walk(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() facade.add_directory('/DIR1') index = 0 for dirname, dirlist, filelist in facade.walk('/'): if index == 0: assert(dirname == '/') else: assert(dirname == '/DIR1') index += 1 iso.close() def test_facade_iso9660_open_file_from_iso(): iso = pycdlib.PyCdlib() iso.new() facade = iso.get_iso9660_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with facade.open_file_from_iso('/FOO.;1') as infp: assert(infp.read() == b'foo\n') assert(infp.tell() == 4) iso.close() def test_facade_joliet_not_joliet_iso(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade = pycdlib.facade.PyCdlibJoliet(iso) assert(str(excinfo.value) == 'Can only instantiate a Joliet facade for a Joliet ISO') def test_facade_joliet_get_file_from_iso(tmpdir): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') foofile = os.path.join(str(tmpdir), 'foo') facade.get_file_from_iso(foofile, '/foo') iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'foo\n') def test_facade_joliet_get_file_from_iso_fp(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_joliet_add_fp(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_joliet_add_file(tmpdir): iso = pycdlib.PyCdlib() iso.new(joliet=3) testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') facade = iso.get_joliet_facade() facade.add_file(str(testout), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_joliet_add_directory(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() facade.add_directory('/dir1') rec = facade.get_record('/dir1') assert(rec.file_identifier() == bytes('dir1'.encode('utf-16_be'))) iso.close() def test_facade_joliet_rm_file(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_file('/foo') rec = facade.get_record('/') assert(len(rec.children) == 2) # The dot and dotdot records count iso.close() def test_facade_joliet_rm_directory(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() facade.add_directory('/dir1') rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_directory('/dir1') rec = facade.get_record('/') assert(len(rec.children) == 2) iso.close() def test_facade_joliet_list_children(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() facade.add_directory('/dir1') bootstr = b'boot\n' facade.add_fp(BytesIO(bootstr), len(bootstr), '/dir1/boot') full_path = None for child in facade.list_children('/dir1'): if child.file_identifier() == bytes('boot'.encode('utf-16_be')): full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/dir1/boot') break assert(full_path is not None) iso.close() def test_facade_joliet_get_record(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() facade.add_directory('/dir1') rec = facade.get_record('/dir1') assert(rec.file_identifier() == bytes('dir1'.encode('utf-16_be'))) iso.close() def test_facade_joliet_walk(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() facade.add_directory('/dir1') index = 0 for dirname, dirlist, filelist in facade.walk('/'): if index == 0: assert(dirname == '/') else: assert(dirname == '/dir1') index += 1 iso.close() def test_facade_joliet_open_file_from_iso(): iso = pycdlib.PyCdlib() iso.new(joliet=3) facade = iso.get_joliet_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') with facade.open_file_from_iso('/foo') as infp: assert(infp.read() == b'foo\n') assert(infp.tell() == 4) iso.close() def test_facade_rock_ridge_not_rock_ridge_iso(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade = pycdlib.facade.PyCdlibRockRidge(iso) assert(str(excinfo.value) == 'Can only instantiate a Rock Ridge facade for a Rock Ridge ISO') def test_facade_rock_ridge_get_file_from_iso(tmpdir): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) foofile = os.path.join(str(tmpdir), 'foo') facade.get_file_from_iso(foofile, '/foo') iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'foo\n') def test_facade_rock_ridge_get_file_from_iso_fp(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_rock_ridge_add_fp(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_rock_ridge_add_fp_bad_filename(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade.add_fp(BytesIO(foostr), len(foostr), 'foo', 0o040555) assert(str(excinfo.value) == "rr_path must start with '/'") iso.close() def test_facade_rock_ridge_add_file(tmpdir): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') facade = iso.get_rock_ridge_facade() facade.add_file(str(testout), '/foo', 0o040555) out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_rock_ridge_add_directory(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() facade.add_directory('/dir1', 0o040555) rec = facade.get_record('/dir1') assert(rec.file_identifier() == b'DIR1') iso.close() def test_facade_rock_ridge_rm_file(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_file('/foo') rec = facade.get_record('/') assert(len(rec.children) == 2) # The dot and dotdot records count iso.close() def test_facade_rock_ridge_rm_file_bad_filename(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) rec = facade.get_record('/') assert(len(rec.children) == 3) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade.rm_file('foo') assert(str(excinfo.value) == "rr_path must start with '/'") iso.close() def test_facade_rock_ridge_rm_file_root(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) rec = facade.get_record('/') assert(len(rec.children) == 3) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade.rm_file('/') assert(str(excinfo.value) == 'Cannot remove a directory with rm_file (try rm_directory instead)') iso.close() def test_facade_rock_ridge_rm_directory(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() facade.add_directory('/dir1', 0o040555) rec = facade.get_record('/') assert(len(rec.children) == 3) facade.rm_directory('/dir1') rec = facade.get_record('/') assert(len(rec.children) == 2) iso.close() def test_facade_rock_ridge_rm_directory_root(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) rec = facade.get_record('/') assert(len(rec.children) == 3) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade.rm_directory('/') assert(str(excinfo.value) == 'Cannot remove base directory') iso.close() def test_facade_rock_ridge_add_symlink(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) facade.add_symlink('/sym', 'foo') rec = facade.get_record('/') assert(len(rec.children) == 4) iso.close() def test_facade_rock_ridge_list_children(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() facade.add_directory('/dir1', 0o040555) bootstr = b'boot\n' facade.add_fp(BytesIO(bootstr), len(bootstr), '/dir1/boot', 0o040555) full_path = None for child in facade.list_children('/dir1'): if child.file_identifier() == b'BOOT.;1': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/DIR1/BOOT.;1') break assert(full_path is not None) iso.close() def test_facade_rock_ridge_get_record(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() facade.add_directory('/dir1', 0o040555) rec = facade.get_record('/dir1') assert(rec.file_identifier() == b'DIR1') iso.close() def test_facade_rock_ridge_walk(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() facade.add_directory('/dir1', 0o040555) index = 0 for dirname, dirlist, filelist in facade.walk('/'): if index == 0: assert(dirname == '/') else: assert(dirname == '/dir1') index += 1 iso.close() def test_facade_rock_ridge_open_file_from_iso(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') facade = iso.get_rock_ridge_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo', 0o040555) with facade.open_file_from_iso('/foo') as infp: assert(infp.read() == b'foo\n') assert(infp.tell() == 4) iso.close() def test_facade_udf_not_udf_iso(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: facade = pycdlib.facade.PyCdlibUDF(iso) assert(str(excinfo.value) == 'Can only instantiate a UDF facade for a UDF ISO') def test_facade_udf_get_file_from_iso(tmpdir): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') foofile = os.path.join(str(tmpdir), 'foo') facade.get_file_from_iso(foofile, '/foo') iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'foo\n') def test_facade_udf_get_file_from_iso_fp(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_udf_add_fp(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_udf_add_file(tmpdir): iso = pycdlib.PyCdlib() iso.new(udf='2.60') testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') facade = iso.get_udf_facade() facade.add_file(str(testout), '/foo') out = BytesIO() facade.get_file_from_iso_fp(out, '/foo') assert(out.getvalue() == b'foo\n') iso.close() def test_facade_udf_add_directory(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() facade.add_directory('/dir1') rec = facade.get_record('/dir1') assert(rec.file_identifier() == b'dir1') iso.close() def test_facade_udf_rm_file(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') rec = facade.get_record('/foo') assert(rec.is_file()) facade.rm_file('/foo') iso.close() def test_facade_udf_rm_directory(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() facade.add_directory('/dir1') rec = facade.get_record('/dir1') assert(rec.is_dir()) facade.rm_directory('/dir1') iso.close() def test_facade_udf_add_symlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') facade.add_symlink('/sym', 'foo') rec = facade.get_record('/sym') assert(rec.is_symlink()) iso.close() def test_facade_udf_list_children(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() facade.add_directory('/dir1') bootstr = b'boot\n' facade.add_fp(BytesIO(bootstr), len(bootstr), '/dir1/boot') full_path = None for child in facade.list_children('/dir1'): if child is not None: if child.file_identifier() == b'boot': break else: assert(False) iso.close() def test_facade_udf_get_record(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() facade.add_directory('/dir1') rec = facade.get_record('/dir1') assert(rec.file_identifier() == b'dir1') iso.close() def test_facade_udf_walk(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() facade.add_directory('/dir1') index = 0 for dirname, dirlist, filelist in facade.walk('/'): if index == 0: assert(dirname == '/') else: assert(dirname == '/dir1') index += 1 iso.close() def test_facade_udf_open_file_from_iso(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') facade = iso.get_udf_facade() foostr = b'foo\n' facade.add_fp(BytesIO(foostr), len(foostr), '/foo') with facade.open_file_from_iso('/foo') as infp: assert(infp.read() == b'foo\n') assert(infp.tell() == 4) iso.close() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1599880065.584995 pycdlib-1.11.0/tests/integration/test_hybrid.py0000664000175000017500000031063500000000000024372 0ustar00clalancetteclalancette00000000000000import pytest import subprocess import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import shutil sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pycdlib from test_common import * def do_a_test(iso, check_func): out = BytesIO() iso.write_fp(out) check_func(iso, len(out.getvalue())) iso2 = pycdlib.PyCdlib() iso2.open_fp(out) check_func(iso2, len(out.getvalue())) iso2.close() def test_hybrid_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('nofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefile) iso.close() def test_hybrid_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') do_a_test(iso, check_onedir) iso.close() def test_hybrid_twofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') do_a_test(iso, check_twofiles) iso.close() def test_hybrid_twofiles2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_twofiles) iso.close() def test_hybrid_twofiles3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') do_a_test(iso, check_twofiles) iso.close() def test_hybrid_twofiles4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_twofiles) iso.close() def test_hybrid_twodirs(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twodir') outfile = str(indir)+'.iso' indir.mkdir('aa') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/BB') do_a_test(iso, check_twodirs) iso.close() def test_hybrid_twodirs2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twodir') outfile = str(indir)+'.iso' indir.mkdir('bb') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/AA') do_a_test(iso, check_twodirs) iso.close() def test_hybrid_twodirs3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twodir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/AA') iso.add_directory('/BB') do_a_test(iso, check_twodirs) iso.close() def test_hybrid_twodirs4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twodir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/BB') iso.add_directory('/AA') do_a_test(iso, check_twodirs) iso.close() def test_hybrid_rmfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/BAR.;1') do_a_test(iso, check_onefile) iso.close() def test_hybrid_rmdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1') do_a_test(iso, check_onefile) iso.close() def test_hybrid_onefileonedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') do_a_test(iso, check_onefileonedir) iso.close() def test_hybrid_onefileonedir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefileonedir) iso.close() def test_hybrid_onefileonedir3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefileonedir) iso.close() def test_hybrid_onefileonedir4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_directory('/DIR1') do_a_test(iso, check_onefileonedir) iso.close() def test_hybrid_onefile_onedirwithfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_hybrid_onefile_onedirwithfile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_hybrid_onefile_onedirwithfile3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_directory('/DIR1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_hybrid_onefile_onedirwithfile4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_hybrid_twoextentfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) outstr = b'' for j in range(0, 8): for i in range(0, 256): outstr += struct.pack('=B', i) outstr += struct.pack('=B', 0) iso.add_fp(BytesIO(outstr), len(outstr), '/BIGFILE.;1') do_a_test(iso, check_twoextentfile) iso.close() def test_hybrid_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('manydirs') outfile = str(indir)+'.iso' numdirs = 293 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR294') iso.add_directory('/DIR295') do_a_test(iso, check_dirs_overflow_ptr_extent) iso.close() def test_hybrid_ptr_extent2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('manydirs') outfile = str(indir)+'.iso' numdirs = 295 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR294') iso.rm_directory('/DIR295') do_a_test(iso, check_dirs_just_short_ptr_extent) iso.close() def test_hybrid_remove_many(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('manydirs') outfile = str(indir)+'.iso' numdirs = 295 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) # Now remove all but one of the entries. for i in range(2, 1+numdirs): iso.rm_directory('/DIR' + str(i)) do_a_test(iso, check_onedir) iso.close() def test_hybrid_twoleveldeepdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1/SUBDIR1') do_a_test(iso, check_twoleveldeepdir) iso.close() def test_hybrid_twoleveldeepdir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') do_a_test(iso, check_twoleveldeepdir) iso.close() def test_hybrid_rmsubdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') dir1.mkdir('subdir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1/SUBDIR1') do_a_test(iso, check_onedir) iso.close() def test_hybrid_removeall(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') dir1.mkdir('subdir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1/SUBDIR1') iso.rm_directory('/DIR1') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_add_new_file_to_subdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_hybrid_eltorito_add(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritohybrid') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) # Now add the eltorito stuff bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_eltorito_nofiles) iso.close() def test_hybrid_eltorito_remove(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_eltorito() iso.rm_file('/BOOT.;1') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_eltorito_add_bootcat(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritotwofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'aa'), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_eltorito_twofile) iso.close() def test_hybrid_rr_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrnofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_nofiles) iso.close() def test_hybrid_rr_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_onefile) iso.close() def test_hybrid_rr_rmfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrrmfile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'baz'), 'wb') as outfp: outfp.write(b'baz\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/BAZ.;1', rr_name='baz') do_a_test(iso, check_rr_onefile) iso.close() def test_hybrid_rr_onefileonedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefileonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_onefileonedir) iso.close() def test_hybrid_rr_onefileonedirwithfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefileonedirwithfile') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1', rr_name='bar') do_a_test(iso, check_rr_onefileonedirwithfile) iso.close() def test_hybrid_rr_and_joliet_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrjolietnofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1', rr_name='foo', joliet_path='/foo') do_a_test(iso, check_joliet_and_rr_nofiles) iso.close() def test_hybrid_rr_and_joliet_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrjolietonefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') do_a_test(iso, check_joliet_and_rr_onefile) iso.close() def test_hybrid_rr_and_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_nofiles) iso.close() def test_hybrid_rr_and_eltorito_nofiles2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritonofiles2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_nofiles) iso.close() def test_hybrid_rr_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_and_eltorito_onefile) iso.close() def test_hybrid_rr_and_eltorito_onefile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonefile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_onefile) iso.close() def test_hybrid_rr_and_eltorito_onefile3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonefile3') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_and_eltorito_onefile) iso.close() def test_hybrid_rr_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonedir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_hybrid_rr_and_eltorito_onedir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonedir2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', rr_name='dir1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_hybrid_rr_and_eltorito_onedir3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonedir3') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_hybrid_rr_and_eltorito_onedir4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritoonedir4') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_hybrid_rr_and_eltorito_rmdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritormdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_and_eltorito_nofiles) iso.close() def test_hybrid_rr_and_eltorito_rmdir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rreltoritormdir2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') dir1 = indir.mkdir('dir1') dir1.mkdir('subdir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1/SUBDIR1', rr_name='subdir1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_hybrid_joliet_and_eltorito_remove(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoremove') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_eltorito() iso.rm_file('/BOOT.;1', joliet_path='/boot') do_a_test(iso, check_joliet_nofiles) iso.close() def test_hybrid_joliet_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') do_a_test(iso, check_joliet_and_eltorito_onefile) iso.close() def test_hybrid_joliet_and_eltorito_onefile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoonefile2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_onefile) iso.close() def test_hybrid_joliet_and_eltorito_onefile3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoonefile3') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_onefile) iso.close() def test_hybrid_joliet_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoonedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_onedir) iso.close() def test_hybrid_joliet_and_eltorito_onedir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoonedir2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_joliet_and_eltorito_onedir) iso.close() def test_hybrid_isohybrid(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4) # Now add the syslinux iso.add_isohybrid() do_a_test(iso, check_isohybrid) iso.close() def test_hybrid_isohybrid2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) # Now add the syslinux iso.add_isohybrid() do_a_test(iso, check_isohybrid) iso.close() @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_hybrid_isohybrid3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-o', str(outfile), str(indir)]) subprocess.call(['isohybrid', '-v', str(outfile)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_isohybrid() iso.rm_eltorito() iso.rm_file('/ISOLINUX.BIN;1') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_joliet_rr_and_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1', joliet_path='/foo', rr_name='foo') do_a_test(iso, check_joliet_rr_and_eltorito_nofiles) iso.close() def test_hybrid_joliet_rr_and_eltorito_nofiles2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritonofiles2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_rr_and_eltorito_nofiles) iso.close() def test_hybrid_joliet_rr_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') do_a_test(iso, check_joliet_rr_and_eltorito_onefile) iso.close() def test_hybrid_joliet_rr_and_eltorito_onefile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonefile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_rr_and_eltorito_onefile) iso.close() def test_hybrid_joliet_rr_and_eltorito_onefile3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonefile3') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_rr_and_eltorito_onefile) iso.close() def test_hybrid_joliet_rr_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_rr_and_eltorito_onedir) iso.close() def test_hybrid_joliet_rr_and_eltorito_onedir2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonedir2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_rr_and_eltorito_onedir) iso.close() def test_hybrid_joliet_rr_and_eltorito_onedir3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrreltoritoonedir2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_rr_and_eltorito_onedir) iso.close() def test_hybrid_rr_rmfile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrrmfile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_nofiles) iso.close() def test_hybrid_rr_rmdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrrmfile2') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_nofiles) iso.close() def test_hybrid_xa_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1') do_a_test(iso, check_xa_nofiles) iso.close() def test_hybrid_xa_nofiles2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/FOO.;1') do_a_test(iso, check_xa_nofiles) iso.close() def test_hybrid_xa_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_xa_onefile) iso.close() def test_hybrid_xa_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1') do_a_test(iso, check_xa_onedir) iso.close() def test_hybrid_sevendeepdirs(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('sevendeepdirs') outfile = str(indir)+'.iso' numdirs = 8 x = indir for i in range(1, 1+numdirs): x = x.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') do_a_test(iso, check_sevendeepdirs) iso.close() def test_hybrid_xa_joliet_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_xa_joliet_onedir) iso.close() def test_hybrid_xa_joliet_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') do_a_test(iso, check_xa_joliet_onefile) iso.close() def test_hybrid_isolevel4_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file('/bar') do_a_test(iso, check_isolevel4_onefile) iso.close() def test_hybrid_isolevel4_onefile2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarmfile2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/dir1') do_a_test(iso, check_isolevel4_onefile) iso.close() def test_hybrid_isolevel4_eltorito(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4eltorito') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') do_a_test(iso, check_isolevel4_eltorito) iso.close() def test_hybrid_isolevel4_eltorito2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4eltorito') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_eltorito() iso.rm_file('/boot') do_a_test(iso, check_isolevel4_nofiles) iso.close() def test_hybrid_eltorito_multi_boot(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') do_a_test(iso, check_eltorito_multi_boot) iso.close() def test_hybrid_eltorito_multi_boot_boot_info(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat', boot_info_table=True) do_a_test(iso, check_eltorito_multi_boot) iso.close() def test_hybrid_eltorito_multi_boot_hard_link(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_hard_link(iso_new_path='/bootlink', iso_old_path='/boot2') iso.add_eltorito('/boot2', '/boot.cat') do_a_test(iso, check_eltorito_multi_boot_hard_link) iso.close() def open_and_check(outfile, checkfunc): iso = pycdlib.PyCdlib() iso.open(str(outfile)) checkfunc(iso, os.stat(str(outfile)).st_size) iso.close() def test_hybrid_modify_in_place_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and modify it. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1') iso.close() # Now re-open it and check things out. open_and_check(outfile, check_onefile) def test_hybrid_joliet_modify_in_place_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietmodifyinplaceonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and modify it. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.close() # Now re-open it and check things out. open_and_check(outfile, check_joliet_onefile) def test_hybrid_modify_in_place_iso_level4_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/foo') iso.close() # Now open up the ISO with pycdlib and modify it. open_and_check(outfile, check_isolevel4_onefile) def test_hybrid_modify_in_place_udf(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceoneudf') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1') iso.close() # Now open up the ISO with pycdlib and modify it. open_and_check(outfile, check_udf_onefile) def test_hybrid_modify_in_place_udf_shrink(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceoneudfshrink') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foobarbaz\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1') iso.close() # Now open up the ISO with pycdlib and modify it. open_and_check(outfile, check_udf_onefile) def test_hybrid_try_to_use_new_on_open_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new() assert(str(excinfo.value) == 'This object already has an ISO; either close it or create a new object') iso.close() def test_hybrid_try_to_use_open_on_new_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'This object already has an ISO; either close it or create a new object') iso.close() def test_hybrid_modify_in_place_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_modify_in_place_read_only(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with open(str(outfile), 'rb') as fp: iso.open_fp(fp) foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') assert(str(excinfo.value) == 'To modify a file in place, the original ISO must have been opened in a write mode (r+, w, or a)') iso.close() def test_hybrid_add_isohybrid_file_wrong_size(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with open(os.path.join(str(indir), 'file.bin'), 'wb') as outfp: outfp.write(b'file') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid(os.path.join(str(indir), 'file.bin')) assert(str(excinfo.value) == 'Invalid signature on boot file for iso hybrid') iso.close() def test_hybrid_add_isohybrid_no_eltorito(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid('/usr/share/syslinux/isohdpfx.bin') assert(str(excinfo.value) == 'The ISO must have an El Torito Boot Record to add isohybrid support') iso.close() def test_hybrid_eltorito_remove_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_eltorito() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_eltorito_remove_not_present(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_eltorito() assert(str(excinfo.value) == 'This ISO does not have an El Torito Boot Record') iso.close() def test_hybrid_rmdir_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory('/DIR1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_rmdir_slash(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory('/') assert(str(excinfo.value) == 'Cannot remove base directory') iso.close() def test_hybrid_rmdir_not_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory('/FOO.;1') assert(str(excinfo.value) == 'Cannot remove a file with rm_directory (try rm_file instead)') iso.close() def test_hybrid_rmdir_not_empty(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory('/DIR1') assert(str(excinfo.value) == 'Directory must be empty to use rm_directory') iso.close() def test_hybrid_rmfile_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoremove') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('/BOOT.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_rmfile_bad_filename(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolieteltoritoremove') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('BOOT.;1') assert(str(excinfo.value) == 'Must be a path starting with /') iso.close() def test_hybrid_rmfile_not_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('/DIR1') assert(str(excinfo.value) == 'Cannot remove a directory with rm_file (try rm_directory instead)') iso.close() def test_hybrid_add_directory_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/DIR1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_addfile_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rmdir') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_modify_in_place_bad_path(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.modify_file_in_place(BytesIO(foostr), len(foostr), 'foo', rr_name='foo', joliet_path='/foo') assert(str(excinfo.value) == 'Must be a path starting with /') iso.close() def test_hybrid_modify_in_place_grow_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'f'*2049 with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/foo') assert(str(excinfo.value) == 'When modifying a file in-place, the number of extents for a file cannot change!') iso.close() def test_hybrid_modify_in_place_modify_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/dir1') assert(str(excinfo.value) == 'Cannot modify a directory with modify_file_in_place') iso.close() def test_hybrid_joliet_isolevel4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietisolevel4') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_isolevel4) iso.close() def test_hybrid_joliet_isolevel4_2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietisolevel4') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', joliet_path='/foo') do_a_test(iso, check_joliet_isolevel4) iso.close() def test_hybrid_joliet_isolevel4_3(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietisolevel4') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', joliet_path='/foo') iso.add_directory('/dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_isolevel4) iso.close() def test_hybrid_eltorito_remove_with_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' indir.mkdir('a') with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_eltorito() iso.rm_file('/BOOT.;1') iso.rm_directory('/A') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_modify_in_place_dirrecord_spillover(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') for i in range(1, 49): fname = os.path.join(str(dir1), 'foo%.2d' % (i)) with open(fname, 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/DIR1/FOO48.;1') iso.close() # Now open up the ISO with pycdlib and modify it. open_and_check(outfile, check_modify_in_place_spillover) def test_hybrid_modify_in_place_dirrecord_spillover2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceonefile') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') for i in range(1, 49): fname = os.path.join(str(dir1), 'foo%.2d' % (i)) with open(fname, 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile), 'r+b') foostr = b'foo\n' iso.modify_file_in_place(BytesIO(foostr), len(foostr), '/DIR1/FOO40.;1') iso.close() # Now open up the ISO with pycdlib and modify it. open_and_check(outfile, check_modify_in_place_spillover) def test_hybrid_shuffle_deep(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeepreshuffle') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'dir8', 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) # Before making changes, save off the extent location of DIR1 dir1 = iso.pvd.root_dir_record.children[2] assert(dir1.file_identifier() == b'DIR1') rr_moved = iso.pvd.root_dir_record.children[3] assert(rr_moved.file_identifier() == b'RR_MOVED') dir8_rr = rr_moved.children[2] assert(dir8_rr.file_identifier() == b'DIR8') assert(dir8_rr.rock_ridge.dr_entries.rr_record is not None) orig_pl = dir8_rr.children[1].rock_ridge.dr_entries.pl_record.parent_log_block_num dir2 = dir1.children[2] assert(dir2.file_identifier() == b'DIR2') dir3 = dir2.children[2] assert(dir3.file_identifier() == b'DIR3') dir4 = dir3.children[2] assert(dir4.file_identifier() == b'DIR4') dir5 = dir4.children[2] assert(dir5.file_identifier() == b'DIR5') dir6 = dir5.children[2] assert(dir6.file_identifier() == b'DIR6') dir7 = dir6.children[2] assert(dir7.file_identifier() == b'DIR7') dir8 = dir7.children[2] assert(dir8.file_identifier() == b'DIR8') assert(dir8.rock_ridge.dr_entries.cl_record is not None) orig_cl = dir8.rock_ridge.dr_entries.cl_record.child_log_block_num iso.add_directory('/A', rr_name='a') iso.force_consistency() new_cl = dir8.rock_ridge.dr_entries.cl_record.child_log_block_num assert(orig_cl != new_cl) new_pl = dir8_rr.children[1].rock_ridge.dr_entries.pl_record.parent_log_block_num assert(orig_pl != new_pl) iso.close() def test_hybrid_hidden_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'aaaaaaaa'), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.set_hidden('/AAAAAAAA.;1') do_a_test(iso, check_hidden_file) iso.close() def test_hybrid_hidden_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.set_hidden('/DIR1') do_a_test(iso, check_hidden_dir) iso.close() def test_hybrid_clear_hidden_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-hidden', 'foo', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.clear_hidden('/FOO.;1') do_a_test(iso, check_onefile) iso.close() def test_hybrid_clear_hidden_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-hidden', 'dir1', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.clear_hidden('/DIR1') do_a_test(iso, check_onedir) iso.close() @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_hybrid_isohybrid_file_before(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-o', str(outfile), str(indir)]) subprocess.call(['isohybrid', '-v', str(outfile)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_isohybrid_file_before) iso.close() def test_hybrid_joliet_dirs_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietmanydirs') outfile = str(indir)+'.iso' numdirs = 214 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR215', joliet_path='/dir215') iso.add_directory('/DIR216', joliet_path='/dir216') do_a_test(iso, check_joliet_dirs_overflow_ptr_extent) iso.close() def test_hybrid_joliet_dirs_ptr_extent2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietmanydirs') outfile = str(indir)+'.iso' numdirs = 216 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR216', joliet_path='/dir216') do_a_test(iso, check_joliet_dirs_just_short_ptr_extent) iso.close() def test_hybrid_joliet_dirs_add_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('dirsaddptrextent') outfile = str(indir)+'.iso' numdirs = 293 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR294', joliet_path='/dir294') iso.add_directory('/DIR295', joliet_path='/dir295') do_a_test(iso, check_joliet_dirs_add_ptr_extent) iso.close() def test_hybrid_joliet_dirs_rm_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('dirsrmptrextent') outfile = str(indir)+'.iso' numdirs = 295 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/DIR294', joliet_path='/dir294') iso.rm_directory('/DIR295', joliet_path='/dir295') do_a_test(iso, check_joliet_dirs_rm_ptr_extent) iso.close() def test_hybrid_joliet_rm_large_directory(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('dirsrmptrextent') outfile = str(indir)+'.iso' numdirs = 50 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) for i in range(1, 1+numdirs): iso.rm_directory('/DIR%d' % i, joliet_path='/dir%d' % i) do_a_test(iso, check_joliet_nofiles) iso.close() def test_hybrid_set_relocated_name_not_initialized(tmpdir): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_relocated_name('RR_MOVED', 'rr_moved') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_hybrid_set_relocated_not_rockridge(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('setrelocatednotrr') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_relocated_name('RR_MOVED', 'rr_moved') assert(str(excinfo.value) == 'Can only set the relocated name on a Rock Ridge ISO') iso.close() def test_hybrid_set_relocated_change_name(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('setrelocatednotrr') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.set_relocated_name('RR_MOVED', 'rr_moved') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_relocated_name('XX_MOVED', 'xx_moved') assert(str(excinfo.value) == 'Changing the existing rr_moved name is not allowed') iso.close() def test_hybrid_set_relocated_same_name(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('setrelocatednotrr') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.set_relocated_name('RR_MOVED', 'rr_moved') iso.set_relocated_name('RR_MOVED', 'rr_moved') iso.close() def test_hybrid_rr_hidden_relocated(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('setrelocatednotrr') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.set_relocated_name('_RR_MOVE', '.rr_moved') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8/DIR9', rr_name='dir9') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8/DIR9/FOO.;1', 'foo') do_a_test(iso, check_rr_relocated_hidden) iso.close() def test_hybrid_rr_relocated_list_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeeplistdir') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8').mkdir('dir9') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) for index, child in enumerate(iso.list_dir('/dir1/dir2/dir3/dir4/dir5/dir6/dir7')): if index == 0: assert(child.is_dot()) elif index == 1: assert(child.is_dotdot()) elif index == 2: assert(child.file_identifier() == b'DIR8') assert(child.rock_ridge.name() == b'dir8') for index, child in enumerate(iso.list_dir('/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8')): if index == 0: assert(child.is_dot()) elif index == 1: assert(child.is_dotdot()) assert(child.rock_ridge.parent_link_record_exists()) else: assert(False) def test_hybrid_rm_hard_link(tmpdir): indir = tmpdir.mkdir('rmhardlink') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_nofiles) iso.close() def test_hybrid_udf_onedir(tmpdir): indir = tmpdir.mkdir('udfnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', udf_path='/dir1') do_a_test(iso, check_udf_onedir) iso.close() def test_hybrid_udf_twodirs(tmpdir): indir = tmpdir.mkdir('udfnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR1', udf_path='/dir1') iso.add_directory('/DIR2', udf_path='/dir2') do_a_test(iso, check_udf_twodirs) iso.close() def test_hybrid_udf_twodirs2(tmpdir): indir = tmpdir.mkdir('udfnofiles') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.add_directory('/DIR2', udf_path='/dir2') do_a_test(iso, check_udf_twodirs) iso.close() def test_hybrid_udf_dir_oneshort(tmpdir): indir = tmpdir.mkdir('udfdironeshort') outfile = str(indir)+'.iso' for i in range(ord('a'), ord('v')): dirname = chr(i) * 64 indir.mkdir(dirname) subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_directory('/UUUUUUUU', udf_path='/'+'u'*64) do_a_test(iso, check_udf_dir_oneshort) iso.close() def test_hybrid_udf_zero_udf_file_entry(tmpdir): indir = tmpdir.mkdir('udfzerofileentry') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '3', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO and zero out the UDF File Entry with open(str(outfile), 'r+b') as fp: fp.seek(261*2048) fp.write(b'\x00'*2048) iso = pycdlib.PyCdlib() iso.open(str(outfile)) do_a_test(iso, check_udf_zeroed_file_entry) iso.close() def test_hybrid_udf_rm_zero_udf_file_entry(tmpdir): indir = tmpdir.mkdir('udfzerofileentry') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '3', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO and zero out the UDF File Entry with open(str(outfile), 'r+b') as fp: fp.seek(261*2048) fp.write(b'\x00'*2048) iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_file(iso_path='/FOO.;1') iso.rm_file(udf_path='/foo') do_a_test(iso, check_udf_nofiles) iso.close() def test_hybrid_udf_rm_hard_link_zero_udf_file_entry(tmpdir): indir = tmpdir.mkdir('udfzerofileentry') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '3', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO and zero out the UDF File Entry with open(str(outfile), 'r+b') as fp: fp.seek(261*2048) fp.write(b'\x00'*2048) iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.rm_hard_link(iso_path='/FOO.;1') iso.rm_hard_link(udf_path='/foo') do_a_test(iso, check_udf_nofiles) iso.close() def test_hybrid_udf_get_from_iso_zero_udf_file_entry(tmpdir): indir = tmpdir.mkdir('udfzerofileentry') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '3', '-udf', '-o', str(outfile), str(indir)]) # Now open up the ISO and zero out the UDF File Entry with open(str(outfile), 'r+b') as fp: fp.seek(261*2048) fp.write(b'\x00'*2048) iso = pycdlib.PyCdlib() iso.open(str(outfile)) out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, udf_path='/foo') assert(str(excinfo.value) == 'Cannot get the contents of an empty UDF File Entry') iso.close() def test_hybrid_boot_record_retain_system_use(tmpdir): indir = tmpdir.mkdir('bootrecordretainsystemuse') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now modify the boot record and add some stuff into system use system_use_offset = 17*2048 + 0x4b with open(str(outfile), 'r+b') as fp: fp.seek(system_use_offset) fp.write(b'hello') # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() iso.write_fp(out) out.seek(system_use_offset) assert(out.read(5) == b'hello') iso.close() # FIXME: write tests for 'empty' UDF File Entries (like on the Win2k8 ISO). ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5869951 pycdlib-1.11.0/tests/integration/test_new.py0000664000175000017500000056725000000000000023711 0ustar00clalancetteclalancette00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import io import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pycdlib from test_common import * def do_a_test(iso, check_func, tmpdir=None): if tmpdir is None: out = BytesIO() def do_getlen(obj): return len(obj.getvalue()) def do_sync(obj): pass else: out = open(os.path.join(str(tmpdir), check_func.__name__), 'w+b') def do_getlen(obj): return os.fstat(out.fileno()).st_size def do_sync(obj): obj.flush() os.fsync(obj.fileno()) try: iso.write_fp(out) do_sync(out) check_func(iso, do_getlen(out)) iso2 = pycdlib.PyCdlib() iso2.open_fp(out) check_func(iso2, do_getlen(out)) iso2.close() finally: out.close() def test_new_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() do_a_test(iso, check_nofiles) iso.close() def test_new_onefile(): # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefile) iso.close() def test_new_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. iso.add_directory('/DIR1') do_a_test(iso, check_onedir) iso.close() def test_new_twofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new files. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') do_a_test(iso, check_twofiles) iso.close() def test_new_twofiles2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new files. barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_twofiles) iso.close() def test_new_twodirs(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directories. iso.add_directory('/AA') iso.add_directory('/BB') do_a_test(iso, check_twodirs) iso.close() def test_new_twodirs2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directories. iso.add_directory('/BB') iso.add_directory('/AA') do_a_test(iso, check_twodirs) iso.close() def test_new_onefileonedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add new directory. iso.add_directory('/DIR1') do_a_test(iso, check_onefileonedir) iso.close() def test_new_onefileonedir2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directory. iso.add_directory('/DIR1') # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefileonedir) iso.close() def test_new_onefile_onedirwithfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add new directory. iso.add_directory('/DIR1') # Add new sub-file. barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1') do_a_test(iso, check_onefile_onedirwithfile) iso.close() def test_new_tendirs(): numdirs = 10 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() for i in range(1, 1+numdirs): iso.add_directory('/DIR%d' % i) do_a_test(iso, check_tendirs) iso.close() def test_new_dirs_overflow_ptr_extent(): numdirs = 295 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() for i in range(1, 1+numdirs): iso.add_directory('/DIR%d' % i) do_a_test(iso, check_dirs_overflow_ptr_extent) iso.close() def test_new_dirs_just_short_ptr_extent(): numdirs = 293 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() for i in range(1, 1+numdirs): iso.add_directory('/DIR%d' % i) # Now add two more to push it over the boundary iso.add_directory('/DIR294') iso.add_directory('/DIR295') # Now remove them to put it back down below the boundary. iso.rm_directory('/DIR295') iso.rm_directory('/DIR294') do_a_test(iso, check_dirs_just_short_ptr_extent) iso.close() def test_new_twoextentfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() outstr = b'' for j in range(0, 8): for i in range(0, 256): outstr += struct.pack('=B', i) outstr += struct.pack('=B', 0) iso.add_fp(BytesIO(outstr), len(outstr), '/BIGFILE.;1') do_a_test(iso, check_twoextentfile) iso.close() def test_new_twoleveldeepdir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directory. iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') do_a_test(iso, check_twoleveldeepdir) iso.close() def test_new_twoleveldeepfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directory. iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/SUBDIR1/FOO.;1') do_a_test(iso, check_twoleveldeepfile) iso.close() def test_new_dirs_overflow_ptr_extent_reverse(): numdirs = 295 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() for i in reversed(range(1, 1+numdirs)): iso.add_directory('/DIR%d' % i) do_a_test(iso, check_dirs_overflow_ptr_extent) iso.close() def test_new_toodeepdir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. iso.add_directory('/DIR1') iso.add_directory('/DIR1/DIR2') iso.add_directory('/DIR1/DIR2/DIR3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8') assert(str(excinfo.value) == 'Directory levels too deep (maximum is 7)') # Now make sure we can re-open the written ISO. out = BytesIO() iso.write_fp(out) pycdlib.PyCdlib().open_fp(out) iso.close() def test_new_toodeepfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. iso.add_directory('/DIR1') iso.add_directory('/DIR1/DIR2') iso.add_directory('/DIR1/DIR2/DIR3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7') foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/FOO.;1') assert(str(excinfo.value) == 'Directory levels too deep (maximum is 7)') # Now make sure we can re-open the written ISO. out = BytesIO() iso.write_fp(out) pycdlib.PyCdlib().open_fp(out) iso.close() def test_new_removefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add second new file. barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') # Remove the second file. iso.rm_file('/BAR.;1') do_a_test(iso, check_onefile) iso.close() def test_new_removedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add new directory. iso.add_directory('/DIR1') # Remove the directory iso.rm_directory('/DIR1') do_a_test(iso, check_onefile) iso.close() def test_new_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_eltorito_nofiles) iso.close() def test_new_rm_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_eltorito() iso.rm_file('/BOOT.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_eltorito_twofile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AA.;1') do_a_test(iso, check_eltorito_twofile) iso.close() def test_new_rr_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') do_a_test(iso, check_rr_nofiles) iso.close() def test_new_rr_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_onefile) iso.close() def test_new_rr_twofile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') # Add a new file. barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1', rr_name='bar') do_a_test(iso, check_rr_twofile) iso.close() def test_new_rr_onefileonedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') # Add new directory. iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_onefileonedir) iso.close() def test_new_rr_onefileonedirwithfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') # Add new directory. iso.add_directory('/DIR1', rr_name='dir1') # Add a new file. barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1', rr_name='bar') do_a_test(iso, check_rr_onefileonedirwithfile) iso.close() def test_new_rr_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.add_symlink('/SYM.;1', 'sym', 'foo') do_a_test(iso, check_rr_symlink) iso.close() def test_new_rr_symlink2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add new directory. iso.add_directory('/DIR1', rr_name='dir1') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1', rr_name='foo') iso.add_symlink('/SYM.;1', 'sym', 'dir1/foo') do_a_test(iso, check_rr_symlink2) iso.close() def test_new_rr_symlink_dot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_symlink('/SYM.;1', 'sym', '.') do_a_test(iso, check_rr_symlink_dot) iso.close() def test_new_rr_symlink_dotdot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_symlink('/SYM.;1', 'sym', '..') do_a_test(iso, check_rr_symlink_dotdot) iso.close() def test_new_rr_symlink_broken(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_symlink('/SYM.;1', 'sym', 'foo') do_a_test(iso, check_rr_symlink_broken) iso.close() def test_new_rr_verylongname(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_verylongname) iso.close() def test_new_rr_verylongname_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH, joliet_path='/'+'a'*64) do_a_test(iso, check_rr_verylongname_joliet) iso.close() def test_new_rr_manylongname(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH) bbstr = b'bb\n' iso.add_fp(BytesIO(bbstr), len(bbstr), '/BBBBBBBB.;1', rr_name='b'*RR_MAX_FILENAME_LENGTH) ccstr = b'cc\n' iso.add_fp(BytesIO(ccstr), len(ccstr), '/CCCCCCCC.;1', rr_name='c'*RR_MAX_FILENAME_LENGTH) ddstr = b'dd\n' iso.add_fp(BytesIO(ddstr), len(ddstr), '/DDDDDDDD.;1', rr_name='d'*RR_MAX_FILENAME_LENGTH) eestr = b'ee\n' iso.add_fp(BytesIO(eestr), len(eestr), '/EEEEEEEE.;1', rr_name='e'*RR_MAX_FILENAME_LENGTH) ffstr = b'ff\n' iso.add_fp(BytesIO(ffstr), len(ffstr), '/FFFFFFFF.;1', rr_name='f'*RR_MAX_FILENAME_LENGTH) ggstr = b'gg\n' iso.add_fp(BytesIO(ggstr), len(ggstr), '/GGGGGGGG.;1', rr_name='g'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_manylongname) iso.close() def test_new_rr_manylongname2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH) bbstr = b'bb\n' iso.add_fp(BytesIO(bbstr), len(bbstr), '/BBBBBBBB.;1', rr_name='b'*RR_MAX_FILENAME_LENGTH) ccstr = b'cc\n' iso.add_fp(BytesIO(ccstr), len(ccstr), '/CCCCCCCC.;1', rr_name='c'*RR_MAX_FILENAME_LENGTH) ddstr = b'dd\n' iso.add_fp(BytesIO(ddstr), len(ddstr), '/DDDDDDDD.;1', rr_name='d'*RR_MAX_FILENAME_LENGTH) eestr = b'ee\n' iso.add_fp(BytesIO(eestr), len(eestr), '/EEEEEEEE.;1', rr_name='e'*RR_MAX_FILENAME_LENGTH) ffstr = b'ff\n' iso.add_fp(BytesIO(ffstr), len(ffstr), '/FFFFFFFF.;1', rr_name='f'*RR_MAX_FILENAME_LENGTH) ggstr = b'gg\n' iso.add_fp(BytesIO(ggstr), len(ggstr), '/GGGGGGGG.;1', rr_name='g'*RR_MAX_FILENAME_LENGTH) hhstr = b'hh\n' iso.add_fp(BytesIO(hhstr), len(hhstr), '/HHHHHHHH.;1', rr_name='h'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_manylongname2) iso.close() def test_new_rr_verylongnameandsymlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH) iso.add_symlink('/BBBBBBBB.;1', 'b'*RR_MAX_FILENAME_LENGTH, 'a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_verylongnameandsymlink) iso.close() def test_new_alternating_subdir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() ddstr = b'dd\n' iso.add_fp(BytesIO(ddstr), len(ddstr), '/DD.;1') bbstr = b'bb\n' iso.add_fp(BytesIO(bbstr), len(bbstr), '/BB.;1') iso.add_directory('/CC') iso.add_directory('/AA') subdirfile1 = b'sub1\n' iso.add_fp(BytesIO(subdirfile1), len(subdirfile1), '/AA/SUB1.;1') subdirfile2 = b'sub2\n' iso.add_fp(BytesIO(subdirfile2), len(subdirfile2), '/CC/SUB2.;1') do_a_test(iso, check_alternating_subdir) iso.close() def test_new_joliet_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_joliet_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_joliet_onedir) iso.close() def test_new_joliet_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') do_a_test(iso, check_joliet_onefile) iso.close() def test_new_joliet_onefileonedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_joliet_onefileonedir) iso.close() def test_new_joliet_and_rr_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') do_a_test(iso, check_joliet_and_rr_nofiles) iso.close() def test_new_joliet_and_rr_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') do_a_test(iso, check_joliet_and_rr_onefile) iso.close() def test_new_joliet_and_rr_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') # Add a directory. iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_and_rr_onedir) iso.close() def test_new_rr_and_eltorito_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_nofiles) iso.close() def test_new_rr_and_eltorito_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_and_eltorito_onefile) iso.close() def test_new_rr_and_eltorito_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_new_rr_and_eltorito_onedir2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_rr_and_eltorito_onedir) iso.close() def test_new_joliet_and_eltorito_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_nofiles) iso.close() def test_new_joliet_and_eltorito_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') do_a_test(iso, check_joliet_and_eltorito_onefile) iso.close() def test_new_joliet_and_eltorito_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_joliet_and_eltorito_onedir) iso.close() def test_new_isohybrid(): # Create a new ISO iso = pycdlib.PyCdlib() iso.new() # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4) # Now add the syslinux data iso.add_isohybrid() do_a_test(iso, check_isohybrid) iso.close() def test_new_isohybrid_mac(): # Create a new ISO iso = pycdlib.PyCdlib() iso.new() # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') efibootstr = b'a' iso.add_fp(BytesIO(efibootstr), len(efibootstr), '/EFIBOOT.IMG;1') macbootstr = b'b' iso.add_fp(BytesIO(macbootstr), len(macbootstr), '/MACBOOT.IMG;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4, boot_info_table=True) iso.add_eltorito('/MACBOOT.IMG;1', efi=True) # Now add the syslinux data with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid(part_type=0, mac=True, efi=False) iso.close() def test_new_isohybrid_uefi(): # Create a new ISO iso = pycdlib.PyCdlib() iso.new() # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') efibootstr = b'a' iso.add_fp(BytesIO(efibootstr), len(efibootstr), '/EFIBOOT.IMG;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4, boot_info_table=True) iso.add_eltorito('/EFIBOOT.IMG;1', efi=True) # Now add the syslinux data iso.add_isohybrid(efi=True) do_a_test(iso, check_isohybrid_uefi) iso.close() def test_new_isohybrid_mac_uefi(): # Create a new ISO iso = pycdlib.PyCdlib() iso.new() # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') efibootstr = b'a' iso.add_fp(BytesIO(efibootstr), len(efibootstr), '/EFIBOOT.IMG;1') macbootstr = b'b' iso.add_fp(BytesIO(macbootstr), len(macbootstr), '/MACBOOT.IMG;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4, boot_info_table=True) iso.add_eltorito('/MACBOOT.IMG;1', efi=True) iso.add_eltorito('/EFIBOOT.IMG;1', efi=True) # Now add the syslinux data iso.add_isohybrid(mac=True) do_a_test(iso, check_isohybrid_mac_uefi) iso.close() def test_new_joliet_rr_and_eltorito_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_joliet_rr_and_eltorito_nofiles) iso.close() def test_new_joliet_rr_and_eltorito_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') do_a_test(iso, check_joliet_rr_and_eltorito_onefile) iso.close() def test_new_joliet_rr_and_eltorito_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_rr_and_eltorito_onedir) iso.close() def test_new_rr_rmfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.rm_file('/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_nofiles) iso.close() def test_new_rr_rmdir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.rm_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_nofiles) iso.close() def test_new_joliet_rmfile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.rm_file('/BOOT.;1', joliet_path='/boot') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_joliet_rmdir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.rm_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rr_deep(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') do_a_test(iso, check_rr_deep_dir) iso.close() def test_new_xa_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(xa=True) do_a_test(iso, check_xa_nofiles) iso.close() def test_new_xa_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(xa=True) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_xa_onefile) iso.close() def test_new_xa_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(xa=True) iso.add_directory('/DIR1') do_a_test(iso, check_xa_onedir) iso.close() def test_new_sevendeepdirs(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') do_a_test(iso, check_sevendeepdirs) iso.close() def test_new_xa_joliet_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, xa=True) do_a_test(iso, check_xa_joliet_nofiles) iso.close() def test_new_xa_joliet_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, xa=True) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') do_a_test(iso, check_xa_joliet_onefile) iso.close() def test_new_xa_joliet_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, xa=True) iso.add_directory('/DIR1', joliet_path='/dir1') do_a_test(iso, check_xa_joliet_onedir) iso.close() def test_new_isolevel4_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) do_a_test(iso, check_isolevel4_nofiles) iso.close() def test_new_isolevel4_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo') do_a_test(iso, check_isolevel4_onefile) iso.close() def test_new_isolevel4_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) iso.add_directory('/dir1') do_a_test(iso, check_isolevel4_onedir) iso.close() def test_new_isolevel4_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') do_a_test(iso, check_isolevel4_eltorito) iso.close() def test_new_everything(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4, rock_ridge='1.09', joliet=3, xa=True) iso.add_directory('/dir1', rr_name='dir1', joliet_path='/dir1') iso.add_directory('/dir1/dir2', rr_name='dir2', joliet_path='/dir1/dir2') iso.add_directory('/dir1/dir2/dir3', rr_name='dir3', joliet_path='/dir1/dir2/dir3') iso.add_directory('/dir1/dir2/dir3/dir4', rr_name='dir4', joliet_path='/dir1/dir2/dir3/dir4') iso.add_directory('/dir1/dir2/dir3/dir4/dir5', rr_name='dir5', joliet_path='/dir1/dir2/dir3/dir4/dir5') iso.add_directory('/dir1/dir2/dir3/dir4/dir5/dir6', rr_name='dir6', joliet_path = '/dir1/dir2/dir3/dir4/dir5/dir6') iso.add_directory('/dir1/dir2/dir3/dir4/dir5/dir6/dir7', rr_name='dir7', joliet_path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7') iso.add_directory('/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8', rr_name='dir8', joliet_path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/boot', '/boot.cat', boot_info_table=True) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', rr_name='foo', joliet_path='/foo') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/bar', rr_name='bar', joliet_path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/bar') iso.add_symlink('/sym', 'sym', 'foo', joliet_path='/sym') iso.add_hard_link(iso_new_path='/dir1/foo', iso_old_path='/foo', rr_name='foo') iso.add_hard_link(iso_old_path='/foo', joliet_new_path='/dir1/foo') do_a_test(iso, check_everything) iso.close() def test_new_rr_xa_nofiles(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', xa=True) do_a_test(iso, check_rr_xa_nofiles) iso.close() def test_new_rr_xa_onefile(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', xa=True) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_xa_onefile) iso.close() def test_new_rr_xa_onedir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', xa=True) iso.add_directory('/DIR1', rr_name='dir1') do_a_test(iso, check_rr_xa_onedir) iso.close() def test_new_rr_joliet_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/foo') iso.add_symlink('/SYM.;1', 'sym', 'foo', joliet_path='/sym') do_a_test(iso, check_rr_joliet_symlink) iso.close() def test_new_rr_joliet_deep(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2', joliet_path='/dir1/dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3', joliet_path='/dir1/dir2/dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4', joliet_path='/dir1/dir2/dir3/dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5', joliet_path='/dir1/dir2/dir3/dir4/dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6', joliet_path = '/dir1/dir2/dir3/dir4/dir5/dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7', joliet_path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8', joliet_path='/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8') do_a_test(iso, check_rr_joliet_deep) iso.close() def test_new_duplicate_child(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/DIR1') assert(str(excinfo.value) == 'Failed adding duplicate name to parent') def test_new_eltorito_multi_boot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') do_a_test(iso, check_eltorito_multi_boot) iso.close() def test_new_eltorito_boot_table(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat', boot_info_table=True) do_a_test(iso, check_eltorito_boot_info_table) iso.close() def test_new_eltorito_boot_table_large(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot'*20 iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat', boot_info_table=True) do_a_test(iso, check_eltorito_boot_info_table_large) iso.close() def test_new_hard_link(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') # Add a directory. iso.add_directory('/DIR1') iso.add_hard_link(iso_new_path='/DIR1/FOO.;1', iso_old_path='/FOO.;1') do_a_test(iso, check_hard_link) iso.close() def test_new_invalid_interchange(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(interchange_level=5) assert(str(excinfo.value) == 'Invalid interchange level (must be between 1 and 4)') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(interchange_level=0) assert(str(excinfo.value) == 'Invalid interchange level (must be between 1 and 4)') def test_new_open_twice(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new() assert(str(excinfo.value) == 'This object already has an ISO; either close it or create a new object') iso.close() def test_new_add_fp_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_add_fp_no_rr_name(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') assert(str(excinfo.value) == 'Rock Ridge name must be supplied for a Rock Ridge new path') iso.close() def test_new_add_fp_rr_name(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') assert(str(excinfo.value) == 'A rock ridge name can only be specified for a rock-ridge ISO') iso.close() def test_new_add_fp_no_joliet_name(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_onefile_joliet_no_file) iso.close() def test_new_add_fp_joliet_name(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') assert(str(excinfo.value) == 'A Joliet path can only be specified for a Joliet ISO') iso.close() def test_new_add_fp_joliet_name_too_long(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/'+'a'*65) assert(str(excinfo.value) == 'Joliet names can be a maximum of 64 characters') iso.close() def test_new_add_dir_joliet_name_too_long(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/DIR1', joliet_path='/'+'a'*65) assert(str(excinfo.value) == 'Joliet names can be a maximum of 64 characters') iso.close() def test_new_close_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.close() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rm_isohybrid_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_isohybrid() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_add_isohybrid_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_add_isohybrid_bad_boot_load_size(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() isolinuxstr = b'\x00'*0x801 iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid() assert(str(excinfo.value) == 'El Torito Boot Catalog sector count must be 4 (was actually 0x8)') iso.close() def test_new_add_isohybrid_bad_file_signature(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a new file. isolinuxstr = b'\x00'*0x44 iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_isohybrid() assert(str(excinfo.value) == 'Invalid signature on boot file for iso hybrid') iso.close() def test_new_add_eltorito_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4) assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_add_file(tmpdir): # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.new() # Add a new file. testout = tmpdir.join('writetest.iso') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/FOO.;1') do_a_test(iso, check_onefile) iso.close() def test_new_add_file_twoleveldeep(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add new directory. iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/DIR1/SUBDIR1/FOO.;1') do_a_test(iso, check_twoleveldeepfile) iso.close() def test_new_rr_symlink_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/SYM.;1', 'sym', 'foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rr_symlink_no_rr(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/SYM.;1', 'sym', 'foo') assert(str(excinfo.value) == 'Can only add a symlink to a Rock Ridge or UDF ISO') iso.close() def test_new_rr_symlink_absolute(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_symlink('/SYM.;1', 'sym', '/usr/local/foo') do_a_test(iso, check_rr_absolute_symlink) iso.close() def test_new_add_file_no_rr_name(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_file(str(testout), '/FOO.;1') assert(str(excinfo.value) == 'Rock Ridge name must be supplied for a Rock Ridge new path') def test_new_add_file_not_initialized(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_file(str(testout), '/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_hard_link_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_new_path='/DIR1/FOO.;1', iso_old_path='/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_write_fp_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.write_fp(out) assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_same_dirname_different_parent(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) # Add new directory. iso.add_directory('/DIR1', rr_name='dir1', joliet_path='/dir1') iso.add_directory('/DIR1/BOOT', rr_name='boot', joliet_path='/dir1/boot') iso.add_directory('/DIR2', rr_name='dir2', joliet_path='/dir2') iso.add_directory('/DIR2/BOOT', rr_name='boot', joliet_path='/dir2/boot') do_a_test(iso, check_same_dirname_different_parent) iso.close() def test_new_joliet_isolevel4(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4, joliet=3) # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', joliet_path='/foo') # Add new directory. iso.add_directory('/dir1', joliet_path='/dir1') do_a_test(iso, check_joliet_isolevel4) iso.close() def test_new_eltorito_hide(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link(iso_path='/BOOT.CAT;1') do_a_test(iso, check_eltorito_nofiles_hide) iso.close() def test_new_eltorito_nofiles_hide_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link(joliet_path='/boot.cat') iso.rm_hard_link(iso_path='/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_nofiles_hide) iso.close() def test_new_eltorito_nofiles_hide_joliet_only(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') # After add_fp: # boot - 1 link (1 Joliet) iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') # After add_eltorito: # boot - 1 link (1 Joliet, eltorito initial entry is "special") # boot.cat - 1 link (1 Joliet) iso.rm_hard_link(joliet_path='/boot.cat') # After rm_hard_link: # boot - 1 link (1 Joliet, eltorito initial entry is "special") # boot.cat - 0 links (ISO only) do_a_test(iso, check_joliet_and_eltorito_nofiles_hide_only) iso.close() def test_new_eltorito_nofiles_hide_iso_only(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link(iso_path='/BOOT.CAT;1') do_a_test(iso, check_joliet_and_eltorito_nofiles_hide_iso_only) iso.close() def test_new_hard_link_reshuffle(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_new_path='/BAR.;1', iso_old_path='/FOO.;1') do_a_test(iso, check_hard_link_reshuffle) iso.close() def test_new_invalid_sys_ident(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(sys_ident='a'*33) assert(str(excinfo.value) == 'The system identifer has a maximum length of 32') def test_new_invalid_vol_ident(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(vol_ident='a'*33) assert(str(excinfo.value) == 'The volume identifier has a maximum length of 32') def test_new_seqnum_greater_than_set_size(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(seqnum=99) assert(str(excinfo.value) == 'Sequence number must be less than or equal to set size') def test_new_invalid_vol_set_ident(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(vol_set_ident='a'*129) assert(str(excinfo.value) == 'The maximum length for the volume set identifier is 128') def test_new_invalid_app_use(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(app_use='a'*513) assert(str(excinfo.value) == 'The maximum length for the application use is 512') def test_new_invalid_app_use_xa(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(xa=True, app_use='a'*142) assert(str(excinfo.value) == 'Cannot have XA and an app_use of > 140 bytes') def test_new_invalid_filename_character(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FO#.;1') assert(str(excinfo.value) == 'ISO9660 filenames must consist of characters A-Z, 0-9, and _') def test_new_invalid_filename_semicolons(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FO0;1.;1') assert(str(excinfo.value) == 'ISO9660 filenames must contain exactly one semicolon') def test_new_invalid_filename_version(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;32768') assert(str(excinfo.value) == 'ISO9660 filenames must have a version between 1 and 32767') def test_new_invalid_filename_dotonly(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/.') assert(str(excinfo.value) == 'ISO9660 filenames must have a non-empty name or extension') def test_new_invalid_filename_toolong(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/THISISAVERYLONGNAME.;1') assert(str(excinfo.value) == 'ISO9660 filenames at interchange level 1 cannot have more than 8 characters or 3 characters in the extension') def test_new_invalid_extension_toolong(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/NAME.LONGEXT;1') assert(str(excinfo.value) == 'ISO9660 filenames at interchange level 1 cannot have more than 8 characters or 3 characters in the extension') def test_new_invalid_dirname(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/') assert(str(excinfo.value) == 'ISO9660 directory names must be at least 1 character long') def test_new_invalid_dirname_toolong(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/THISISAVERYLONGDIRECTORY') assert(str(excinfo.value) == 'ISO9660 directory names at interchange level 1 cannot exceed 8 characters') def test_new_invalid_dirname_toolong4(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=3) # Add a directory. with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory('/'+'a'*208) assert(str(excinfo.value) == 'ISO9660 directory names at interchange level 3 cannot exceed 207 characters') def test_new_rr_invalid_name(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_file(str(testout), '/FOO.;1', rr_name='foo/bar') assert(str(excinfo.value) == 'A rock ridge name must be relative') def test_new_hard_link_invalid_keyword(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(foo='bar') assert(str(excinfo.value) == 'Exactly one old path must be specified') def test_new_hard_link_no_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(boot_catalog_old=True) assert(str(excinfo.value) == 'Attempting to make link to non-existent El Torito boot catalog') def test_new_hard_link_no_old_kw(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_new_path='/FOO.;1') assert(str(excinfo.value) == 'Exactly one old path must be specified') def test_new_hard_link_no_new_kw(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_old_path='/FOO.;1') assert(str(excinfo.value) == 'Exactly one new path must be specified') def test_new_hard_link_new_missing_rr(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: outfp.write(b'foo\n') iso.add_file(str(testout), '/FOO.;1', rr_name='foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') assert(str(excinfo.value) == 'Rock Ridge name must be supplied for a Rock Ridge new path') def test_new_hard_link_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link('/BOOT.CAT;1') iso.add_hard_link(boot_catalog_old=True, iso_new_path='/BOOT.CAT;1') do_a_test(iso, check_eltorito_nofiles) iso.close() def test_new_rm_hard_link_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rm_hard_link_no_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link() assert(str(excinfo.value) == 'Must provide exactly one of iso_path, joliet_path, or udf_path') def test_new_rm_hard_link_both_paths(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(iso_path='/BOOT.;1', joliet_path='/boot') assert(str(excinfo.value) == 'Must provide exactly one of iso_path, joliet_path, or udf_path') def test_new_rm_hard_link_bad_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(iso_path='BOOT.;1') assert(str(excinfo.value) == 'Must be a path starting with /') def test_new_rm_hard_link_dir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a directory. iso.add_directory('/DIR1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(iso_path='/DIR1') assert(str(excinfo.value) == 'Cannot remove a directory with rm_hard_link (try rm_directory instead)') def test_new_rm_hard_link_no_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(joliet_path='/boot') assert(str(excinfo.value) == 'Cannot remove Joliet link from non-Joliet ISO') def test_new_rm_hard_link_remove_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.rm_hard_link(iso_path='/BOOT.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_rm_hard_link_joliet_remove_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.rm_hard_link(iso_path='/BOOT.;1') iso.rm_hard_link(joliet_path='/boot') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_hard_link_rm_second(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAZ.;1') iso.rm_hard_link(iso_path='/BAR.;1') iso.rm_hard_link(iso_path='/BAZ.;1') do_a_test(iso, check_onefile) iso.close() def test_new_rm_hard_link_rm_joliet_first(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_hard_link(joliet_path='/foo') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_hard_link_rm_joliet_and_links(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAZ.;1') iso.rm_hard_link(joliet_path='/foo') iso.rm_hard_link(iso_path='/BAR.;1') iso.rm_hard_link(iso_path='/BAZ.;1') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_hard_link_isolevel4(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_isolevel4_nofiles) iso.close() def test_add_hard_link_joliet_to_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_hard_link(joliet_old_path='/foo', joliet_new_path='/bar') iso.close() def test_new_rr_deeper(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') iso.add_directory('/A1', rr_name='a1') iso.add_directory('/A1/A2', rr_name='a2') iso.add_directory('/A1/A2/A3', rr_name='a3') iso.add_directory('/A1/A2/A3/A4', rr_name='a4') iso.add_directory('/A1/A2/A3/A4/A5', rr_name='a5') iso.add_directory('/A1/A2/A3/A4/A5/A6', rr_name='a6') iso.add_directory('/A1/A2/A3/A4/A5/A6/A7', rr_name='a7') iso.add_directory('/A1/A2/A3/A4/A5/A6/A7/A8', rr_name='a8') do_a_test(iso, check_rr_deeper_dir) iso.close() def test_new_eltorito_boot_table_large_odd(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boo'*27 iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat', boot_info_table=True) do_a_test(iso, check_eltorito_boot_info_table_large_odd) iso.close() def test_new_eltorito_boot_table_invalid_out(tmpdir): testboot = tmpdir.join('boot') testout = tmpdir.join('boot.out') iso = pycdlib.PyCdlib() iso.new(interchange_level=4) with open(str(testboot), 'wb') as outfp: outfp.write(b'abcdefghijklmnopqrstuvwxyz'*10) iso.add_file(str(testboot), '/boot') iso.add_eltorito('/boot', '/boot.cat', boot_info_table=True) iso.force_consistency() iso.get_file_from_iso(str(testout), iso_path='/boot') with open(str(testout), 'rb') as infp: data = infp.read() assert(data == b'abcdefgh\x10\x00\x00\x00\x1b\x00\x00\x00\x04\x01\x00\x00\xf5:\x045\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz') iso.close() def test_new_joliet_large_directory(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) for i in range(1, 50): iso.add_directory('/DIR%d' % i, joliet_path='/dir%d' % i) do_a_test(iso, check_joliet_large_directory) iso.close() def test_new_zero_byte_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=1) foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1') do_a_test(iso, check_zero_byte_file) iso.close() def test_new_eltorito_hide_boot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link(iso_path='/BOOT.;1') do_a_test(iso, check_eltorito_hide_boot) iso.close() def test_new_full_path_from_dirrecord(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/DIR1/BOOT.;1') full_path = None for child in iso.list_children(iso_path='/DIR1'): if child.file_identifier() == b'BOOT.;1': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/DIR1/BOOT.;1') break assert(full_path is not None) iso.close() def test_new_full_path_from_dirrecord_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.full_path_from_dirrecord(None) assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rock_ridge_one_point_twelve(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.12') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.close() def test_new_duplicate_pvd(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.duplicate_pvd() do_a_test(iso, check_duplicate_pvd) iso.close() def test_new_duplicate_pvd_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.duplicate_pvd() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_eltorito_multi_multi_boot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') boot3str = b'boot3\n' iso.add_fp(BytesIO(boot3str), len(boot3str), '/boot3') iso.add_eltorito('/boot3', '/boot.cat') do_a_test(iso, check_eltorito_multi_multi_boot) iso.close() def test_new_duplicate_pvd_not_same(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.duplicate_pvd() out = BytesIO() iso.write_fp(out) iso.close() # Back up to the application use portion of the duplicate PVD to make # it different than the primary one. The duplicate PVD lives at extent # 17, so go to extent 18, backup 653 (to skip the zeros), then backup # one more to get back into the application use area. out.seek(18*2048 - 653 - 1) out.write(b'\xff') iso2 = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso2.open_fp(out) assert(str(excinfo.value) == 'Multiple occurrences of PVD did not agree!') def infinitenamechecks(iso, filesize): dr = iso.pvd.root_dir_record.children[2] assert(len(dr.rock_ridge.dr_entries.nm_records) == 1) assert(dr.rock_ridge.dr_entries.nm_records[0].posix_name == b'a'*172) assert(dr.rock_ridge.dr_entries.nm_records[0].posix_name_flags == 1) assert(dr.rock_ridge.dr_entries.ce_record is not None) assert(len(dr.rock_ridge.ce_entries.nm_records) == 2) assert(dr.rock_ridge.ce_entries.nm_records[0].posix_name == b'a'*250) assert(dr.rock_ridge.ce_entries.nm_records[0].posix_name_flags == 1) assert(dr.rock_ridge.ce_entries.nm_records[1].posix_name == b'a'*78) assert(dr.rock_ridge.ce_entries.nm_records[1].posix_name_flags == 0) def test_new_rr_exceedinglylongname(): # This is a test to test out names > 255 in pycdlib. Note that the Linux # kernel doesn't support this (nor does genisoimage), so this is strictly # an internal-only test to make sure we get things correct. # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*500) do_a_test(iso, infinitenamechecks) iso.close() def symlink_path_checks(iso, size): assert(iso.pvd.root_dir_record.children[3].rock_ridge.symlink_path() == b'aaaaaaaa') def test_new_rr_symlink_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='aaaaaaaa') iso.add_symlink('/BBBBBBBB.;1', 'bbbbbbbb', 'aaaaaaaa') do_a_test(iso, symlink_path_checks) iso.close() def test_new_rr_symlink_path_not_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='aaaaaaaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.pvd.root_dir_record.children[2].rock_ridge.symlink_path() assert(str(excinfo.value) == 'Entry is not a symlink!') def verylongsymlinkchecks(iso, size): assert(iso.pvd.root_dir_record.children[3].rock_ridge.symlink_path() == b'a'*RR_MAX_FILENAME_LENGTH) def test_new_rr_verylongnameandsymlink_symlink_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='a'*RR_MAX_FILENAME_LENGTH) iso.add_symlink('/BBBBBBBB.;1', 'b'*RR_MAX_FILENAME_LENGTH, 'a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, verylongsymlinkchecks) iso.close() def verylong_symlink_path_checks(iso, size): assert(iso.pvd.root_dir_record.children[3].rock_ridge.symlink_path() == b'a'*RR_MAX_FILENAME_LENGTH) def test_new_rr_verylongsymlink_symlink_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='aaaaaaaa') iso.add_symlink('/BBBBBBBB.;1', 'bbbbbbbb', 'a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, verylong_symlink_path_checks) iso.close() def extremelylong_symlink_path_checks(iso, size): assert(iso.pvd.root_dir_record.children[3].rock_ridge.symlink_path() == b'a'*500) def test_new_rr_extremelylongsymlink_symlink_path(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', rr_name='aaaaaaaa') iso.add_symlink('/BBBBBBBB.;1', 'bbbbbbbb', 'a'*500) do_a_test(iso, extremelylong_symlink_path_checks) iso.close() def test_new_rr_invalid_rr_version(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(rock_ridge='1.90') assert(str(excinfo.value) == 'Rock Ridge value must be None (no Rock Ridge), 1.09, 1.10, or 1.12') def test_new_rr_onefile_onetwelve(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.12') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') do_a_test(iso, check_rr_onefile_onetwelve) iso.close() def test_new_set_hidden_file(): iso = pycdlib.PyCdlib() iso.new() aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1') iso.set_hidden('/AAAAAAAA.;1') do_a_test(iso, check_hidden_file) iso.close() def test_new_set_hidden_dir(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') iso.set_hidden('/DIR1') do_a_test(iso, check_hidden_dir) iso.close() def test_new_set_hidden_joliet_file(): iso = pycdlib.PyCdlib() iso.new(joliet=3) aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', joliet_path='/aaaaaaaa') iso.set_hidden(joliet_path='/aaaaaaaa') do_a_test(iso, check_hidden_joliet_file) iso.close() def test_new_set_hidden_joliet_dir(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.set_hidden(joliet_path='/dir1') do_a_test(iso, check_hidden_joliet_dir) iso.close() def test_new_set_hidden_rr_onefileonedir(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.set_hidden(rr_path='/foo') iso.add_directory('/DIR1', rr_name='dir1') iso.set_hidden(rr_path='/dir1') do_a_test(iso, check_rr_onefileonedir_hidden) iso.close() def test_new_clear_hidden_joliet_file(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.clear_hidden(joliet_path='/foo') do_a_test(iso, check_joliet_onefile) iso.close() def test_new_clear_hidden_joliet_dir(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.clear_hidden(joliet_path='/dir1') do_a_test(iso, check_joliet_onedir) iso.close() def test_new_clear_hidden_rr_onefileonedir(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.clear_hidden(rr_path='/foo') iso.add_directory('/DIR1', rr_name='dir1') iso.clear_hidden(rr_path='/dir1') do_a_test(iso, check_rr_onefileonedir) iso.close() def test_new_set_hidden_not_initialized(): iso = pycdlib.PyCdlib() iso.new() aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1') iso.close() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_hidden('/AAAAAAAA.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_clear_hidden_file(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.clear_hidden('/FOO.;1') do_a_test(iso, check_onefile) iso.close() def test_new_clear_hidden_dir(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') iso.clear_hidden('/DIR1') do_a_test(iso, check_onedir) iso.close() def test_new_clear_hidden_not_initialized(): iso = pycdlib.PyCdlib() iso.new() aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1') iso.close() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.clear_hidden('/AAAAAAAA.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_duplicate_rrmoved_name(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/A', rr_name='A') iso.add_directory('/A/B', rr_name='B') iso.add_directory('/A/B/C', rr_name='C') iso.add_directory('/A/B/C/D', rr_name='D') iso.add_directory('/A/B/C/D/E', rr_name='E') iso.add_directory('/A/B/C/D/E/F', rr_name='F') iso.add_directory('/A/B/C/D/E/F/G', rr_name='G') iso.add_directory('/A/B/C/D/E/F/G/1', rr_name='1') iso.add_directory('/A/B/C/D/E/F/H', rr_name='H') iso.add_directory('/A/B/C/D/E/F/H/1', rr_name='1') firststr = b'first\n' iso.add_fp(BytesIO(firststr), len(firststr), '/A/B/C/D/E/F/G/1/FIRST.;1', rr_name='first') secondstr = b'second\n' iso.add_fp(BytesIO(secondstr), len(secondstr), '/A/B/C/D/E/F/H/1/SECOND.;1', rr_name='second') do_a_test(iso, check_rr_two_dirs_same_level) iso.close() def test_new_eltorito_hd_emul(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') do_a_test(iso, check_eltorito_hd_emul) iso.close() def test_new_eltorito_hd_emul_too_short(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') assert(str(excinfo.value) == 'Could not read entire HD MBR, must be at least 512 bytes') iso.close() def test_new_eltorito_hd_emul_bad_keybyte1(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x56' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') assert(str(excinfo.value) == 'Invalid magic on HD MBR') iso.close() def test_new_eltorito_hd_emul_bad_keybyte2(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xab' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') assert(str(excinfo.value) == 'Invalid magic on HD MBR') iso.close() def test_new_eltorito_hd_emul_multiple_part(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') assert(str(excinfo.value) == 'Boot image has multiple partitions') iso.close() def test_new_eltorito_hd_emul_no_part(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') assert(str(excinfo.value) == 'Boot image has no partitions') iso.close() def test_new_eltorito_hd_emul_bad_sec(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x00\x00\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') do_a_test(iso, check_eltorito_hd_emul_bad_sec) iso.close() def test_new_eltorito_hd_emul_invalid_geometry(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul') do_a_test(iso, check_eltorito_hd_emul_invalid_geometry) iso.close() def test_new_eltorito_hd_emul_not_bootable(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='hdemul', bootable=False) do_a_test(iso, check_eltorito_hd_emul_not_bootable) iso.close() def test_new_eltorito_floppy12(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*(2400*512) iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='floppy', bootable=True) do_a_test(iso, check_eltorito_floppy12) iso.close() def test_new_eltorito_floppy144(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*(2880*512) iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='floppy', bootable=True) do_a_test(iso, check_eltorito_floppy144) iso.close() def test_new_eltorito_floppy288(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*(5760*512) iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='floppy', bootable=True) do_a_test(iso, check_eltorito_floppy288) iso.close() def test_new_eltorito_bad_floppy(): iso = pycdlib.PyCdlib() iso.new(interchange_level=1) bootstr = b'\x00'*(576*512) iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', media_name='floppy', bootable=True) assert(str(excinfo.value) == 'Invalid sector count for floppy media type; must be 2400, 2880, or 5760') iso.close() def test_new_eltorito_multi_hidden(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') iso.rm_hard_link(iso_path='/boot2') do_a_test(iso, check_eltorito_multi_hidden) iso.close() def test_new_eltorito_rr_verylongname(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot') iso.add_eltorito('/BOOT.;1', '/AAAAAAAA.;1', rr_bootcatname='a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_eltorito_rr_verylongname) iso.close() def test_new_isohybrid_file_before(): # Create a new ISO iso = pycdlib.PyCdlib() iso.new() # Add Eltorito isolinuxstr = b'\x00'*0x40 + b'\xfb\xc0\x78\x70' iso.add_fp(BytesIO(isolinuxstr), len(isolinuxstr), '/ISOLINUX.BIN;1') iso.add_eltorito('/ISOLINUX.BIN;1', '/BOOT.CAT;1', boot_load_size=4) # Now add the syslinux data iso.add_isohybrid() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') do_a_test(iso, check_isohybrid_file_before) iso.close() def test_new_force_consistency_not_initialized(): # Create a new ISO iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.force_consistency() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_eltorito_rr_joliet_verylongname(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', rr_name='boot', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/AAAAAAAA.;1', rr_bootcatname='a'*RR_MAX_FILENAME_LENGTH, joliet_bootcatfile='/'+'a'*64) do_a_test(iso, check_eltorito_rr_joliet_verylongname) iso.close() def test_new_joliet_dirs_overflow_ptr_extent(): numdirs = 216 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) for i in range(1, 1+numdirs): iso.add_directory('/DIR%d' % i, joliet_path='/dir%d' % i) do_a_test(iso, check_joliet_dirs_overflow_ptr_extent) iso.close() def test_new_joliet_dirs_just_short_ptr_extent(): numdirs = 215 # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) for i in range(1, 1+numdirs): iso.add_directory('/DIR%d' % i, joliet_path='/dir%d' % i) do_a_test(iso, check_joliet_dirs_just_short_ptr_extent) iso.close() def test_new_joliet_rm_large_directory(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) for i in range(1, 50): iso.add_directory('/DIR%d' % i, joliet_path='/dir%d' % i) for i in range(1, 50): iso.rm_directory('/DIR%d' % i, joliet_path='/dir%d' % i) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_overflow_root_dir_record(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') for letter in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o'): thisstr = b'\n' iso.add_fp(BytesIO(thisstr), len(thisstr), '/'+letter.upper()*7+'.;1', rr_name=letter*20, joliet_path='/'+letter*20) do_a_test(iso, check_overflow_root_dir_record) iso.close() def test_new_overflow_correct_extents(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') thisstr = b'\n' for letter in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'): iso.add_fp(BytesIO(thisstr), len(thisstr), '/'+letter.upper()*8+'.;1', rr_name=letter*136, joliet_path='/'+letter*64) iso.add_fp(BytesIO(thisstr), len(thisstr), '/OOOOOOOO.;1', rr_name='o'*57, joliet_path='/'+'o'*57) iso.add_fp(BytesIO(thisstr), len(thisstr), '/P.;1', rr_name='p', joliet_path='/p') do_a_test(iso, check_overflow_correct_extents) iso.close() def test_new_overflow_correct_extents2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') thisstr = b'\n' iso.add_fp(BytesIO(thisstr), len(thisstr), '/P.;1', rr_name='p', joliet_path='/p') iso.add_fp(BytesIO(thisstr), len(thisstr), '/OOOOOOOO.;1', rr_name='o'*57, joliet_path='/'+'o'*57) for letter in ('n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'): iso.add_fp(BytesIO(thisstr), len(thisstr), '/'+letter.upper()*8+'.;1', rr_name=letter*136, joliet_path='/'+letter*64) do_a_test(iso, check_overflow_correct_extents) iso.close() def test_new_duplicate_deep_dir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') iso.add_directory('/BOOKS', rr_name='books', joliet_path='/books') iso.add_directory('/BOOKS/LKHG', rr_name='lkhg', joliet_path='/books/lkhg') iso.add_directory('/BOOKS/LKHG/HYPERNEW', rr_name='HyperNews', joliet_path='/books/lkhg/HyperNews') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET', rr_name='get', joliet_path='/books/lkhg/HyperNews/get') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/FS', rr_name='fs', joliet_path='/books/lkhg/HyperNews/get/fs') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/FS/FS', rr_name='fs', joliet_path='/books/lkhg/HyperNews/get/fs/fs') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/FS/FS/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/fs/fs/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG', rr_name='khg', joliet_path='/books/lkhg/HyperNews/get/khg') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/117', rr_name='117', joliet_path='/books/lkhg/HyperNews/get/khg/117') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/117/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/117/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/117/1/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/117/1/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/117/1/1/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/117/1/1/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/117/1/1/1/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/117/1/1/1/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/35', rr_name='35', joliet_path='/books/lkhg/HyperNews/get/khg/35') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/35/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/35/1') iso.add_directory('/BOOKS/LKHG/HYPERNEW/GET/KHG/35/1/1', rr_name='1', joliet_path='/books/lkhg/HyperNews/get/khg/35/1/1') do_a_test(iso, check_duplicate_deep_dir) iso.close() def test_new_always_consistent(): iso = pycdlib.PyCdlib(always_consistent=True) iso.new(joliet=3) # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_hard_link(joliet_path='/foo') iso.add_directory('/DIR1', joliet_path='/dir1') iso.rm_hard_link(iso_path='/FOO.;1') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_file('/FOO.;1', joliet_path='/foo') iso.rm_directory('/DIR1', joliet_path='/dir1') iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') iso.rm_eltorito() do_a_test(iso, check_joliet_onefile) iso.close() def test_new_remove_eighth_dir(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') iso.rm_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/DIR8', rr_name='dir8') do_a_test(iso, check_sevendeepdirs) iso.close() def test_new_joliet_level_1(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=1) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_joliet_level_2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=2) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_joliet_level_3(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_joliet_invalid_level(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(joliet=4) assert(str(excinfo.value) == 'Invalid Joliet level; must be 1, 2, or 3') def test_new_duplicate_pvd_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.duplicate_pvd() do_a_test(iso, check_duplicate_pvd) iso.close() def test_new_rr_symlink_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.add_symlink('/SYM.;1', 'sym', 'foo') do_a_test(iso, check_rr_symlink) iso.close() def test_new_eltorito_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_eltorito_nofiles) iso.close() def test_new_joliet_false(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=False) do_a_test(iso, check_nofiles) iso.close() def test_new_joliet_true(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=True) do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_eltorito_multi_boot_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') do_a_test(iso, check_eltorito_multi_boot) iso.close() def test_new_rm_joliet_hard_link(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_hard_link(joliet_path='/foo') do_a_test(iso, check_onefile_joliet_no_file) iso.close() def test_new_add_joliet_directory_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_joliet_directory('/foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_add_joliet_directory(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1') iso.add_joliet_directory('/dir1') do_a_test(iso, check_joliet_onedir) iso.close() def test_new_add_joliet_directory_isolevel4(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4, joliet=3) # Add new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', joliet_path='/foo') # Add new directory. iso.add_directory('/dir1') iso.add_joliet_directory('/dir1') do_a_test(iso, check_joliet_isolevel4) iso.close() def test_new_add_joliet_directory_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new(joliet=3) iso.add_directory('/DIR1') iso.add_joliet_directory('/dir1') do_a_test(iso, check_joliet_onedir) iso.close() def test_new_rm_joliet_directory(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.rm_directory('/DIR1') iso.rm_joliet_directory('/dir1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_joliet_directory_not_initialized(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_joliet_directory('/dir1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rm_joliet_directory_always_consistent(): # Create a new ISO. iso = pycdlib.PyCdlib(always_consistent=True) iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.rm_directory('/DIR1') iso.rm_joliet_directory('/dir1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_joliet_directory_iso_level4(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4, joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.rm_directory('/DIR1') iso.rm_joliet_directory('/dir1') do_a_test(iso, check_joliet_isolevel4_nofiles) iso.close() def test_new_deep_rr_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a large directory structure. iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/DIR2', rr_name='dir2') iso.add_directory('/DIR1/DIR2/DIR3', rr_name='dir3') iso.add_directory('/DIR1/DIR2/DIR3/DIR4', rr_name='dir4') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5', rr_name='dir5') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6', rr_name='dir6') iso.add_directory('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7', rr_name='dir7') iso.add_symlink('/DIR1/DIR2/DIR3/DIR4/DIR5/DIR6/DIR7/SYM.;1', 'sym', '/usr/share/foo') do_a_test(iso, check_deep_rr_symlink) iso.close() def test_new_rr_deep_weird_layout(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/ASTROID', rr_name='astroid') iso.add_directory('/ASTROID/ASTROID', rr_name='astroid') iso.add_directory('/ASTROID/ASTROID/TESTS', rr_name='tests') iso.add_directory('/ASTROID/ASTROID/TESTS/TESTDATA', rr_name='testdata') iso.add_directory('/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3', rr_name='python3') iso.add_directory('/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA', rr_name='data') iso.add_directory('/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP', rr_name='absimp') iso.add_directory('/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP/SIDEPACK', rr_name='sidepackage') strstr = b'from __future__ import absolute_import, print_functino\nimport string\nprint(string)\n' iso.add_fp(BytesIO(strstr), len(strstr), '/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP/STRING.PY;1', rr_name='string.py') initstr = b'"""a side package with nothing in it\n"""\n' iso.add_fp(BytesIO(initstr), len(initstr), '/ASTROID/ASTROID/TESTS/TESTDATA/PYTHON3/DATA/ABSIMP/SIDEPACK/__INIT__.PY;1', rr_name='__init__.py') do_a_test(iso, check_rr_deep_weird_layout) iso.close() def test_new_rr_long_dir_name(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/AAAAAAAA', rr_name='a'*248) do_a_test(iso, check_rr_long_dir_name) iso.close() def test_new_rr_out_of_order_ce(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_symlink('/SYM.;1', 'sym', '/'.join(['a'*RR_MAX_FILENAME_LENGTH, 'b'*RR_MAX_FILENAME_LENGTH, 'c'*RR_MAX_FILENAME_LENGTH, 'd'*RR_MAX_FILENAME_LENGTH, 'e'*RR_MAX_FILENAME_LENGTH])) iso.add_directory('/AAAAAAAA', rr_name='a'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_out_of_order_ce) iso.close() def test_new_rr_ce_removal(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/AAAAAAAA', rr_name='a'*RR_MAX_FILENAME_LENGTH) iso.add_directory('/BBBBBBBB', rr_name='b'*RR_MAX_FILENAME_LENGTH) iso.add_directory('/CCCCCCCC', rr_name='c'*RR_MAX_FILENAME_LENGTH) iso.add_directory('/DDDDDDDD', rr_name='d'*RR_MAX_FILENAME_LENGTH) iso.rm_directory('/CCCCCCCC', rr_name='c'*RR_MAX_FILENAME_LENGTH) iso.add_directory('/EEEEEEEE', rr_name='e'*RR_MAX_FILENAME_LENGTH) do_a_test(iso, check_rr_ce_removal) iso.close() def test_new_duplicate_pvd_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.duplicate_pvd() do_a_test(iso, check_duplicate_pvd_joliet) iso.close() def test_new_write_fp_not_binary(tmpdir): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: with open(os.path.join(str(tmpdir), 'out.iso'), 'w') as outfp: iso.write_fp(outfp) assert(str(excinfo.value) == "The file to write out must be in binary mode (add 'b' to the open flags)") iso.close() def test_new_add_directory_no_path(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory() assert(str(excinfo.value) == 'Either iso_path or joliet_path must be passed') iso.close() def test_new_add_directory_joliet_only(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1') iso.add_directory(joliet_path='/dir1') do_a_test(iso, check_joliet_onedir) iso.close() def test_new_rm_directory_no_path(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory() assert(str(excinfo.value) == 'Either iso_path or joliet_path must be passed') iso.close() def test_new_rm_directory_joliet_only(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_joliet_directory(joliet_path='/dir1') iso.rm_directory(joliet_path='/dir1') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_get_and_write_dir(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_and_write_fp('/DIR1', out) assert(str(excinfo.value) == 'Cannot write out a directory') iso.close() def test_new_get_and_write_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3) # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() iso.get_and_write_fp('/foo', out) assert(out.getvalue() == b'foo\n') iso.close() def test_new_get_and_write_iso9660(): iso = pycdlib.PyCdlib() iso.new(joliet=3) # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() iso.get_and_write_fp('/FOO.;1', out) assert(out.getvalue() == b'foo\n') iso.close() def test_new_get_and_write_rr(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') out = BytesIO() iso.get_and_write_fp('/foo', out) assert(out.getvalue() == b'foo\n') iso.close() def test_new_get_and_write_iso9660_no_rr(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_and_write_fp('/BAR.;1', out) assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_get_record_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_record() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_get_record_invalid_kwarg(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_record(foo='bar') assert(str(excinfo.value) == "Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_get_record_multiple_paths(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_record(iso_path='/bar', joliet_path='/bar') assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_get_record_joliet_path(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') rec = iso.get_record(joliet_path='/dir1') assert(rec.file_identifier().decode('utf-16_be') == 'dir1') assert(len(rec.children) == 2) iso.close() def test_new_get_record_iso_path(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') rec = iso.get_record(iso_path='/DIR1') assert(rec.file_identifier() == b'DIR1') assert(len(rec.children) == 2) iso.close() def test_new_get_record_rr_path(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') rec = iso.get_record(rr_path='/dir1') assert(rec.file_identifier() == b'DIR1') assert(len(rec.children) == 2) assert(rec.rock_ridge.name() == b'dir1') iso.close() def test_new_different_joliet_name(): iso = pycdlib.PyCdlib() iso.new(joliet=3, rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', joliet_path='/bar') foojstr = b'foojoliet\n' iso.add_fp(BytesIO(foojstr), len(foojstr), '/FOOJ.;1', rr_name='fooj', joliet_path='/foo') do_a_test(iso, check_joliet_different_names) # Check that we can get the content for the first file using its various names out = BytesIO() iso.get_file_from_iso_fp(out, iso_path='/FOO.;1') assert(out.getvalue() == b'foo\n') out2 = BytesIO() iso.get_file_from_iso_fp(out2, rr_path='/foo') assert(out2.getvalue() == b'foo\n') out3 = BytesIO() iso.get_file_from_iso_fp(out3, joliet_path='/bar') assert(out3.getvalue() == b'foo\n') # Check that we can get the content for the second file using its various names out4 = BytesIO() iso.get_file_from_iso_fp(out4, iso_path='/FOOJ.;1') assert(out4.getvalue() == b'foojoliet\n') out5 = BytesIO() iso.get_file_from_iso_fp(out5, rr_path='/fooj') assert(out5.getvalue() == b'foojoliet\n') out6 = BytesIO() iso.get_file_from_iso_fp(out6, joliet_path='/foo') assert(out6.getvalue() == b'foojoliet\n') iso.close() def test_new_different_rr_isolevel4_name(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4, rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/foo', rr_name='bar') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/bar', rr_name='foo') out = BytesIO() iso.get_file_from_iso_fp(out, iso_path='/foo') assert(out.getvalue() == b'foo\n') out2 = BytesIO() iso.get_file_from_iso_fp(out2, rr_path='/bar') assert(out2.getvalue() == b'foo\n') iso.close() def test_new_get_file_from_iso_fp_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp('foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_get_file_from_iso_fp_invalid_keyword(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp('junk', foo='bar') assert(str(excinfo.value) == 'Unknown keyword foo') def test_new_get_file_from_iso_fp_too_many_args(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp('junk', iso_path='/bar', rr_path='/bar') assert(str(excinfo.value) == "Exactly one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path' must be passed") def test_new_list_children_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(): pass assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_list_children_too_few_args(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(): pass assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_list_children_too_many_args(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(iso_path='/foo', rr_path='/bar'): pass assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_list_children_invalid_arg(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(foo='bar'): pass assert(str(excinfo.value) == "Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_list_children_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory(joliet_path='/dir1') for index,c in enumerate(iso.list_children(joliet_path='/')): if index == 2: assert(c.file_identifier() == 'dir1'.encode('utf-16_be')) assert(index == 2) iso.close() def test_new_list_children_rr(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory(iso_path='/DIR1', rr_name='dir1') for index,c in enumerate(iso.list_children(rr_path='/')): if index == 2: assert(c.file_identifier() == b'DIR1') assert(c.rock_ridge.name() == b'dir1') assert(index == 2) iso.close() def test_new_list_children(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory(iso_path='/DIR1') for index,c in enumerate(iso.list_children(iso_path='/')): if index == 2: assert(c.file_identifier() == b'DIR1') assert(index == 2) iso.close() def test_new_list_dir_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') for index,c in enumerate(iso.list_dir('/', joliet=True)): if index == 2: assert(c.file_identifier() == 'dir1'.encode('utf-16_be')) assert(index == 2) iso.close() def test_new_get_file_from_iso_invalid_path(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, iso_path='/FOO.;1/BAR.;1') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_get_file_from_iso_invalid_joliet_path(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, joliet_path='/foo/bar') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_get_file_from_iso_joliet_path_not_absolute(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, joliet_path='foo') assert(str(excinfo.value) == 'Must be a path starting with /') iso.close() def test_new_get_file_from_iso_joliet_path_not_found(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, joliet_path='/bar') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_get_file_from_iso_blocksize(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') out = BytesIO() iso.get_file_from_iso_fp(out, joliet_path='/foo', blocksize=16384) assert(out.getvalue() == b'foo\n') iso.close() def test_new_get_file_from_iso_no_joliet(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, joliet_path='/foo') assert(str(excinfo.value) == 'Cannot fetch a joliet_path from a non-Joliet ISO') iso.close() def test_new_get_file_from_iso_no_rr(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, rr_path='/foo') assert(str(excinfo.value) == 'Cannot fetch a rr_path from a non-Rock Ridge ISO') iso.close() def test_new_set_hidden_no_paths(): iso = pycdlib.PyCdlib() iso.new() aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_hidden() assert(str(excinfo.value) == 'Must provide exactly one of iso_path, rr_path, or joliet_path') iso.close() def test_new_clear_hidden_no_paths(): iso = pycdlib.PyCdlib() iso.new() aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.clear_hidden() assert(str(excinfo.value) == 'Must provide exactly one of iso_path, rr_path, or joliet_path') iso.close() def test_new_set_hidden_too_many_paths(): iso = pycdlib.PyCdlib() iso.new(joliet=3) aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', joliet_path='/aaaaaaaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.set_hidden(iso_path='/AAAAAAAA.;1', joliet_path='/aaaaaaaa') assert(str(excinfo.value) == 'Must provide exactly one of iso_path, rr_path, or joliet_path') iso.close() def test_new_clear_hidden_too_many_paths(): iso = pycdlib.PyCdlib() iso.new(joliet=3) aastr = b'aa\n' iso.add_fp(BytesIO(aastr), len(aastr), '/AAAAAAAA.;1', joliet_path='/aaaaaaaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.clear_hidden(iso_path='/AAAAAAAA.;1', joliet_path='/aaaaaaaa') assert(str(excinfo.value) == 'Must provide exactly one of iso_path, rr_path, or joliet_path') iso.close() def test_new_add_directory_with_mode(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory(iso_path='/DIR1', file_mode=0o040555) assert(str(excinfo.value) == 'A file mode can only be specified for Rock Ridge ISOs') iso.close() def test_new_full_path_from_dirrecord_root(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') fullpath = iso.full_path_from_dirrecord(iso.pvd.root_directory_record()) assert(fullpath == '/') iso.close() def test_new_full_path_rockridge(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory(iso_path='/DIR1', rr_name='dir1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/DIR1/BOOT.;1', rr_name='boot') full_path = None for child in iso.list_children(rr_path='/dir1'): if child.file_identifier() == b'BOOT.;1': full_path = iso.full_path_from_dirrecord(child, rockridge=True) assert(full_path == '/dir1/boot') break assert(full_path is not None) iso.close() def test_new_list_children_joliet_subdir(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory(iso_path='/DIR1', joliet_path='/dir1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/DIR1/BOOT.;1', joliet_path='/dir1/boot') full_path = None for child in iso.list_children(joliet_path='/dir1'): if child.file_identifier() == 'boot'.encode('utf-16_be'): full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/dir1/boot') break assert(full_path is not None) iso.close() def test_new_joliet_encoded_system_identifier(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4, joliet=3, rock_ridge='1.09', sys_ident='LINUX', vol_ident='cidata') user_data_str = b'''\ #cloud-config password: password chpasswd: { expire: False } ssh_pwauth: True ''' iso.add_fp(BytesIO(user_data_str), len(user_data_str), '/user-data', rr_name='user-data', joliet_path='/user-data') meta_data_str = b'''\ local-hostname: cloudimg ''' iso.add_fp(BytesIO(meta_data_str), len(meta_data_str), '/meta-data', rr_name='meta-data', joliet_path='/meta-data') do_a_test(iso, check_joliet_ident_encoding) iso.close() def test_new_duplicate_pvd_isolevel4(): # 51200 without interchange_level 4, without duplicate_pvd # 53248 without interchange level 4, with duplicate pvd # 55296 with interchange level 4, with duplicate pvd iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.duplicate_pvd() do_a_test(iso, check_duplicate_pvd_isolevel4) iso.close() def test_new_joliet_hidden_iso_file(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_joliet_hidden_iso_file) iso.close() def test_new_add_file_hard_link_rm_file(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/LINK.;1') iso.rm_file('/FOO.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_file_mode_not_rock_ridge(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', file_mode=0o0100444) assert(str(excinfo.value) == 'Can only specify a file mode for Rock Ridge ISOs') iso.close() def test_new_eltorito_hide_boot_link(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_hard_link(iso_old_path='/BOOT.;1', iso_new_path='/BOOTLINK.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link(iso_path='/BOOT.;1') do_a_test(iso, check_eltorito_bootlink) iso.close() def test_new_iso_only_add_rm_hard_link(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.rm_hard_link('/BAR.;1') iso.rm_file('/FOO.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_rm_hard_link_twice(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.rm_hard_link(iso_path='/BAR.;1') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_rm_hard_link_twice2(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.rm_hard_link(iso_path='/FOO.;1') iso.rm_hard_link(iso_path='/BAR.;1') do_a_test(iso, check_nofiles) iso.close() def test_new_rm_eltorito_leave_file(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') iso.rm_eltorito() do_a_test(iso, check_onefile) iso.close() def test_new_add_eltorito_rm_file(): iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('/BOOT.;1') assert(str(excinfo.value) == "Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") iso.close() def test_new_eltorito_multi_boot_rm_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2') iso.add_eltorito('/boot2', '/boot.cat') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('/boot2') assert(str(excinfo.value) == "Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") iso.close() def test_new_get_file_from_iso_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.add_symlink('/SYM.;1', 'sym', 'foo') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, iso_path='/SYM.;1') assert(str(excinfo.value) == 'Symlinks have no data associated with them') iso.close() def test_new_udf_nofiles(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') do_a_test(iso, check_udf_nofiles) iso.close() def test_new_udf_onedir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') do_a_test(iso, check_udf_onedir) iso.close() def test_new_udf_twodirs(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') iso.add_directory('/DIR2', udf_path='/dir2') do_a_test(iso, check_udf_twodirs) iso.close() def test_new_udf_subdir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') iso.add_directory('/DIR1/SUBDIR1', udf_path='/dir1/subdir1') do_a_test(iso, check_udf_subdir) iso.close() def test_new_udf_subdir_odd(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') iso.add_directory('/DIR1/SUBDI1', udf_path='/dir1/subdi1') do_a_test(iso, check_udf_subdir_odd) iso.close() def test_new_udf_rm_directory(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') iso.rm_directory('/DIR1', udf_path='/dir1') do_a_test(iso, check_udf_nofiles) iso.close() def test_new_udf_onefile(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') do_a_test(iso, check_udf_onefile) iso.close() def test_new_udf_onefileonedir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') do_a_test(iso, check_udf_onefileonedir) iso.close() def test_new_udf_rm_file(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.rm_file('/FOO.;1', udf_path='/foo') do_a_test(iso, check_udf_nofiles) iso.close() def test_new_udf_dir_spillover(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') for i in range(ord('a'), ord('v')): iso_dirname = '/' + chr(i).upper() * 8 udf_dirname = '/' + chr(i) * 64 iso.add_directory(iso_dirname, udf_path=udf_dirname) do_a_test(iso, check_udf_dir_spillover) iso.close() def test_new_udf_dir_oneshort(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') for i in range(ord('a'), ord('u')): iso_dirname = '/' + chr(i).upper() * 8 udf_dirname = '/' + chr(i) * 64 iso.add_directory(iso_dirname, udf_path=udf_dirname) do_a_test(iso, check_udf_dir_oneshort) iso.close() def test_new_udf_iso_hidden(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.rm_hard_link(iso_path='/FOO.;1') do_a_test(iso, check_udf_iso_hidden) iso.close() def test_new_udf_hard_link(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', udf_new_path='/foo') do_a_test(iso, check_udf_onefile) iso.close() def test_new_udf_rm_add_hard_link(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.rm_hard_link(iso_path='/FOO.;1') iso.add_hard_link(udf_old_path='/foo', iso_new_path='/FOO.;1') do_a_test(iso, check_udf_onefile) iso.close() def test_new_udf_hidden(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.rm_hard_link(udf_path='/foo') do_a_test(iso, check_udf_hidden) iso.close() @pytest.mark.slow def test_new_very_largefile(tmpdir): indir = tmpdir.mkdir('verylarge') largefile = os.path.join(str(indir), 'bigfile') with open(largefile, 'w') as outfp: outfp.truncate(5*1024*1024*1024) # 5 GB iso = pycdlib.PyCdlib() iso.new(interchange_level=3) # Add a new file. iso.add_file(largefile, '/BIGFILE.;1') full_path = None num_children = 0 for child in iso.list_children(iso_path='/'): if child.file_identifier() == b'BIGFILE.;1': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/BIGFILE.;1') num_children += 1 assert(full_path is not None) assert(num_children == 3) do_a_test(iso, check_very_largefile, tmpdir) iso.close() @pytest.mark.slow def test_new_six_gb_file(tmpdir): # An issue was found where any files larger than 6442444800 bytes couldn't # be extracted with pycdlib. This test ensures that that continues to work. indir = tmpdir.mkdir('sixgb') largefile = os.path.join(str(indir), 'bigfile') output_iso = os.path.join(str(indir), 'sixgb.iso') testfile = os.path.join(str(indir), 'testfile') with open(largefile, 'w') as outfp: outfp.truncate(6442444801) iso = pycdlib.PyCdlib() iso.new(interchange_level=3) iso.add_file(largefile, '/BIGFILE.;1') iso.write(output_iso) iso.close() newiso = pycdlib.PyCdlib() newiso.open(output_iso) newiso.get_file_from_iso(testfile, iso_path='/BIGFILE.;1') newiso.close() st = os.stat(testfile) assert(st.st_size == 6442444801) @pytest.mark.slow def test_new_rm_very_largefile(tmpdir): indir = tmpdir.mkdir('rmverylarge') largefile = os.path.join(str(indir), 'bigfile') with open(largefile, 'w') as outfp: outfp.truncate(5*1024*1024*1024) # 5 GB iso = pycdlib.PyCdlib() iso.new(interchange_level=3) # Add a new file. iso.add_file(largefile, '/BIGFILE.;1') iso.rm_file('/BIGFILE.;1') do_a_test(iso, check_nofiles, tmpdir) iso.close() @pytest.mark.slow def test_new_udf_very_large(tmpdir): indir = tmpdir.mkdir('udfverylarge') largefile = os.path.join(str(indir), 'foo') with open(largefile, 'wb') as outfp: outfp.truncate(1073739776+1) iso = pycdlib.PyCdlib() iso.new(interchange_level=1, udf='2.60') # Add a new file. iso.add_file(largefile, '/FOO.;1', udf_path='/foo') do_a_test(iso, check_udf_very_large, tmpdir) iso.close() def test_new_lookup_after_rmdir(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') rec = iso.get_record(iso_path='/DIR1') assert(rec.file_identifier() == b'DIR1') assert(len(rec.children) == 2) iso.rm_directory('/DIR1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rec = iso.get_record(iso_path='/DIR1') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_lookup_after_rmfile(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') rec = iso.get_record(iso_path='/FOO.;1') assert(rec.file_identifier() == b'FOO.;1') assert(len(rec.children) == 0) iso.rm_file('/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rec = iso.get_record(iso_path='/FOO.;1') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_udf_lookup_after_rmdir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') rec = iso.get_record(udf_path='/dir1') iso.rm_directory('/DIR1', udf_path='/dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rec = iso.get_record(udf_path='/dir1') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_udf_lookup_after_rmfile(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') rec = iso.get_record(udf_path='/foo') iso.rm_file('/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rec = iso.get_record(udf_path='/foo') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_full_path_no_rr(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') rec = iso.get_record(iso_path='/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: name = iso.full_path_from_dirrecord(rec, True) assert(str(excinfo.value) == 'Cannot generate a Rock Ridge path on a non-Rock Ridge ISO') iso.close() def test_new_list_children_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/DIR1/BOOT.;1', udf_path='/dir1/boot') full_path = None for child in iso.list_children(udf_path='/dir1'): if child is not None: if child.file_identifier() == b'boot': break else: assert(False) iso.close() def test_new_udf_list_children_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(udf_path='/foo'): pass assert(str(excinfo.value) == 'UDF File Entry is not a directory!') iso.close() def test_new_list_children_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(iso_path='/FOO.;1'): pass assert(str(excinfo.value) == 'Record is not a directory!') iso.close() def test_new_list_children_joliet_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for c in iso.list_children(joliet_path='/foo'): pass assert(str(excinfo.value) == 'Record is not a directory!') iso.close() def test_new_udf_remove_base(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory(udf_path='/') assert(str(excinfo.value) == 'Cannot remove base directory') iso.close() def test_new_remove_udf_path_not_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_directory(udf_path='/dir1') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_add_dir_udf_path_not_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_directory(udf_path='/dir1') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_rm_link_udf_path_not_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(udf_path='/foo') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_rm_link_udf_path_not_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_hard_link(udf_path='/dir1') assert(str(excinfo.value) == 'Cannot remove a directory with rm_hard_link (try rm_directory instead)') iso.close() def test_new_add_link_udf_path_not_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_old_path='/FOO.;1', udf_new_path='/foo') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_add_fp_udf_path_not_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_get_file_from_iso_fp_udf_path_not_udf(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, udf_path='/foo') assert(str(excinfo.value) == 'Cannot fetch a udf_path from a non-UDF ISO') iso.close() def test_new_joliet_udf_nofiles(): iso = pycdlib.PyCdlib() iso.new(joliet=3, udf='2.60') do_a_test(iso, check_joliet_udf_nofiles) iso.close() def test_new_udf_dir_exactly2048(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/AAAAAAAA', udf_path='/' + 'a'*248) iso.add_directory('/BBBBBBBB', udf_path='/' + 'b'*248) iso.add_directory('/CCCCCCCC', udf_path='/' + 'c'*248) iso.add_directory('/DDDDDDDD', udf_path='/' + 'd'*248) iso.add_directory('/EEEEEEEE', udf_path='/' + 'e'*248) iso.add_directory('/FFFFFFFF', udf_path='/' + 'f'*248) iso.add_directory('/GGGGGGGG', udf_path='/' + 'g'*240) do_a_test(iso, check_udf_dir_exactly2048) iso.close() def test_new_udf_symlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_symlink('/BAR.;1', udf_symlink_path='/bar', udf_target='foo') do_a_test(iso, check_udf_symlink) iso.close() def test_new_udf_symlink_in_dir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1', udf_path='/dir1/foo') iso.add_symlink('/BAR.;1', udf_symlink_path='/bar', udf_target='dir1/foo') do_a_test(iso, check_udf_symlink_in_dir) iso.close() def test_new_udf_symlink_abs_path(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_symlink('/BAR.;1', udf_symlink_path='/bar', udf_target='/etc/os-release') do_a_test(iso, check_udf_symlink_abs_path) iso.close() def test_new_symlink_no_rr_symlink_name(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1') assert(str(excinfo.value) == 'Either a Rock Ridge or a UDF symlink must be specified') iso.close() def test_new_symlink_rr_path_no_rr(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1', rr_path='/foo') assert(str(excinfo.value) == 'Can only add a symlink to a Rock Ridge or UDF ISO') iso.close() def test_new_symlink_no_rr_no_udf(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1', udf_symlink_path='/foo') assert(str(excinfo.value) == 'Can only add a symlink to a Rock Ridge or UDF ISO') iso.close() def test_new_symlink_no_udf(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1', udf_symlink_path='/foo', udf_target='bar') assert(str(excinfo.value) == 'A UDF symlink can only be created on a UDF ISO') iso.close() def test_new_udf_symlink_no_target(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1', udf_symlink_path='/foo') assert(str(excinfo.value) == "Both of 'udf_symlink_path' and 'udf_target' must be provided for a UDF symlink") iso.close() def test_new_udf_symlink_add_rr(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/BAR.;1', rr_symlink_name='foo', rr_path='/') assert(str(excinfo.value) == 'A Rock Ridge symlink can only be created on a Rock Ridge ISO') iso.close() def test_new_rr_symlink_no_iso_path(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink(rr_symlink_name='foo', rr_path='/') assert(str(excinfo.value) == "When making a Rock Ridge symlink 'symlink_path' is required") iso.close() def test_new_rr_symlink_no_iso_path(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink() assert(str(excinfo.value) == 'Either a Rock Ridge or a UDF symlink must be specified') iso.close() def test_new_rr_rm_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') iso.add_symlink('/SYM.;1', 'sym', 'foo') iso.rm_file('/SYM.;1') do_a_test(iso, check_rr_onefile) iso.close() def test_new_udf_rm_link_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') # Add: any new extents for FI container (0) + log_block_size (File Entry) + file_entry.info_len iso.add_symlink('/SYM.;1', udf_symlink_path='/sym', udf_target='/foo') iso.rm_file('/SYM.;1') iso.rm_hard_link(udf_path='/sym') do_a_test(iso, check_udf_onefile) iso.close() def test_new_udf_rr_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09', udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo', udf_path='/foo') # Add: any new extents for FI container (0) + log_block_size (File Entry) + file_entry.info_len iso.add_symlink('/SYM.;1', rr_symlink_name='sym', rr_path='foo', udf_symlink_path='/sym', udf_target='foo') do_a_test(iso, check_udf_rr_symlink) iso.close() def test_new_udf_overflow_dir_extent(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') tmp = [] for i in range(1, 1+46): tmp.append('/dir' + str(i)) names = sorted(tmp) for name in names: iso.add_directory(name.upper(), udf_path=name) do_a_test(iso, check_udf_overflow_dir_extent) iso.close() def test_new_udf_hardlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_hard_link(udf_old_path='/foo', udf_new_path='/bar') do_a_test(iso, check_udf_hardlink) iso.close() def test_new_multi_hard_link(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.add_hard_link(iso_old_path='/BAR.;1', iso_new_path='/BAZ.;1') do_a_test(iso, check_multi_hard_link) iso.close() def test_new_multi_hard_link2(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAZ.;1') do_a_test(iso, check_multi_hard_link) iso.close() def test_new_joliet_with_version(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo.;1') do_a_test(iso, check_joliet_with_version) iso.close() def test_new_link_joliet_to_iso(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_hard_link(iso_path='/FOO.;1') iso.add_hard_link(joliet_old_path='/foo', iso_new_path='/FOO.;1') do_a_test(iso, check_joliet_onefile) iso.close() def test_new_udf_joliet_onefile(): iso = pycdlib.PyCdlib() iso.new(joliet=3, udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo', udf_path='/foo') do_a_test(iso, check_udf_joliet_onefile) iso.close() def test_new_link_joliet_to_udf(): iso = pycdlib.PyCdlib() iso.new(joliet=3, udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.add_hard_link(joliet_old_path='/foo', udf_new_path='/foo') do_a_test(iso, check_udf_joliet_onefile) iso.close() def test_new_link_udf_to_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3, udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_hard_link(udf_old_path='/foo', joliet_new_path='/foo') do_a_test(iso, check_udf_joliet_onefile) iso.close() def test_new_joliet_hard_link_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(joliet=3) bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', joliet_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link('/BOOT.CAT;1') iso.rm_hard_link(joliet_path='/boot.cat') iso.add_hard_link(boot_catalog_old=True, joliet_new_path='/boot.cat') do_a_test(iso, check_joliet_and_eltorito_joliet_only) iso.close() def test_new_udf_hard_link_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1', udf_path='/boot') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') iso.rm_hard_link('/BOOT.CAT;1') iso.rm_hard_link(udf_path='/boot.cat') iso.add_hard_link(boot_catalog_old=True, udf_new_path='/boot.cat') do_a_test(iso, check_udf_and_eltorito_udf_only) iso.close() def test_new_bogus_symlink(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/SYM.;1', 'sym') assert(str(excinfo.value) == "Both of 'rr_symlink_name' and 'rr_path' must be provided for a Rock Ridge symlink") iso.close() def test_new_joliet_symlink_no_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_symlink('/SYM.;1', 'sym', 'foo', joliet_path='/foo') assert(str(excinfo.value) == 'A Joliet path can only be specified for a Joliet ISO') iso.close() def test_new_eltorito_udf_rm_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') iso.rm_eltorito() do_a_test(iso, check_udf_onefile) iso.close() def test_new_add_eltorito_udf_path_no_udf(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1', udf_bootcatfile='/foo') assert(str(excinfo.value) == 'A UDF path must not be passed when adding El Torito to a non-UDF ISO') iso.close() def test_new_add_eltorito_joliet_path_no_joliet(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1', joliet_bootcatfile='/foo') assert(str(excinfo.value) == 'A joliet path must not be passed when adding El Torito to a non-Joliet ISO') iso.close() def test_new_rm_file_linked_by_eltorito_bootcat(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file('/BOOT.CAT;1') assert(str(excinfo.value) == "Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") iso.close() def test_new_invalid_udf_version(): # Create a new ISO. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.new(udf='foo') assert(str(excinfo.value) == 'UDF value must be empty (no UDF), or 2.60') def test_new_udf_rm_hard_link_multi_links(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/bar') iso.add_hard_link(udf_old_path='/bar', udf_new_path='/foo') iso.add_hard_link(udf_old_path='/bar', udf_new_path='/baz') iso.rm_hard_link(udf_path='/bar') do_a_test(iso, check_udf_onefile_multi_links) iso.close() def test_new_hard_link_invalid_new_keyword(): iso = pycdlib.PyCdlib() iso.new() # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_hard_link(iso_old_path='/FOO.;1', blah='some') assert(str(excinfo.value) == 'Unknown keyword blah') iso.close() def test_new_udf_dotdot_symlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_symlink('/DIR1/SYM.;1', udf_symlink_path='/dir1/sym', udf_target='../foo') do_a_test(iso, check_udf_dotdot_symlink) iso.close() def test_new_udf_dot_symlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_symlink('/SYM.;1', udf_symlink_path='/sym', udf_target='./foo') do_a_test(iso, check_udf_dot_symlink) iso.close() def test_new_udf_zero_byte_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/BAR.;1', udf_path='/bar') do_a_test(iso, check_udf_zero_byte_file) iso.close() def test_new_udf_fail_find(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp('/foo') assert(str(excinfo.value) == "Exactly one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path' must be passed") iso.close() def test_new_udf_onefile_onedirwithfile(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') barstr = b'bar\n' iso.add_fp(BytesIO(barstr), len(barstr), '/DIR1/BAR.;1', udf_path='/dir1/bar') do_a_test(iso, check_udf_onefile_onedirwithfile) iso.close() def test_new_udf_get_invalid(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') out = BytesIO() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(out, udf_path='/foo/some') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_zero_byte_hard_link(): iso = pycdlib.PyCdlib() iso.new() foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') iso.add_hard_link(iso_old_path='/FOO.;1', iso_new_path='/BAR.;1') do_a_test(iso, check_zero_byte_hard_link) iso.close() def test_new_udf_zero_byte_hard_link(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_hard_link(udf_old_path='/foo', udf_new_path='/bar') do_a_test(iso, check_udf_zero_byte_hard_link) iso.close() def test_new_unicode_name(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F__O.;1') do_a_test(iso, check_unicode_name) iso.close() def test_new_unicode_name_isolevel4(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/föo') do_a_test(iso, check_unicode_name_isolevel4) iso.close() def test_new_unicode_name_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F__O.;1', joliet_path='/föo') do_a_test(iso, check_unicode_name_joliet) iso.close() def test_new_unicode_name_udf(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F__O.;1', udf_path='/föo') do_a_test(iso, check_unicode_name_udf) iso.close() def test_new_unicode_name_two_byte(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1') do_a_test(iso, check_unicode_name_two_byte) iso.close() def test_new_unicode_name_two_byte_isolevel4(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/fᴔo') do_a_test(iso, check_unicode_name_two_byte_isolevel4) iso.close() def test_new_unicode_name_two_byte_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1', joliet_path='/fᴔo') do_a_test(iso, check_unicode_name_two_byte_joliet) iso.close() def test_new_unicode_name_two_byte_udf(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1', udf_path='/fᴔo') do_a_test(iso, check_unicode_name_two_byte_udf) iso.close() def test_new_unicode_name_two_byte_isolevel4_list_children(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/fᴔo') full_path = None for child in iso.list_children(iso_path='/'): if child.file_identifier() == b'f\xe1\xb4\x94o': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/fᴔo') break assert(full_path is not None) iso.close() def test_new_unicode_name_two_byte_joliet_list_children(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1', joliet_path='/fᴔo') full_path = None for child in iso.list_children(joliet_path='/'): if child.file_identifier() == b'\x00f\x1d\x14\x00o': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/fᴔo') break assert(full_path is not None) iso.close() def test_new_unicode_name_two_byte_udf_list_children(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1', udf_path='/fᴔo') full_path = None for child in iso.list_children(udf_path='/'): if child is not None and child.file_identifier() == b'\x00f\x1d\x14\x00o': full_path = iso.full_path_from_dirrecord(child) assert(full_path == '/fᴔo') break assert(full_path is not None) iso.close() def test_new_add_non_binary_file(): iso = pycdlib.PyCdlib() iso.new() foostr = u'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(io.StringIO(foostr), len(foostr), '/FOO.;1') assert(str(excinfo.value) == 'The fp argument must be in binary mode') iso.close() def test_new_udf_get_symlink_file(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_symlink('/BAR.;1', udf_symlink_path='/bar', udf_target='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso_fp(BytesIO(), udf_path='/bar') assert(str(excinfo.value) == 'Can only write out a file') iso.close() def test_new_udf_unicode_symlink(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/F___O.;1', udf_path='/fᴔo') iso.add_symlink('/BAR.;1', udf_symlink_path='/bar', udf_target='fᴔo') do_a_test(iso, check_udf_unicode_symlink) iso.close() def test_new_udf_bad_tag_location(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') out = BytesIO() iso.write_fp(out) iso.close() # Seek to the end anchor and change the tag location to be incorrect out.seek(-2048, 2) out.seek(12, 1) out.write(b'\x00\x00\x00\x00') # Now fix up the checksum out.seek(-2048, 2) out.seek(4, 1) out.write(b'\xcd') out.seek(0) iso = pycdlib.PyCdlib() iso.open_fp(out) # Now check that the tag location has been corrected by pycdlib. assert(iso.udf_anchors[1].desc_tag.tag_location == 266) iso.close() def test_new_eltorito_rm_multi_boot(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'foo\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/FOO.;1') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/BOOT2.;1') iso.add_eltorito('/BOOT2.;1', '/BOOT.CAT;1') iso.rm_eltorito() iso.rm_file('/BOOT2.;1') do_a_test(iso, check_onefile) iso.close() def test_new_full_path_from_dirrecord_udf_root(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') assert(iso.full_path_from_dirrecord(iso.udf_root) == '/') iso.close() def test_new_udf_file_entry_is_dot(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') rec = iso.get_record(udf_path='/dir1') assert(not rec.is_dot()) iso.close() def test_new_udf_file_entry_is_dotdot(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') rec = iso.get_record(udf_path='/dir1') assert(not rec.is_dotdot()) iso.close() def test_new_walk_iso(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1') iso.add_directory('/DIR2') iso.add_fp(BytesIO(foostr), len(foostr), '/DIR2/FOO.;1') iso.add_directory('/DIR3') iso.add_directory('/DIR3/SUBDIR3') # A list of lists, where each sub-list consists of the expected # name, directories, and files. expected_names = [ ['/', ['DIR3', 'DIR2', 'DIR1'], []], ['/DIR1', ['SUBDIR1'], ['FOO.;1']], ['/DIR1/SUBDIR1', [], []], ['/DIR2', [], ['FOO.;1']], ['/DIR3', ['SUBDIR3'], []], ['/DIR3/SUBDIR3', [], []] ] expected_offset = 0 for dirname, dirlist, filelist in iso.walk(iso_path='/'): assert(dirname == expected_names[expected_offset][0]) assert(dirlist == expected_names[expected_offset][1]) assert(filelist == expected_names[expected_offset][2]) expected_offset += 1 iso.close() def test_new_walk_rr(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') iso.add_directory('/DIR1/SUBDIR1', rr_name='subdir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1', rr_name='foo') iso.add_directory('/DIR2', rr_name='dir2') iso.add_fp(BytesIO(foostr), len(foostr), '/DIR2/FOO.;1', rr_name='foo') iso.add_directory('/DIR3', rr_name='dir3') iso.add_directory('/DIR3/SUBDIR3', rr_name='subdir3') # A list of lists, where each sub-list consists of the expected # name, directories, and files. expected_names = [ ['/', ['dir3', 'dir2', 'dir1'], []], ['/dir1', ['subdir1'], ['foo']], ['/dir1/subdir1', [], []], ['/dir2', [], ['foo']], ['/dir3', ['subdir3'], []], ['/dir3/subdir3', [], []] ] expected_offset = 0 for dirname, dirlist, filelist in iso.walk(rr_path='/'): assert(dirname == expected_names[expected_offset][0]) assert(dirlist == expected_names[expected_offset][1]) assert(filelist == expected_names[expected_offset][2]) expected_offset += 1 iso.close() def test_new_walk_joliet(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') iso.add_directory('/DIR1/SUBDIR1', joliet_path='/dir1/subdir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1', joliet_path='/dir1/foo') iso.add_directory('/DIR2', joliet_path='/dir2') iso.add_fp(BytesIO(foostr), len(foostr), '/DIR2/FOO.;1', joliet_path='/dir2/foo') iso.add_directory('/DIR3', joliet_path='/dir3') iso.add_directory('/DIR3/SUBDIR3', joliet_path='/dir3/subdir3') # A list of lists, where each sub-list consists of the expected # name, directories, and files. expected_names = [ ['/', ['dir3', 'dir2', 'dir1'], []], ['/dir1', ['subdir1'], ['foo']], ['/dir1/subdir1', [], []], ['/dir2', [], ['foo']], ['/dir3', ['subdir3'], []], ['/dir3/subdir3', [], []] ] expected_offset = 0 for dirname, dirlist, filelist in iso.walk(joliet_path='/'): assert(dirname == expected_names[expected_offset][0]) assert(dirlist == expected_names[expected_offset][1]) assert(filelist == expected_names[expected_offset][2]) expected_offset += 1 iso.close() def test_new_walk_udf(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') iso.add_directory('/DIR1/SUBDIR1', udf_path='/dir1/subdir1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1', udf_path='/dir1/foo') iso.add_directory('/DIR2', udf_path='/dir2') iso.add_fp(BytesIO(foostr), len(foostr), '/DIR2/FOO.;1', udf_path='/dir2/foo') iso.add_directory('/DIR3', udf_path='/dir3') iso.add_directory('/DIR3/SUBDIR3', udf_path='/dir3/subdir3') # A list of lists, where each sub-list consists of the expected # name, directories, and files. expected_names = [ ['/', ['dir3', 'dir2', 'dir1'], []], ['/dir1', ['subdir1'], ['foo']], ['/dir1/subdir1', [], []], ['/dir2', [], ['foo']], ['/dir3', ['subdir3'], []], ['/dir3/subdir3', [], []] ] expected_offset = 0 for dirname, dirlist, filelist in iso.walk(udf_path='/'): assert(dirname == expected_names[expected_offset][0]) assert(dirlist == expected_names[expected_offset][1]) assert(filelist == expected_names[expected_offset][2]) expected_offset += 1 iso.close() def test_new_walk_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(iso_path='/'): pass assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_walk_bad_keyword(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(foo='bar'): pass assert(str(excinfo.value) == "Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_walk_no_paths(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(): pass assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_walk_too_many_paths(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(iso_path='/', joliet_path='/'): pass assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_walk_joliet_no_joliet(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(joliet_path='/'): pass assert(str(excinfo.value) == 'A Joliet path can only be specified for a Joliet ISO') iso.close() def test_new_walk_rr_no_rr(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(rr_path='/'): pass assert(str(excinfo.value) == 'Cannot fetch a rr_path from a non-Rock Ridge ISO') iso.close() def test_new_walk_udf_no_udf(): iso = pycdlib.PyCdlib() iso.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for x,y,z in iso.walk(udf_path='/'): pass assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_walk_iso_remove_dirlist_entry(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') iso.add_directory('/DIR1/SUBDIR1') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/DIR1/FOO.;1') iso.add_directory('/DIR2') iso.add_fp(BytesIO(foostr), len(foostr), '/DIR2/FOO.;1') iso.add_directory('/DIR3') iso.add_directory('/DIR3/SUBDIR3') # A list of lists, where each sub-list consists of the expected # name, directories, and files. expected_names = [ ['/', ['DIR3', 'DIR2', 'DIR1'], []], ['/DIR1', ['SUBDIR1'], ['FOO.;1']], ['/DIR2', [], ['FOO.;1']], ['/DIR3', ['SUBDIR3'], []], ['/DIR3/SUBDIR3', [], []] ] expected_offset = 0 for dirname, dirlist, filelist in iso.walk(iso_path='/'): assert(dirname == expected_names[expected_offset][0]) assert(dirlist == expected_names[expected_offset][1]) assert(filelist == expected_names[expected_offset][2]) if dirname == '/DIR1': del dirlist[:] expected_offset += 1 iso.close() def test_new_walk_filename(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for dirname, dirlist, filelist in iso.walk(iso_path='/FOO.;1'): pass assert(str(excinfo.value) == 'Record is not a directory!') def test_new_walk_udf_filename(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for dirname, dirlist, filelist in iso.walk(udf_path='/foo'): pass assert(str(excinfo.value) == 'UDF File Entry is not a directory!') def test_new_walk_udf_zero_entry_path(): # FIXME: implement me! pass def test_new_open_file_from_iso_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(iso_path='/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_open_file_from_iso_invalid_kwarg(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(foo_path='/FOO.;1') assert(str(excinfo.value) == "Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_open_file_from_iso_too_many_kwarg(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(iso_path='/FOO.;1', udf_path='/foo') assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_open_file_from_iso_too_few_kwarg(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso() assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_open_file_from_iso_invalid_joliet(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(joliet_path='/foo') assert(str(excinfo.value) == 'A Joliet path can only be specified for a Joliet ISO') iso.close() def test_new_open_file_from_iso_invalid_rr(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(rr_path='/foo') assert(str(excinfo.value) == 'Cannot fetch a rr_path from a non-Rock Ridge ISO') iso.close() def test_new_open_file_from_iso_invalid_udf(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(udf_path='/foo') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_open_file_from_iso_dir(): iso = pycdlib.PyCdlib() iso.new() iso.add_directory('/DIR1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(iso_path='/DIR1') assert(str(excinfo.value) == 'Path to open must be a file') iso.close() def test_new_open_file_from_iso_joliet_dir(): iso = pycdlib.PyCdlib() iso.new(joliet=3) iso.add_directory('/DIR1', joliet_path='/dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(joliet_path='/dir1') assert(str(excinfo.value) == 'Path to open must be a file') iso.close() def test_new_open_file_from_iso_rr_dir(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') iso.add_directory('/DIR1', rr_name='dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(rr_path='/dir1') assert(str(excinfo.value) == 'Path to open must be a file') iso.close() def test_new_open_file_from_iso_udf_dir(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory('/DIR1', udf_path='/dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(udf_path='/dir1') assert(str(excinfo.value) == 'Path to open must be a file') iso.close() def test_new_open_file_from_iso_udf_no_inode(): # FIXME: implement me! pass def test_new_open_file_from_iso_ctxt_manager(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.read() == b'foo\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_past_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(20) assert(infp.read() == b'') assert(infp.tell() == 20) iso.close() def test_new_open_file_from_iso_single(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.read(1) == b'f') assert(infp.tell() == 1) iso.close() def test_new_open_file_from_iso_past_half_past_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(2) assert(infp.read(4) == b'o\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_readall(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.readall() == b'foo\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_readall_past_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(20) assert(infp.readall() == b'') assert(infp.tell() == 20) iso.close() def test_new_open_file_from_iso_readall_half_past_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(2) assert(infp.readall() == b'o\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_seek_invalid_offset(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(4.5) assert(str(excinfo.value) == 'an integer is required') iso.close() def test_new_open_file_from_iso_seek_invalid_whence(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(0, whence=5) assert(str(excinfo.value) == 'Invalid value for whence (options are 0, 1, and 2)') iso.close() def test_new_open_file_from_iso_seek_whence_begin(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(1, whence=0) assert(infp.tell() == 1) assert(infp.readall() == b'oo\n') iso.close() def test_new_open_file_from_iso_seek_whence_negative_begin(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(-1, whence=0) assert(str(excinfo.value) == 'Invalid offset value (must be positive)') iso.close() def test_new_open_file_from_iso_seek_whence_begin_beyond_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(10, whence=0) assert(infp.tell() == 10) assert(infp.readall() == b'') assert(infp.tell() == 10) iso.close() def test_new_open_file_from_iso_seek_whence_curr(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(1, whence=1) assert(infp.tell() == 1) infp.seek(1, whence=1) assert(infp.tell() == 2) assert(infp.readall() == b'o\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_seek_whence_curr_before_start(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(-2, whence=1) assert(str(excinfo.value) == 'Invalid offset value (cannot seek before start of file)') iso.close() def test_new_open_file_from_iso_seek_whence_curr_negative(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.readall() == b'foo\n') assert(infp.tell() == 4) infp.seek(-2, whence=1) assert(infp.tell() == 2) assert(infp.readall() == b'o\n') iso.close() def test_new_open_file_from_iso_seek_whence_end(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(-2, whence=2) assert(infp.tell() == 2) assert(infp.readall() == b'o\n') assert(infp.tell() == 4) iso.close() def test_new_open_file_from_iso_seek_whence_end_before_start(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(-5, whence=2) assert(str(excinfo.value) == 'Invalid offset value (cannot seek before start of file)') iso.close() def test_new_open_file_from_iso_not_open(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.close() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.read() assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.readall() assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.readinto(bytearray(5)) assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seek(0, 0) assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.tell() assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.length() assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.readable() assert(str(excinfo.value) == 'I/O operation on closed file.') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: infp.seekable() assert(str(excinfo.value) == 'I/O operation on closed file.') iso.close() def test_new_open_file_from_iso_length(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.length() == 4) iso.close() def test_new_open_file_from_iso_readable(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.readable()) iso.close() def test_new_open_file_from_iso_seekable(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: assert(infp.seekable()) iso.close() def test_new_open_file_from_iso_readinto(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: arr = bytearray(4) assert(infp.readinto(arr) == 4) assert(arr == b'\x66\x6f\x6f\x0a') iso.close() def test_new_open_file_from_iso_readinto_partial(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: arr = bytearray(2) assert(infp.readinto(arr) == 2) assert(arr == b'\x66\x6f') assert(infp.readinto(arr) == 2) assert(arr == b'\x6f\x0a') iso.close() def test_new_open_file_from_iso_readinto_past_eof(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with iso.open_file_from_iso(iso_path='/FOO.;1') as infp: infp.seek(4) arr = bytearray(2) assert(infp.readinto(arr) == 0) assert(arr == b'\x00\x00') iso.close() def test_new_udf_cyrillic(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') teststr = b'' iso.add_fp(BytesIO(teststr), len(teststr), '/TEST.TXT;1', udf_path='/test.txt') iso.add_directory('/__', udf_path='/РЭ') iso.add_directory('/__/PORT', udf_path='/РЭ/Port') iso.add_directory('/__/________', udf_path='/РЭ/Руководства') iso.add_fp(BytesIO(teststr), len(teststr), '/__/PORT/________.TXT;1', udf_path='/РЭ/Port/виртуальный порт.txt') iso.add_fp(BytesIO(teststr), len(teststr), '/__/________/________.TXT;1', udf_path='/РЭ/Руководства/Руководство по.txt') do_a_test(iso, check_udf_unicode) iso.close() def test_new_eltorito_get_bootcat(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') do_a_test(iso, check_eltorito_get_bootcat) iso.close() def test_new_eltorito_invalid_platform_id(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', None, None, None, 0xff) assert(str(excinfo.value) == 'Invalid platform ID (must be one of 0, 1, 2, or 0xef)') iso.close() def test_new_eltorito_uefi(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1', None, None, None, 0xef) do_a_test(iso, check_eltorito_uefi) iso.close() def test_new_has_rock_ridge_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.has_rock_ridge() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_has_joliet_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.has_joliet() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_has_udf_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.has_udf() assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_open_file_from_iso_eltorito_boot_catalog(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new() bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/BOOT.;1') iso.add_eltorito('/BOOT.;1', '/BOOT.CAT;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open_file_from_iso(iso_path='/BOOT.CAT;1') assert(str(excinfo.value) == 'File has no data') iso.close() def test_new_add_fp_all_none(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.add_fp(BytesIO(foostr), len(foostr)) assert(str(excinfo.value) == "At least one of 'iso_path', 'joliet_path', or 'udf_path' must be provided") iso.close() def test_new_rm_joliet_only(): iso = pycdlib.PyCdlib() iso.new(joliet=3) # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', joliet_path='/foo') iso.rm_file(joliet_path='/foo') do_a_test(iso, check_joliet_nofiles) iso.close() def test_new_rm_udf_only(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.rm_file(udf_path='/foo') do_a_test(iso, check_udf_nofiles) iso.close() def test_new_udf_zero_byte_rm_file(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), udf_path='/foo') iso.rm_file(udf_path='/foo') do_a_test(iso, check_udf_nofiles) iso.close() def test_new_rm_file_no_udf(): iso = pycdlib.PyCdlib() iso.new(joliet=3) foostr = b'' iso.add_fp(BytesIO(foostr), len(foostr), joliet_path='/foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file(udf_path='/foo') assert(str(excinfo.value) == 'Can only specify a UDF path for a UDF ISO') iso.close() def test_new_rm_dir_udf_only(): iso = pycdlib.PyCdlib() iso.new(udf='2.60') iso.add_directory(udf_path='/dir1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file(udf_path='/dir1') assert(str(excinfo.value) == 'Cannot remove a directory with rm_file (try rm_directory instead)') iso.close() def test_new_eltorito_udf_rm_eltorito(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(udf='2.60') # Add a new file. foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', udf_path='/foo') iso.add_eltorito('/FOO.;1', '/BOOT.CAT;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file(udf_path='/foo') assert(str(excinfo.value) == "Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") iso.close() def test_new_udf_eltorito_multi_boot_rm_file(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(interchange_level=4, udf='2.60') bootstr = b'boot\n' iso.add_fp(BytesIO(bootstr), len(bootstr), '/boot', udf_path='/boot') iso.add_eltorito('/boot', '/boot.cat') boot2str = b'boot2\n' iso.add_fp(BytesIO(boot2str), len(boot2str), '/boot2', udf_path='/boot2') iso.add_eltorito('/boot2', '/boot.cat') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.rm_file(udf_path='/boot2') assert(str(excinfo.value) == "Cannot remove a file that is referenced by El Torito; either use 'rm_eltorito' to remove El Torito first, or use 'rm_hard_link' to hide the entry") iso.close() def test_new_rr_file_mode(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') assert(iso.file_mode(rr_path='/foo') == 0o0100444) iso.close() def test_new_file_mode_not_initialized(): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.file_mode(rr_path='/foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_new_rr_file_mode_bad_kwarg(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.file_mode(foo_path='/foo') assert(str(excinfo.value) == "Invalid keyword, must be one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_rr_file_mode_multiple_kwarg(): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1', rr_name='foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.file_mode(rr_path='/foo', iso_path='/FOO.;1') assert(str(excinfo.value) == "Must specify one, and only one of 'iso_path', 'rr_path', 'joliet_path', or 'udf_path'") iso.close() def test_new_rr_file_mode_not_rr(): iso = pycdlib.PyCdlib() iso.new() foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/FOO.;1') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.file_mode(rr_path='/foo') assert(str(excinfo.value) == 'Cannot fetch a rr_path from a non-Rock Ridge ISO') iso.close() def test_new_rr_empty_dir_get_record(): # Create a new ISO. iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # Add new directory. iso.add_directory('/DIR1', rr_name='dir1') # Now try to get a non-existent record in that directory. with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rec = iso.get_record(rr_path='/dir1/foo') assert(str(excinfo.value) == 'Could not find path') iso.close() def test_new_isolevel4_deep_directory(): iso = pycdlib.PyCdlib() iso.new(interchange_level=4) iso.add_directory('/dir1') iso.add_directory('/dir1/dir2') iso.add_directory('/dir1/dir2/dir3') iso.add_directory('/dir1/dir2/dir3/dir4') iso.add_directory('/dir1/dir2/dir3/dir4/dir5') iso.add_directory('/dir1/dir2/dir3/dir4/dir5/dir6') iso.add_directory('/dir1/dir2/dir3/dir4/dir5/dir6/dir7') foostr = b'foo\n' iso.add_fp(BytesIO(foostr), len(foostr), '/dir1/dir2/dir3/dir4/dir5/dir6/dir7/foo') do_a_test(iso, check_isolevel4_deep_directory) iso.close() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1600003985.162449 pycdlib-1.11.0/tests/integration/test_parse.py0000664000175000017500000037430500000000000024227 0ustar00clalancetteclalancette00000000000000# -*- coding: utf-8 -*- import pytest import subprocess import os import sys import struct sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pycdlib from test_common import * def do_a_test(tmpdir, outfile, check_func): testout = tmpdir.join('writetest.iso') # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) check_func(iso, os.stat(str(outfile)).st_size) iso.write(str(testout)) iso.close() # Now round-trip through write. iso2 = pycdlib.PyCdlib() iso2.open(str(testout)) check_func(iso2, os.stat(str(outfile)).st_size) iso2.close() def test_parse_invalid_file(tmpdir): iso = pycdlib.PyCdlib() with pytest.raises(TypeError): iso.open(None) def test_parse_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('nofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_nofiles) def test_parse_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_onefile) def test_parse_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_onedir) def test_parse_twofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_twofiles) def test_parse_twodirs(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefileonedir') outfile = str(indir)+'.iso' indir.mkdir('bb') indir.mkdir('aa') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_twodirs) def test_parse_onefileonedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefileonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_onefileonedir) def test_parse_onefile_onedirwithfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefileonedirwithfile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_onefile_onedirwithfile) def test_parse_tendirs(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('tendirs') outfile = str(indir)+'.iso' numdirs = 10 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_tendirs) def test_parse_dirs_overflow_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('manydirs') outfile = str(indir)+'.iso' numdirs = 295 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_dirs_overflow_ptr_extent) def test_parse_dirs_just_short_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('manydirs') outfile = str(indir)+'.iso' numdirs = 293 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_dirs_just_short_ptr_extent) def test_parse_twoextentfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('bigfile') outfile = str(indir)+'.iso' outstr = b'' for j in range(0, 8): for i in range(0, 256): outstr += struct.pack('=B', i) outstr += struct.pack('=B', 0) with open(os.path.join(str(indir), 'bigfile'), 'wb') as outfp: outfp.write(outstr) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) testout = tmpdir.join('writetest.iso') do_a_test(tmpdir, outfile, check_twoextentfile) def test_parse_twoleveldeepdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeep') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') dir1.mkdir('subdir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_twoleveldeepdir) def test_parse_twoleveldeepfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twoleveldeepfile') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') subdir1 = dir1.mkdir('subdir1') with open(os.path.join(str(subdir1), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_twoleveldeepfile) def test_parse_joliet_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('joliet') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_nofiles) def test_parse_joliet_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('joliet') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_onedir) def test_parse_joliet_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietfile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_onefile) def test_parse_joliet_onefileonedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietfile') outfile = str(indir)+'.iso' indir.mkdir('dir1') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_onefileonedir) def test_parse_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_nofiles) def test_parse_eltorito_twofile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritotwofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'aa'), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_twofile) def test_parse_rr_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_nofiles) def test_parse_rr_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_onefile) def test_parse_rr_twofile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrtwofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_twofile) def test_parse_rr_onefileonedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefileonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_onefileonedir) def test_parse_rr_onefileonedirwithfile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rronefileonedirwithfile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_onefileonedirwithfile) def test_parse_rr_symlink(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlink') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') pwd = os.getcwd() os.chdir(str(indir)) os.symlink('foo', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_symlink) def test_parse_rr_symlink2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlink2') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') pwd = os.getcwd() os.chdir(str(indir)) os.symlink('dir1/foo', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_symlink2) def test_parse_rr_symlink_dot(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlinkdot') outfile = str(indir)+'.iso' pwd = os.getcwd() os.chdir(str(indir)) os.symlink('.', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_symlink_dot) def test_parse_rr_symlink_dotdot(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlinkdotdot') outfile = str(indir)+'.iso' pwd = os.getcwd() os.chdir(str(indir)) os.symlink('..', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_symlink_dotdot) def test_parse_rr_symlink_broken(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlinkbroken') outfile = str(indir)+'.iso' pwd = os.getcwd() os.chdir(str(indir)) os.symlink('foo', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_symlink_broken) def test_parse_alternating_subdir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('alternating') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'bb'), 'wb') as outfp: outfp.write(b'bb\n') cc = indir.mkdir('cc') aa = indir.mkdir('aa') with open(os.path.join(str(indir), 'dd'), 'wb') as outfp: outfp.write(b'dd\n') with open(os.path.join(str(cc), 'sub2'), 'wb') as outfp: outfp.write(b'sub2\n') with open(os.path.join(str(aa), 'sub1'), 'wb') as outfp: outfp.write(b'sub1\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_alternating_subdir) def test_parse_rr_verylongname(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrverylongname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'a'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_verylongname) def test_parse_rr_verylongname_joliet(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrverylongname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'a'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_verylongname_joliet) def test_parse_rr_manylongname(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrmanylongname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'a'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'aa\n') with open(os.path.join(str(indir), 'b'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'bb\n') with open(os.path.join(str(indir), 'c'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'cc\n') with open(os.path.join(str(indir), 'd'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'dd\n') with open(os.path.join(str(indir), 'e'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'ee\n') with open(os.path.join(str(indir), 'f'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'ff\n') with open(os.path.join(str(indir), 'g'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'gg\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_manylongname) def test_parse_rr_manylongname2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrmanylongname2') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'a'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'aa\n') with open(os.path.join(str(indir), 'b'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'bb\n') with open(os.path.join(str(indir), 'c'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'cc\n') with open(os.path.join(str(indir), 'd'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'dd\n') with open(os.path.join(str(indir), 'e'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'ee\n') with open(os.path.join(str(indir), 'f'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'ff\n') with open(os.path.join(str(indir), 'g'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'gg\n') with open(os.path.join(str(indir), 'h'*RR_MAX_FILENAME_LENGTH), 'wb') as outfp: outfp.write(b'hh\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_manylongname2) def test_parse_joliet_and_rr_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandrrnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_rr_nofiles) def test_parse_joliet_and_rr_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandrronefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_rr_onefile) def test_parse_joliet_and_rr_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandrronedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_rr_onedir) def test_parse_rr_and_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrandeltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_and_eltorito_nofiles) def test_parse_rr_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrandeltoritoonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_and_eltorito_onefile) def test_parse_rr_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrandeltoritoonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_and_eltorito_onedir) def test_parse_joliet_and_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandeltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_nofiles) def test_parse_joliet_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandeltoritoonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_onefile) def test_parse_joliet_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietandeltoritoonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_onedir) @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_parse_isohybrid(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-o', str(outfile), str(indir)]) subprocess.call(['isohybrid', '-v', str(outfile)]) do_a_test(tmpdir, outfile, check_isohybrid) @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_parse_isohybrid_uefi(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybriduefi') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') with open(os.path.join(str(indir), 'efiboot.img'), 'wb') as outfp: outfp.write(b'a') retcode = subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-boot-info-table', '-eltorito-alt-boot', '-efi-boot', 'efiboot.img', '-no-emul-boot', '-o', str(outfile), str(indir)]) if retcode != 0: pytest.skip("This version of genisoimage doesn't support -efi-boot") subprocess.call(['isohybrid', '-u', '-v', str(outfile)]) do_a_test(tmpdir, outfile, check_isohybrid_uefi) @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_parse_isohybrid_mac_uefi(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybridmacuefi') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') with open(os.path.join(str(indir), 'efiboot.img'), 'wb') as outfp: outfp.write(b'a') with open(os.path.join(str(indir), 'macboot.img'), 'wb') as outfp: outfp.write(b'b') retcode = subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-boot-info-table', '-eltorito-alt-boot', '-efi-boot', 'efiboot.img', '-no-emul-boot', '-eltorito-alt-boot', '-efi-boot', 'macboot.img', '-no-emul-boot', '-o', str(outfile), str(indir)]) if retcode != 0: pytest.skip("This version of genisoimage doesn't support -efi-boot") subprocess.call(['isohybrid', '-u', '-m', '-v', str(outfile)]) do_a_test(tmpdir, outfile, check_isohybrid_mac_uefi) def test_parse_joliet_rr_and_eltorito_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrrandeltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_rr_and_eltorito_nofiles) def test_parse_joliet_rr_and_eltorito_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrrandeltoritoonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_rr_and_eltorito_onefile) def test_parse_joliet_rr_and_eltorito_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietrrandeltoritoonedir') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_rr_and_eltorito_onedir) def test_parse_rr_deep_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_deep_dir) def test_parse_rr_deep(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'dir8', 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_deep) def test_parse_rr_deep2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8').mkdir('dir9') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'dir8', 'dir9', 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_deep2) def test_parse_xa_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xa') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_nofiles) def test_parse_xa_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xa') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_onefile) def test_parse_xa_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xa') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_onedir) def test_parse_sevendeepdirs(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('sevendeepdirs') outfile = str(indir)+'.iso' numdirs = 7 x = indir for i in range(1, 1+numdirs): x = x.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_sevendeepdirs) def test_parse_xa_joliet_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xajoliet') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_joliet_nofiles) def test_parse_xa_joliet_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xajolietonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_joliet_onefile) def test_parse_xa_joliet_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xajolietonefile') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_xa_joliet_onedir) def test_parse_iso_level4_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4nofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_isolevel4_nofiles) def test_parse_iso_level4_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_isolevel4_onefile) def test_parse_iso_level4_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4onedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_isolevel4_onedir) def test_parse_iso_level4_eltorito(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isolevel4eltorito') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_isolevel4_eltorito) def test_parse_everything(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('everything') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'dir8', 'bar'), 'wb') as outfp: outfp.write(b'bar\n') pwd = os.getcwd() os.chdir(str(indir)) os.symlink('foo', 'sym') os.chdir(pwd) os.link(os.path.join(str(indir), 'foo'), os.path.join(str(indir), 'dir1', 'foo')) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-rational-rock', '-xa', '-boot-info-table', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_everything) def test_parse_rr_xa_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarrnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_xa_nofiles) def test_parse_rr_xa_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarronefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_xa_onefile) def test_parse_rr_xa_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('xarronefile') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-xa', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_xa_onedir) def test_parse_rr_joliet_symlink(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrsymlinkbroken') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') pwd = os.getcwd() os.chdir(str(indir)) os.symlink('foo', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_joliet_symlink) def test_parse_rr_joliet_deep(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrjolietdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_joliet_deep) def test_parse_eltorito_multi_boot(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('multiboot') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') with open(os.path.join(str(indir), 'boot2'), 'wb') as outfp: outfp.write(b'boot2\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-eltorito-alt-boot', '-b', 'boot2', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_multi_boot) def test_parse_eltorito_boot_table(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-boot-info-table', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_boot_info_table) def test_parse_eltorito_boot_table_large(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot'*20) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-boot-info-table', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_boot_info_table_large) def test_parse_hard_link(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') dir1 = indir.mkdir('dir1') os.link(os.path.join(str(indir), 'foo'), os.path.join(str(indir), str(dir1), 'foo')) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_hard_link) def test_parse_open_twice(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'This object already has an ISO; either close it or create a new object') iso.close() def test_parse_get_and_write_fp_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_and_write_fp('/FOO.;1', open(os.path.join(str(tmpdir), 'bar'), 'w')) assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_get_and_write_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_and_write('/FOO.;1', 'foo') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_write_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.write('out.iso') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_write_with_progress(tmpdir): test_parse_write_with_progress.num_progress_calls = 0 test_parse_write_with_progress.done = 0 def _progress(done, total): assert(total == 73728) test_parse_write_with_progress.num_progress_calls += 1 test_parse_write_with_progress.done = done # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.write(str(tmpdir.join('writetest.iso')), progress_cb=_progress) assert(test_parse_write_with_progress.num_progress_calls == 14) assert(test_parse_write_with_progress.done == 73728) iso.close() def test_parse_write_with_progress_three_arg(tmpdir): def _progress(done, total, opaque): assert(total == 73728) opaque['num_calls'] += 1 opaque['done'] = done # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) collect = {'num_calls': 0, 'done': 0} iso.write(str(tmpdir.join('writetest.iso')), progress_cb=_progress, progress_opaque=collect) assert(collect['num_calls'] == 14) assert(collect['done'] == 73728) iso.close() def test_parse_get_entry(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) fooentry = iso.get_entry('/FOO.;1') assert(len(fooentry.children) == 0) assert(fooentry.isdir == False) assert(fooentry.is_root == False) assert(fooentry.file_ident == b'FOO.;1') assert(fooentry.dr_len == 40) assert(fooentry.extent_location() == 24) assert(fooentry.file_flags == 0) assert(fooentry.get_data_length() == 4) iso.close() def test_parse_get_entry_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: fooentry = iso.get_entry('/FOO.;1') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_list_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) for children in iso.list_dir('/DIR1'): pass iso.close() def test_parse_list_dir_not_initialized(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for children in iso.list_dir('/DIR1'): pass assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_list_dir_not_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('twofile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: for children in iso.list_dir('/FOO.;1'): pass assert(str(excinfo.value) == 'Record is not a directory!') iso.close() def test_parse_get_and_write(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'f\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) foofile = os.path.join(str(indir), 'foo') iso.get_and_write('/foo', foofile) iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'f\n') def test_parse_open_fp_twice(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: with open(str(outfile), 'rb') as infp: iso.open_fp(infp) assert(str(excinfo.value) == 'This object already has an ISO; either close it or create a new object') def test_parse_open_invalid_vd(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(16*2048) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Valid ISO9660 filesystems must have at least one PVD') def test_parse_same_dirname_different_parent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('samedirnamedifferentparent') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') dir2 = indir.mkdir('dir2') boot1 = dir1.mkdir('boot') boot2 = dir2.mkdir('boot') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_same_dirname_different_parent) def test_parse_joliet_iso_level_4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietisolevel4') outfile = str(indir)+'.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_isolevel4) def test_parse_eltorito_nofiles_hide(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-hide', 'boot.cat', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_nofiles_hide) def test_parse_eltorito_nofiles_hide_joliet(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-hide', 'boot.cat', '-hide-joliet', 'boot.cat', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_nofiles_hide) def test_parse_eltorito_nofiles_hide_joliet_only(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-hide-joliet', 'boot.cat', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_nofiles_hide_only) def test_parse_eltorito_nofiles_hide_iso_only_joliet(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-J', '-hide', 'boot.cat', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_and_eltorito_nofiles_hide_iso_only) def test_parse_hard_link_reshuffle(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') os.link(os.path.join(str(indir), 'foo'), os.path.join(str(indir), 'bar')) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_hard_link_reshuffle) def test_parse_open_invalid_pvd_ident(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+5) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Valid ISO9660 filesystems must have at least one PVD') def test_parse_open_invalid_pvd_version(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+6) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid volume descriptor version 2') def test_parse_open_invalid_pvd_unused1(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+7) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'PVD flags field is not zero') def test_parse_open_invalid_pvd_unused2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+72) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'data in 2nd unused field not zero') def test_parse_open_invalid_pvd_unused4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+882) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'data in 2nd unused field not zero') def test_parse_open_invalid_pvd_unused5(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the last # byte of the PVD. According to Ecma-119 this is invalid, but we have # seen ISOs in the wild where there is something other than 0 here, so # we allow it. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)-1) fp.write(b'\x02') iso = pycdlib.PyCdlib() iso.open(str(outfile)) iso.close() def test_parse_invalid_pvd_space_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+84) fp.write(b'\x00\x00\x00\x00') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian space size disagree') def test_parse_invalid_pvd_set_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+122) fp.write(b'\x00\x44') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian set size disagree') def test_parse_invalid_pvd_seqnum_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+126) fp.write(b'\x00\x44') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian seqnum disagree') def test_parse_invalid_pvd_lb_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+130) fp.write(b'\x00\x01') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian logical block size disagree') def test_parse_invalid_pvd_ptr_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((16*2048)+136) fp.write(b'\x00\x01\x00\x00') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian path table size disagree') def test_parse_open_invalid_vdst_ident(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+5) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Valid ISO9660 filesystems must have at least one Volume Descriptor Set Terminator') def test_parse_open_invalid_vdst_version(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+6) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid VDST version') def test_parse_invalid_br_ident(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+5) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) # The error is a bit odd, but correct; since we can't identify the record, # we have to assume it is the end of volume descriptors (there is no other # definitive marker). We don't find out that this was a problem until we # see that we didn't parse the VDST. assert(str(excinfo.value) == 'Valid ISO9660 filesystems must have at least one Volume Descriptor Set Terminator') def test_parse_invalid_br_version(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+6) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid boot record version') def test_parse_open_invalid_svd_ident(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+5) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) # The error is a bit odd, but correct; since we can't identify the record, # we have to assume it is the end of volume descriptors (there is no other # definitive marker). We don't find out that this was a problem until we # see that we didn't parse the VDST. assert(str(excinfo.value) == 'Valid ISO9660 filesystems must have at least one Volume Descriptor Set Terminator') def test_parse_open_invalid_svd_version(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+6) fp.write(b'\x03') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid volume descriptor version 3') def test_parse_open_invalid_svd_unused1(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+72) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'data in 2nd unused field not zero') def test_parse_open_invalid_svd_file_structure_version(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+881) fp.write(b'\x03') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'File structure version expected to be 1') def test_parse_open_invalid_svd_unused2(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+882) fp.write(b'\x02') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'data in 2nd unused field not zero') def test_parse_invalid_svd_space_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+84) fp.write(b'\x00\x00\x00\x00') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian space size disagree') def test_parse_invalid_svd_set_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+122) fp.write(b'\x00\x44') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian set size disagree') def test_parse_invalid_svd_seqnum_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+126) fp.write(b'\x00\x44') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian seqnum disagree') def test_parse_invalid_svd_lb_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+130) fp.write(b'\x00\x01') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian logical block size disagree') def test_parse_invalid_svd_ptr_size_le_be_mismatch(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte. This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek((17*2048)+136) fp.write(b'\x00\x01\x00\x00') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian path table size disagree') def test_parse_iso_too_small(tmpdir): indir = tmpdir.mkdir('isotoosmall') outfile = str(indir)+'.iso' with open(outfile, 'wb') as outfp: outfp.write(b'\x00'*16*2048) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Failed to read entire volume descriptor') def test_parse_rr_deeper_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8') indir.mkdir('a1').mkdir('a2').mkdir('a3').mkdir('a4').mkdir('a5').mkdir('a6').mkdir('a7').mkdir('a8') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_deeper_dir) def test_parse_eltorito_boot_table_odd(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boo'*27) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-boot-info-table', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_boot_info_table_large_odd) def test_parse_joliet_large_directory(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietlargedirectory') outfile = str(indir)+'.iso' for i in range(1, 50): indir.mkdir('dir' + str(i)) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_large_directory) def test_parse_zero_byte_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('zerobytefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: pass with open(os.path.join(str(indir), 'bar'), 'wb') as outfp: outfp.write(b'bar\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_zero_byte_file) def test_parse_dirrecord_too_short(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('tooshort') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) with open(outfile, 'a+b') as editfp: os.ftruncate(editfp.fileno(), 47104) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid directory record') def test_parse_eltorito_hide_boot(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritohideboot') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-hide', 'boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_hide_boot) def test_parse_get_entry_joliet(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('getentryjoliet') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) fooentry = iso.get_entry('/foo', joliet=True) assert(len(fooentry.children) == 0) assert(fooentry.isdir == False) assert(fooentry.is_root == False) assert(fooentry.file_ident == 'foo'.encode('utf-16_be')) assert(fooentry.dr_len == 40) assert(fooentry.extent_location() == 30) assert(fooentry.file_flags == 0) assert(fooentry.get_data_length() == 4) iso.close() def test_parse_dirrecord_nonzero_pad(tmpdir): indir = tmpdir.mkdir('dirrecordnonzeropad') outfile = str(indir) + '.iso' for d in range(0, 53): indir.mkdir('dir%d' % d) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) with open(outfile, 'r+b') as changefp: changefp.seek(24*2048 - 1) changefp.write(b'\xff') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid padding on ISO') def test_parse_open_invalid_eltorito_header_id(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the El Torito # header ID (extent 25). This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(25*2048 + 0) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito Validation entry header ID not 1') def test_parse_open_invalid_eltorito_platform_id(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the El Torito # header ID (extent 25). This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(25*2048 + 1) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito Validation entry platform ID not valid') def test_parse_open_invalid_eltorito_first_key_byte(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the El Torito # header ID (extent 25). This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(25*2048 + 0x1e) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito Validation entry first keybyte not 0x55') def test_parse_open_invalid_eltorito_second_key_byte(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the El Torito # header ID (extent 25). This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(25*2048 + 0x1f) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito Validation entry second keybyte not 0xaa') def test_parse_open_invalid_eltorito_csum(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('modifyinplaceisolevel4onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the El Torito # header ID (extent 25). This should be enough to make an invalid ISO. with open(str(outfile), 'r+b') as fp: fp.seek(25*2048 + 0x1c) fp.write(b'\x00') fp.write(b'\x00') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito Validation entry checksum not correct') def test_parse_hidden_file(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'aaaaaaaa'), 'wb') as outfp: outfp.write(b'aa\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-hidden', 'aaaaaaaa', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_hidden_file) def test_parse_hidden_dir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-hidden', 'dir1', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_hidden_dir) def test_parse_eltorito_bad_boot_indicator(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we have the ISO, perturb the initial entry with open(str(outfile), 'r+b') as fp: fp.seek(25*2048+32) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid El Torito initial entry boot indicator') def test_parse_eltorito_bad_boot_media(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we have the ISO, perturb the initial entry with open(str(outfile), 'r+b') as fp: fp.seek(25*2048+33) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Invalid El Torito boot media type') def test_parse_eltorito_bad_unused(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) # Now that we have the ISO, perturb the initial entry with open(str(outfile), 'r+b') as fp: fp.seek(25*2048+37) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'El Torito unused field must be 0') def test_parse_eltorito_hd_emul(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-hard-disk-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_hd_emul) def test_parse_eltorito_hd_emul_not_bootable(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'\x00'*446 + b'\x00\x01\x01\x00\x02\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x55' + b'\xaa') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-hard-disk-boot', '-no-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_hd_emul_not_bootable) def test_parse_eltorito_floppy12(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'\x00'*(2400*512)) # If you don't pass -hard-disk-boot or -no-emul-boot to genisoimage, # it assumes floppy. subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_floppy12) def test_parse_eltorito_floppy144(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'\x00'*(2880*512)) # If you don't pass -hard-disk-boot or -no-emul-boot to genisoimage, # it assumes floppy. subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_floppy144) def test_parse_eltorito_floppy288(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'\x00'*(5760*512)) # If you don't pass -hard-disk-boot or -no-emul-boot to genisoimage, # it assumes floppy. subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_floppy288) def test_parse_ptr_le_and_be_disagree(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte of the Big Endian PTR. This should make open_fp() fail. with open(str(outfile), 'r+b') as fp: fp.seek(21*2048) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Little-endian and big-endian path table records do not agree') def test_parse_joliet_ptr_le_and_be_disagree(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and perturb the first # byte of the Joliet Big Endian PTR. This should make open_fp() fail. with open(str(outfile), 'r+b') as fp: fp.seek(24*2048) fp.write(b'\xF4') iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: iso.open(str(outfile)) assert(str(excinfo.value) == 'Joliet little-endian and big-endian path table records do not agree') def test_parse_add_file_with_semicolon(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'FOO;1'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-relaxed-filenames', '-o', str(outfile), str(indir)]) # Now open up the ISO with pycdlib and check some things out. iso = pycdlib.PyCdlib() iso.open(str(outfile)) do_a_test(tmpdir, outfile, check_onefile_with_semicolon) iso.close() def test_parse_bad_eltorito_ident(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+') as outfp: # Change the EL TORITO SPECIFICATION string to be something else outfp.seek(17*2048+7) outfp.write('Z') do_a_test(tmpdir, outfile, check_bad_eltorito_ident) def test_parse_duplicate_rrmoved_name(tmpdir): iso = pycdlib.PyCdlib() iso.new(rock_ridge='1.09') # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' fdir = indir.mkdir('A').mkdir('B').mkdir('C').mkdir('D').mkdir('E').mkdir('F') fdir.mkdir('G').mkdir('1') fdir.mkdir('H').mkdir('1') with open(os.path.join(str(indir), 'A', 'B', 'C', 'D', 'E', 'F', 'G', '1', 'first'), 'wb') as outfp: outfp.write(b'first\n') with open(os.path.join(str(indir), 'A', 'B', 'C', 'D', 'E', 'F', 'H', '1', 'second'), 'wb') as outfp: outfp.write(b'second\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_two_dirs_same_level) def test_parse_eltorito_rr_verylongname(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrverylongname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'a'*RR_MAX_FILENAME_LENGTH, '-b', 'boot', '-no-emul-boot', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_rr_verylongname) @pytest.mark.skipif(find_executable('isohybrid') is None, reason='syslinux not installed') def test_parse_isohybrid_file_before(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('isohybrid') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'isolinux.bin'), 'wb') as outfp: outfp.seek(0x40) outfp.write(b'\xfb\xc0\x78\x70') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-o', str(outfile), str(indir)]) subprocess.call(['isohybrid', '-v', str(outfile)]) do_a_test(tmpdir, outfile, check_isohybrid_file_before) def test_parse_eltorito_rr_joliet_verylongname(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrjolietverylongname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'a'*RR_MAX_FILENAME_LENGTH, '-b', 'boot', '-no-emul-boot', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_rr_joliet_verylongname) def test_parse_joliet_dirs_overflow_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietmanydirs') outfile = str(indir)+'.iso' numdirs = 216 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_dirs_overflow_ptr_extent) def test_parse_joliet_dirs_just_short_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietjustshortdirs') outfile = str(indir)+'.iso' numdirs = 215 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_dirs_just_short_ptr_extent) def test_parse_joliet_dirs_add_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietjustshortdirs') outfile = str(indir)+'.iso' numdirs = 295 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_dirs_add_ptr_extent) def test_parse_joliet_dirs_rm_ptr_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietjustshortdirs') outfile = str(indir)+'.iso' numdirs = 293 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_dirs_rm_ptr_extent) def test_parse_long_directory_name(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('longdirectoryname') outfile = str(indir)+'.iso' indir.mkdir('directory1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '3', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_long_directory_name) def test_parse_long_file_name(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('longdirectoryname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foobarbaz1'), 'wb') as outfp: outfp.write(b'foobarbaz1\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '3', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_long_file_name) def test_parse_overflow_root_dir_record(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('overflowrootdirrecord') outfile = str(indir)+'.iso' for letter in ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o'): with open(os.path.join(str(indir), letter*20), 'wb') as outfp: outfp.write(b'\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_overflow_root_dir_record) def test_parse_duplicate_deep_dir(tmpdir): indir = tmpdir.mkdir('duplicatedeepdir') outfile = str(indir)+'.iso' get = indir.mkdir('books').mkdir('lkhg').mkdir('HyperNews').mkdir('get') get.mkdir('fs').mkdir('fs').mkdir('1') khg = get.mkdir('khg') khg.mkdir('1') khg.mkdir('117').mkdir('1').mkdir('1').mkdir('1').mkdir('1') khg.mkdir('35').mkdir('1').mkdir('1') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_duplicate_deep_dir) def test_parse_no_joliet_name(tmpdir): indir = tmpdir.mkdir('nojolietname') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-hide-joliet', 'foo', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_onefile_joliet_no_file) def test_parse_joliet_isolevel4_nofiles(tmpdir): indir = tmpdir.mkdir('jolietisolevel4nofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-J', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_isolevel4_nofiles) def test_parse_deep_rr_symlink(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('sevendeepdirs') outfile = str(indir)+'.iso' dir7 = indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7') pwd = os.getcwd() os.chdir(str(dir7)) os.symlink('/usr/share/foo', 'sym') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_deep_rr_symlink) def test_parse_rr_deep_weird_layout(tmpdir): indir = tmpdir.mkdir('rrdeepweird') outfile = str(indir) + '.iso' absimp = indir.mkdir('astroid').mkdir('astroid').mkdir('tests').mkdir('testdata').mkdir('python3').mkdir('data').mkdir('absimp') sidepackage = absimp.mkdir('sidepackage') with open(os.path.join(str(absimp), 'string.py'), 'wb') as outfp: outfp.write(b'from __future__ import absolute_import, print_functino\nimport string\nprint(string)\n') with open(os.path.join(str(sidepackage), '__init__.py'), 'wb') as outfp: outfp.write(b'"""a side package with nothing in it\n"""\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_deep_weird_layout) def test_parse_rr_long_dir_name(tmpdir): indir = tmpdir.mkdir('rrlongdirname') outfile = str(indir) + '.iso' indir.mkdir('a'*RR_MAX_FILENAME_LENGTH) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_long_dir_name) def test_parse_rr_hidden_relocated(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7').mkdir('dir8').mkdir('dir9') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'dir8', 'dir9', 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-rational-rock', '-hide-rr-moved', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_rr_relocated_hidden) def test_parse_open_fp_not_binary(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('rrdeep') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: with open(str(outfile), 'r') as infp: iso.open_fp(infp) assert(str(excinfo.value) == "The file to open must be in binary mode (add 'b' to the open flags)") def test_parse_open_too_small_pvd(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('toosmallpvd') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and shrink the volume # size to be too small. PyCdlib should fix it at open time. with open(str(outfile), 'r+b') as fp: fp.seek(16*2048+0x50) fp.write(b'\x16\x00\x00\x00\x00\x00\x00\x16') do_a_test(tmpdir, outfile, check_onefile) def test_parse_open_too_small_joliet(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('toosmalljoliet') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-J', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and shrink the volume # size to be too small. PyCdlib should fix it at open time. with open(str(outfile), 'r+b') as fp: fp.seek(16*2048+0x50) fp.write(b'\x16\x00\x00\x00\x00\x00\x00\x16') do_a_test(tmpdir, outfile, check_joliet_onefile) def test_parse_open_too_small_isolevel4(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('toosmallisolevel4') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '4', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and shrink the volume # size to be too small. PyCdlib should fix it at open time. with open(str(outfile), 'r+b') as fp: fp.seek(16*2048+0x50) fp.write(b'\x16\x00\x00\x00\x00\x00\x00\x16') do_a_test(tmpdir, outfile, check_isolevel4_onefile) def test_parse_open_larger_than_iso(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('largerthaniso') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) # Now that we've made a valid ISO, we open it up and make the file too # large. PyCdlib should fix it at open time. with open(str(outfile), 'r+b') as fp: fp.seek(23*2048+0x4e) fp.write(b'\x01\x08\x00\x00\x00\x00\x08\x01') do_a_test(tmpdir, outfile, check_onefile_toolong) def test_parse_pvd_zero_datetime(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('zerodatetimeiso') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 813) fp.write(b'\x00'*17) do_a_test(tmpdir, outfile, check_pvd_zero_datetime) def test_parse_pvd_zero_digit_datetime(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('zerodatetimeiso') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 813) fp.write(b'0'*17) do_a_test(tmpdir, outfile, check_pvd_zero_datetime) def test_parse_pvd_zero_digit_datetime_zero_tz(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('zerodatetimeiso') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 813) fp.write(b'0'*16) fp.write(b'\x00') do_a_test(tmpdir, outfile, check_pvd_zero_datetime) def test_parse_pvd_invalid_year(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('zerodatetimeiso') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 813) fp.write(b'0'*4) do_a_test(tmpdir, outfile, check_pvd_zero_datetime) def test_parse_bad_root_dir_ident(tmpdir): indir = tmpdir.mkdir('badrootdirident') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 156 + 33) fp.write(b'\x01') do_a_test(tmpdir, outfile, check_nofiles) def test_parse_bad_file_structure_version(tmpdir): indir = tmpdir.mkdir('badfilestructureversion') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-o', str(outfile), str(indir)]) with open(str(outfile), 'r+b') as fp: fp.seek(16*2048 + 881) fp.write(b'\x02') do_a_test(tmpdir, outfile, check_nofiles) def test_parse_get_file_from_iso_not_initialized(tmpdir): iso = pycdlib.PyCdlib() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: iso.get_file_from_iso('junk') assert(str(excinfo.value) == 'This object is not initialized; call either open() or new() to create an ISO') def test_parse_get_file_from_iso(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) iso = pycdlib.PyCdlib() iso.open(str(outfile)) foofile = os.path.join(str(indir), 'foo') iso.get_file_from_iso(foofile, iso_path='/FOO.;1') iso.close() with open(foofile, 'r') as infp: assert(infp.read() == 'foo\n') def test_parse_joliet_encoded_system_identifier(tmpdir): indir = tmpdir.mkdir('jolietsysident') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'user-data'), 'wb') as outfp: outfp.write(b'''\ #cloud-config password: password chpasswd: { expire: False } ssh_pwauth: True ''') with open(os.path.join(str(indir), 'meta-data'), 'wb') as outfp: outfp.write(b'''\ local-hostname: cloudimg ''') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '4', '-J', '-rational-rock', '-sysid', 'LINUX', '-volid', 'cidata', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_ident_encoding) def test_parse_joliet_hidden_iso_file(tmpdir): indir = tmpdir.mkdir('joliethiddeniso') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-J', '-hide', 'foo', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_hidden_iso_file) def test_parse_udf_nofiles(tmpdir): indir = tmpdir.mkdir('udfnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_nofiles) def test_parse_udf_onedir(tmpdir): indir = tmpdir.mkdir('udfonedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_onedir) def test_parse_udf_twodirs(tmpdir): indir = tmpdir.mkdir('udftwodirs') outfile = str(indir)+'.iso' indir.mkdir('dir1') indir.mkdir('dir2') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_twodirs) def test_parse_udf_subdir(tmpdir): indir = tmpdir.mkdir('udfsubdir') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('subdir1') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_subdir) def test_parse_udf_subdir_odd(tmpdir): indir = tmpdir.mkdir('udfsubdir') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('subdi1') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_subdir_odd) def test_parse_udf_onefile(tmpdir): indir = tmpdir.mkdir('udfonefile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_onefile) def test_parse_udf_onefileonedir(tmpdir): indir = tmpdir.mkdir('udfonefileonedir') outfile = str(indir)+'.iso' indir.mkdir('dir1') with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_onefileonedir) def test_parse_udf_dir_spillover(tmpdir): indir = tmpdir.mkdir('udfdirspillover') outfile = str(indir)+'.iso' for i in range(ord('a'), ord('v')): dirname = chr(i) * 64 indir.mkdir(dirname) subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_dir_spillover) def test_parse_udf_dir_oneshort(tmpdir): indir = tmpdir.mkdir('udfdironeshort') outfile = str(indir)+'.iso' for i in range(ord('a'), ord('u')): dirname = chr(i) * 64 indir.mkdir(dirname) subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_dir_oneshort) def test_parse_udf_iso_hidden(tmpdir): indir = tmpdir.mkdir('udfisohidden') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-hide', 'foo', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_iso_hidden) @pytest.mark.slow def test_parse_udf_very_large(tmpdir): indir = tmpdir.mkdir('udfverylarge') outfile = str(indir)+'.iso' largefile = os.path.join(str(indir), 'foo') with open(largefile, 'wb') as outfp: outfp.truncate(1073739776+1) subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_very_large) def test_parse_joliet_udf_nofiles(tmpdir): indir = tmpdir.mkdir('jolietudfnofiles') outfile = str(indir)+'.iso' subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-J', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_joliet_udf_nofiles) def test_parse_udf_dir_exactly2048(tmpdir): indir = tmpdir.mkdir('udfdirspillover') outfile = str(indir)+'.iso' indir.mkdir('a' * 248) indir.mkdir('b' * 248) indir.mkdir('c' * 248) indir.mkdir('d' * 248) indir.mkdir('e' * 248) indir.mkdir('f' * 248) indir.mkdir('g' * 240) subprocess.call(['genisoimage', '-v', '-v', '-no-pad', '-iso-level', '1', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_dir_exactly2048) def test_parse_udf_overflow_dir_extent(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('udfoverflow') outfile = str(indir)+'.iso' numdirs = 46 for i in range(1, 1+numdirs): indir.mkdir('dir%d' % i) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_overflow_dir_extent) def test_parse_multi_hard_link(tmpdir): indir = tmpdir.mkdir('jolietudfnofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') pwd = os.getcwd() os.chdir(str(indir)) os.link('foo', 'bar') os.link('bar', 'baz') os.chdir(pwd) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-cache-inodes', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_multi_hard_link) def test_parse_udf_joliet_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('jolietfile') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-J', '-udf', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_udf_joliet_onefile) def test_parse_zero_byte_hard_link(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('boottable') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: pass os.link(os.path.join(str(indir), 'foo'), os.path.join(str(indir), 'bar')) subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_zero_byte_hard_link) def test_parse_unicode_name(tmpdir): indir = tmpdir.mkdir('unicode') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'föo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name) def test_parse_unicode_name_isolevel4(tmpdir): indir = tmpdir.mkdir('unicodeisolevel4') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'föo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_isolevel4) def test_parse_unicode_name_joliet(tmpdir): indir = tmpdir.mkdir('unicodejoliet') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'föo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-J', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_joliet) def test_parse_unicode_name_udf(tmpdir): indir = tmpdir.mkdir('unicodeudf') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'föo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-udf', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_udf) def test_parse_unicode_name_two_byte(tmpdir): indir = tmpdir.mkdir('unicode') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'fᴔo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_two_byte) def test_parse_unicode_name_two_byte(tmpdir): indir = tmpdir.mkdir('unicode') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'fᴔo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_two_byte) def test_parse_unicode_name_two_byte_isolevel4(tmpdir): indir = tmpdir.mkdir('unicodeisolevel4') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'fᴔo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_two_byte_isolevel4) def test_parse_unicode_name_two_byte_joliet(tmpdir): indir = tmpdir.mkdir('unicodetwobytejoliet') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'fᴔo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-J', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_two_byte_joliet) def test_parse_unicode_name_two_byte_udf(tmpdir): indir = tmpdir.mkdir('unicodeudftwobyte') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'fᴔo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-udf', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_unicode_name_two_byte_udf) def test_parse_eltorito_get_bootcat(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritogetbootcat') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-b', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_get_bootcat) def test_parse_eltorito_uefi(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('eltoritonofiles') outfile = str(indir)+'.iso' with open(os.path.join(str(indir), 'boot'), 'wb') as outfp: outfp.write(b'boot\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '1', '-no-pad', '-c', 'boot.cat', '-e', 'boot', '-no-emul-boot', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_eltorito_uefi) def test_parse_isolevel4_deep_directory(tmpdir): indir = tmpdir.mkdir('isolevel4deep') outfile = str(indir)+'.iso' indir.mkdir('dir1').mkdir('dir2').mkdir('dir3').mkdir('dir4').mkdir('dir5').mkdir('dir6').mkdir('dir7') with open(os.path.join(str(indir), 'dir1', 'dir2', 'dir3', 'dir4', 'dir5', 'dir6', 'dir7', 'foo'), 'wb') as outfp: outfp.write(b'foo\n') subprocess.call(['genisoimage', '-v', '-v', '-iso-level', '4', '-no-pad', '-o', str(outfile), str(indir)]) do_a_test(tmpdir, outfile, check_isolevel4_deep_directory) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0734355 pycdlib-1.11.0/tests/tools/0000775000175000017500000000000000000000000020305 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6611116 pycdlib-1.11.0/tests/tools/test_pycdlib_genisoimage.py0000664000175000017500000001626700000000000025727 0ustar00clalancetteclalancette00000000000000import os import subprocess import pytest pycdlib_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) pycdlib_exe = os.path.join(pycdlib_root, 'tools', 'pycdlib-genisoimage') def find_executable(executable): paths = os.environ['PATH'].split(os.pathsep) if os.path.isfile(executable): return executable else: for p in paths: f = os.path.join(p, executable) if os.path.isfile(f): return f return None class ProcessException(Exception): def __init__(self, msg): Exception.__init__(self, msg) def run_process(cmdline): if not 'LD_LIBRARY_PATH' in os.environ: os.environ['LD_LIBRARY_PATH'] = '' process = subprocess.Popen(cmdline, env={ 'LD_LIBRARY_PATH': os.environ['LD_LIBRARY_PATH'], 'PATH': os.environ['PATH'], 'PYTHONPATH': pycdlib_root, }, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = process.communicate() ret = process.wait() if ret != 0: raise ProcessException('Process failed: %s\n%s' % (out, err)) return out, err @pytest.mark.skipif(find_executable('isosize') is None or find_executable('isovfy') is None, reason='isosize not installed') def test_pycdlib_genisoimage_nofiles(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('nofiles') outfile = str(indir) + '.iso' def _do_test(binary): run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) run_process(['isovfy', str(outfile)]) out, err = run_process(['isosize', str(outfile)]) size = int(out.strip()) assert(size == 49152) _do_test('genisoimage') _do_test(pycdlib_exe) @pytest.mark.skipif(find_executable('isosize') is None or find_executable('isovfy') is None or find_executable('iso-read') is None, reason='isosize not installed') def test_pycdlib_genisoimage_onedir(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir) + '.iso' dir1 = indir.mkdir('dir1') with open(os.path.join(str(dir1), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') def _do_test(binary): run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) run_process(['isovfy', str(outfile)]) out, err = run_process(['isosize', str(outfile)]) genisoimage_size = int(out.strip()) assert(genisoimage_size == 53248) foocheck = os.path.join(str(tmpdir), 'foocheck') out, err = run_process(['iso-read', '-i', str(outfile), '-e', 'dir1/foo', '-o', foocheck]) with open(foocheck, 'rb') as infp: assert(infp.read() == b'foo\n') _do_test('genisoimage') _do_test(pycdlib_exe) @pytest.mark.skipif(find_executable('isosize') is None or find_executable('isovfy') is None or find_executable('iso-read') is None, reason='isosize not installed') def test_pycdlib_genisoimage_onefile(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir) + '.iso' with open(os.path.join(str(indir), 'foo'), 'wb') as outfp: outfp.write(b'foo\n') def _do_test(binary): run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), str(indir)]) run_process(['isovfy', str(outfile)]) out, err = run_process(['isosize', str(outfile)]) genisoimage_size = int(out.strip()) assert(genisoimage_size == 51200) foocheck = os.path.join(str(tmpdir), 'foocheck') out, err = run_process(['iso-read', '-i', str(outfile), '-e', 'foo', '-o', foocheck]) with open(foocheck, 'rb') as infp: assert(infp.read() == b'foo\n') _do_test('genisoimage') _do_test(pycdlib_exe) @pytest.mark.skipif(find_executable('isosize') is None or find_executable('isovfy') is None or find_executable('iso-read') is None, reason='isosize not installed') def test_pycdlib_genisoimage_file_cmdline(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir) + '.iso' foofile = os.path.join(str(indir), 'foo') with open(foofile, 'wb') as outfp: outfp.write(b'foo\n') def _do_test(binary): run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-o', str(outfile), foofile]) run_process(['isovfy', str(outfile)]) out, err = run_process(['isosize', str(outfile)]) genisoimage_size = int(out.strip()) assert(genisoimage_size == 51200) foocheck = os.path.join(str(tmpdir), 'foocheck') out, err = run_process(['iso-read', '-i', str(outfile), '-e', 'foo', '-o', foocheck]) with open(foocheck, 'rb') as infp: assert(infp.read() == b'foo\n') _do_test('genisoimage') _do_test(pycdlib_exe) @pytest.mark.skipif(find_executable('isosize') is None or find_executable('isovfy') is None or find_executable('iso-read') is None, reason='isosize not installed') def test_pycdlib_genisoimage_boot_file_cmdline(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir) + '.iso' bootfile = os.path.join(str(indir), 'boot') with open(bootfile, 'wb') as outfp: outfp.write(b'boot\n') def _do_test(binary): run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-o', str(outfile), bootfile]) run_process(['isovfy', str(outfile)]) out, err = run_process(['isosize', str(outfile)]) genisoimage_size = int(out.strip()) assert(genisoimage_size == 55296) bootcheck = os.path.join(str(tmpdir), 'bootcheck') out, err = run_process(['iso-read', '-i', str(outfile), '-e', 'boot', '-o', bootcheck]) with open(bootcheck, 'rb') as infp: assert(infp.read() == b'boot\n') _do_test('genisoimage') _do_test(pycdlib_exe) def test_pycdlib_genisoimage_bootfile_bad(tmpdir): # First set things up, and generate the ISO with genisoimage. indir = tmpdir.mkdir('onedir') outfile = str(indir) + '.iso' def _do_test(binary): try: run_process([binary, '-v', '-iso-level', '1', '-no-pad', '-b', 'boot', '-c', 'boot.cat', '-no-emul-boot', '-o', str(outfile), str(indir)]) except ProcessException as e: err1 = "Uh oh, I cant find the boot image 'boot'" in str(e) assert(err1) _do_test('genisoimage') _do_test(pycdlib_exe) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0744355 pycdlib-1.11.0/tests/unit/0000775000175000017500000000000000000000000020124 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5889952 pycdlib-1.11.0/tests/unit/test_dates.py0000664000175000017500000002003700000000000022637 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct import time prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.dates import pycdlib.pycdlibexception def test_string_to_timestruct_invalid_input_type(): if sys.version_info >= (3, 0): with pytest.raises(AttributeError) as exc_info: ts = pycdlib.dates.string_to_timestruct('') def test_string_to_timestruct_blank_bytes(): ts = pycdlib.dates.string_to_timestruct(b'') assert(ts.tm_year == 0) assert(ts.tm_mon == 0) assert(ts.tm_mday == 0) assert(ts.tm_hour == 0) assert(ts.tm_min == 0) assert(ts.tm_sec == 0) assert(ts.tm_wday == 0) assert(ts.tm_yday == 0) assert(ts.tm_isdst == 0) def test_string_to_timestruct(): ts = pycdlib.dates.string_to_timestruct(b'20180718212300') assert(ts.tm_year == 2018) assert(ts.tm_mon == 7) assert(ts.tm_mday == 18) assert(ts.tm_hour == 21) assert(ts.tm_min == 23) assert(ts.tm_sec == 0) assert(ts.tm_wday == 2) assert(ts.tm_yday == 199) assert(ts.tm_isdst == -1) def test_dirrecorddate_record_not_initialized(): drdate = pycdlib.dates.DirectoryRecordDate() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.record() assert(str(excinfo.value) == 'Directory Record Date not initialized') def test_dirrecorddate_new_after_new(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.new() assert(str(excinfo.value) == 'Directory Record Date already initialized') def test_dirrecorddate_parse_after_new(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.parse(b'') assert(str(excinfo.value) == 'Directory Record Date already initialized') def test_dirrecorddate_parse_after_parse(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.parse(b'\x76\x07\x12\x15\x21\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.parse(b'\x76\x07\x12\x15\x21\x00\x00') assert(str(excinfo.value) == 'Directory Record Date already initialized') def test_dirrecorddate_new_after_parse(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.parse(b'\x76\x07\x12\x15\x21\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.new() assert(str(excinfo.value) == 'Directory Record Date already initialized') def test_dirrecorddate_record_after_new(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.new() assert(len(drdate.record()) == 7) def test_dirrecorddate_compare_equal(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.new() drdate2 = drdate assert(not(drdate2 != drdate)) def test_dirrecorddate_compare_not_equal(): drdate = pycdlib.dates.DirectoryRecordDate() drdate.new() time.sleep(1) drdate2 = pycdlib.dates.DirectoryRecordDate() drdate2.new() assert(drdate2 != drdate) def test_volumedescdate_record_not_initialized(): voldate = pycdlib.dates.VolumeDescriptorDate() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: voldate.record() assert(str(excinfo.value) == 'This Volume Descriptor Date is not initialized') def test_volumedescdate_parse_invalid_string(): voldate = pycdlib.dates.VolumeDescriptorDate() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: voldate.parse(b'') assert(str(excinfo.value) == 'Invalid ISO9660 date string') def test_volumedescdate_new_after_new(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.new() assert(str(excinfo.value) == 'This Volume Descriptor Date object is already initialized') def test_volumedescdate_parse_after_new(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.parse(b'') assert(str(excinfo.value) == 'This Volume Descriptor Date object is already initialized') def test_volumedescdate_parse_after_parse(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'This Volume Descriptor Date object is already initialized') def test_volumedescdate_new_after_parse(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: drdate.new() assert(str(excinfo.value) == 'This Volume Descriptor Date object is already initialized') def test_volumedescdate_parse_zero(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(drdate.year == 0) assert(drdate.month == 0) assert(drdate.dayofmonth == 0) assert(drdate.hour == 0) assert(drdate.minute == 0) assert(drdate.second == 0) assert(drdate.hundredthsofsecond == 0) assert(drdate.gmtoffset == 0) assert(drdate.date_str == b'0000000000000000' + b'\x00') def test_volumedescdate_parse_nonzero(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'2019010721250000\x00') assert(drdate.year == 2019) assert(drdate.month == 1) assert(drdate.dayofmonth == 7) assert(drdate.hour == 21) assert(drdate.minute == 25) assert(drdate.second == 0) assert(drdate.hundredthsofsecond == 0) assert(drdate.gmtoffset == 0) def test_volumedescdate_parse_hundredths(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'2019010721250005\x00') assert(drdate.year == 2019) assert(drdate.month == 1) assert(drdate.dayofmonth == 7) assert(drdate.hour == 21) assert(drdate.minute == 25) assert(drdate.second == 0) assert(drdate.hundredthsofsecond == 5) assert(drdate.gmtoffset == 0) def test_volumedescdate_record_after_parse(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.parse(b'2019010721250000\x00') rec = drdate.record() assert(rec == b'2019010721250000\x00') def save_and_set_tz(newtz): if 'TZ' in os.environ: oldtz = os.environ['TZ'] else: oldtz = None os.environ['TZ'] = newtz time.tzset() return oldtz def restore_tz(oldtz): if oldtz is not None: os.environ['TZ'] = oldtz else: del os.environ['TZ'] time.tzset() def test_volumedescdate_new_nonzero(): oldtz = save_and_set_tz('US/Eastern') test_seconds_since_epoch = 1546914300.0 drdate = pycdlib.dates.VolumeDescriptorDate() drdate.new(test_seconds_since_epoch) assert(drdate.year == 2019) assert(drdate.month == 1) assert(drdate.dayofmonth == 7) assert(drdate.hour == 21) assert(drdate.minute == 25) assert(drdate.second == 0) assert(drdate.hundredthsofsecond == 0) restore_tz(oldtz) def test_volumedescdate_test_equal(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.new(1546914300.0) drdate2 = pycdlib.dates.VolumeDescriptorDate() drdate2.new(1546914300.0) assert(not(drdate != drdate2)) def test_volumedescdate_test_not_equal(): drdate = pycdlib.dates.VolumeDescriptorDate() drdate.new(1546914300.0) drdate2 = pycdlib.dates.VolumeDescriptorDate() drdate2.new(1546914200.0) assert(drdate != drdate2) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1600133947.17298 pycdlib-1.11.0/tests/unit/test_dr.py0000664000175000017500000003636200000000000022154 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.dr # XA def test_xa_parse_initialized_twice(): xa = pycdlib.dr.XARecord() xa.parse(b'\x00\x00\x00\x00\x00\x00\x58\x41\x00\x00\x00\x00\x00\x00', 1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: xa.parse(b'\x00\x00\x00\x00\x00\x00\x58\x41\x00\x00\x00\x00\x00\x00', 1) assert(str(excinfo.value) == 'This XARecord is already initialized') def test_xa_parse_bad_reserved(): xa = pycdlib.dr.XARecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: xa.parse(b'\x00\x00\x00\x00\x00\x00\x58\x41\x00\x00\x00\x00\x00\x01', 1) assert(str(excinfo.value) == 'Unused fields should be 0') def test_xa_parse_padding(): xa = pycdlib.dr.XARecord() # Test out the case when there is a bit of padding at the front of the # XA Record, as there are for Windows 98 SE ISOs. xa.parse(b'\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x58\x41\x00\x00\x00\x00\x00\x00', 4) assert(xa._initialized) assert(xa._pad_size == 4) def test_xa_new_initialized_twice(): xa = pycdlib.dr.XARecord() xa.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: xa.new() assert(str(excinfo.value) == 'This XARecord is already initialized') def test_xa_record_not_initialized(): xa = pycdlib.dr.XARecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: xa.record() assert(str(excinfo.value) == 'This XARecord is not initialized') # DR def test_dr_parse_initialized_twice(): dr = pycdlib.dr.DirectoryRecord() dr.parse(None, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*7 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00', None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.parse(None, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*7 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00', None) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_bad_record_length(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dr.parse(None, b'\x00' * 256, None) assert(str(excinfo.value) == 'Directory record longer than 255 bytes!') def test_dr_bad_extent_location(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dr.parse(None, b'\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*7 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00', None) assert(str(excinfo.value) == 'Little-endian (1) and big-endian (2) extent location disagree') def test_dr_bad_seqnum(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dr.parse(None, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*7 + b'\x00\x00\x00\x01\x00\x00\x02\x00\x00', None) assert(str(excinfo.value) == 'Little-endian and big-endian seqnum disagree') def test_dr_bad_rr_parent(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.parse(None, b'\x22\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00\x78\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x00', None) dotdot_dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dotdot_dr.parse(None, b'\x66\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00\x78\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x01\x52\x52\x05\x01\x81\x50\x58\x24\x01\x6d\x41\x00\x00\x00\x00\x41\x6d\x02\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54\x46\x1a\x01\x0e\x78\x09\x0d\x0d\x07\x15\xf0\x78\x09\x0d\x0d\x07\x26\xf0\x78\x09\x0d\x0d\x07\x15\xf0\x00', root_dr) assert(str(excinfo.value) == 'Parent has no dot child') def test_dr_rr_dot_no_rr(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.parse(None, b'\x22\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00\x78\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x00', None) dot_dr = pycdlib.dr.DirectoryRecord() dot_dr.parse(None, b'\x22\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00x\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x00', root_dr) root_dr.track_child(dot_dr, 2048) dotdot_dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dotdot_dr.parse(None, b'\x66\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00\x78\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x01\x52\x52\x05\x01\x81\x50\x58\x24\x01\x6d\x41\x00\x00\x00\x00\x41\x6d\x02\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x54\x46\x1a\x01\x0e\x78\x09\x0d\x0d\x07\x15\xf0\x78\x09\x0d\x0d\x07\x26\xf0\x78\x09\x0d\x0d\x07\x15\xf0\x00', root_dr) assert(str(excinfo.value) == 'Dot child does not have Rock Ridge; ISO is corrupt') def test_dr_rr_dir_no_rr(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.parse(None, b'\x22\x00\x17\x00\x00\x00\x00\x00\x00\x17\x00\x08\x00\x00\x00\x00\x08\x00\x78\x09\x0d\x0d\x07\x15\xf0\x02\x00\x00\x01\x00\x00\x01\x01\x00', None) dir1_dr = pycdlib.dr.DirectoryRecord() dir1_dr.parse(None, b'\x26\x00\x18\x00\x00\x00\x00\x00\x00\x18\x00\x08\x00\x00\x00\x00\x08\x00x\x09\x0d\x0d\x18\x06\xf0\x02\x00\x00\x01\x00\x00\x01\x04DIR1\x00', root_dr) foo_dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: foo_dr.parse(None, b'\x74\x00\x1a\x00\x00\x00\x00\x00\x00\x1a\x04\x00\x00\x00\x00\x00\x00\x04\x78\x09\x0d\x0d\x18\x06\xf0\x00\x00\x00\x01\x00\x00\x01\x06FOO.;1\x00RR\x05\x01\x89NM\x08\x01\x00fooPX$\x01$\x81\x00\x00\x00\x00\x81$\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TF\x1a\x01\x0ex\t\r\r\x18\x06\xf0x\x09\x0d\x0d\x18\x0a\xf0x\x09\x0d\x0d\x18\x06\xf0\x00', dir1_dr) assert(str(excinfo.value) == 'Parent does not have Rock Ridge; ISO is corrupt') def test_dr_rr_new_on_root(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: root_dr._rr_new('1.09', b'', b'', False, False, False, 0) assert(str(excinfo.value) == 'Invalid call to create new Rock Ridge on root directory') def test_dr_new_dir_no_parent_rr(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.new_root(None, 1, 2048) dir1_dr = pycdlib.dr.DirectoryRecord() dir1_dr.new_dir(None, b'DIR1', root_dr, 1, '', b'', 2048, False, False, False, 0) dir2_dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dir2_dr.new_dir(None, b'DIR2', dir1_dr, 1, '1.09', b'', 2048, False, False, False, 0) assert(str(excinfo.value) == 'Parent of the entry did not have Rock Ridge, ISO is corrupt') def test_dr_rr_new_on_root(): root_dr = pycdlib.dr.DirectoryRecord() root_dr.new_root(None, 1, 2048) dir1_dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: dir1_dr.new_dir(None, b'DIR1', root_dr, 1, '1.09', b'', 2048, False, False, False, 0) assert(str(excinfo.value) == 'Expected at least 2 children of the root directory record, saw 0') def test_dr_new_symlink_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_symlink(None, b'', None, b'', 1, '', b'', False) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_new_file_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_file(None, 0, b'', None, 1, '', b'', False, 0) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_new_root_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_root(None, 1, 2048) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_new_dot_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_dot(None, None, 1, '', 2048, False, 0) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_new_dotdot_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_dotdot(None, None, 1, '', 2048, False, False, 0) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_new_dir_already_initialized(): dr = pycdlib.dr.DirectoryRecord() dr.new_root(None, 1, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.new_dir(None, b'', None, 1, '', b'', 2048, False, False, False, 0) assert(str(excinfo.value) == 'Directory Record already initialized') def test_dr_change_existence_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.change_existence(False) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_add_child_not_dir(): dr = pycdlib.dr.DirectoryRecord() dr.new_file(None, 0, b'', None, 1, '', b'', False, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: dr.add_child(None, 2048) assert(str(excinfo.value) == 'Trying to add a child to a record that is not a directory') def test_dr_add_child_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.add_child(None, 2048) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_track_child_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.track_child(None, 2048) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_remove_child_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.remove_child(None, 0, 2048) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_remove_child_negative_index(): dr = pycdlib.dr.DirectoryRecord() dr.new_file(None, 0, b'', None, 1, '', b'', False, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.remove_child(None, -1, 2048) assert(str(excinfo.value) == 'Invalid child index to remove') def test_dr_is_dir_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_dir() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_is_file_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_file() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_is_symlink_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_symlink() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_is_dot_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_dot() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_is_dotdot_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_dotdot() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_directory_record_length_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.directory_record_length() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_directory_record_length(): dr = pycdlib.dr.DirectoryRecord() dr.new_file(None, 0, b'', None, 1, '', b'', False, 0) assert(dr.directory_record_length() == 34) def test_dr_directory_extent_location_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.extent_location() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_directory_file_identifier_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.file_identifier() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_record_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.record() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_is_associated_file_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.is_associated_file() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_set_ptr_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.set_ptr(None) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_set_data_location_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.set_data_location(0, 0) assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_get_data_length_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.get_data_length() assert(str(excinfo.value) == 'Directory Record not initialized') def test_dr_set_data_length_not_initialized(): dr = pycdlib.dr.DirectoryRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: dr.set_data_length(0) assert(str(excinfo.value) == 'Directory Record not initialized') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600303902.1815667 pycdlib-1.11.0/tests/unit/test_eltorito.py0000664000175000017500000002727000000000000023406 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.eltorito import pycdlib.headervd import pycdlib.inode # BootInfoTable def test_eltorito_bit_parse_initialized_twice(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(pycdlib.headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY) pvd.new(0, b'', b'', 0, 0, 2048, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'') pvd.set_extent_location(16) ino = pycdlib.inode.Inode() ino.new(0, None, False, 0) ino.set_extent_location(17) bit = pycdlib.eltorito.EltoritoBootInfoTable() bit.parse(pvd, b'\x10\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ino) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bit.parse(pvd, b'\x10\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ino) assert(str(excinfo.value) == 'This Eltorito Boot Info Table is already initialized') def test_eltorito_bit_new_initialized_twice(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(pycdlib.headervd.VOLUME_DESCRIPTOR_TYPE_PRIMARY) pvd.new(0, b'', b'', 0, 0, 2048, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'') pvd.set_extent_location(16) ino = pycdlib.inode.Inode() ino.new(0, None, False, 0) ino.set_extent_location(17) bit = pycdlib.eltorito.EltoritoBootInfoTable() bit.new(pvd, ino, 0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bit.new(pvd, ino, 0, 0) assert(str(excinfo.value) == 'This Eltorito Boot Info Table is already initialized') def test_eltorito_bit_record_not_initialized(): bit = pycdlib.eltorito.EltoritoBootInfoTable() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bit.record() assert(str(excinfo.value) == 'This Eltorito Boot Info Table not initialized') # Validation Entry def test_eltorito_validation_entry_parse_initialized_twice(): val = pycdlib.eltorito.EltoritoValidationEntry() val.parse(b'\x01\x00\x00\x00' + b'\x00'*24 + b'\xaa\x55\x55\xaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: val.parse(b'\x01\x00\x00\x00' + b'\x00'*24 + b'\xaa\x55\x55\xaa') assert(str(excinfo.value) == 'El Torito Validation Entry already initialized') def test_eltorito_validation_entry_new_initialized_twice(): val = pycdlib.eltorito.EltoritoValidationEntry() val.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: val.new(0) assert(str(excinfo.value) == 'El Torito Validation Entry already initialized') def test_eltorito_validation_entry_record_not_initialized(): val = pycdlib.eltorito.EltoritoValidationEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: val.record() assert(str(excinfo.value) == 'El Torito Validation Entry not initialized') # Entry def test_eltorito_entry_parse_initialized_twice(): entry = pycdlib.eltorito.EltoritoEntry() entry.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*19) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*19) assert(str(excinfo.value) == 'El Torito Entry already initialized') def test_eltorito_entry_new_initialized_twice(): entry = pycdlib.eltorito.EltoritoEntry() entry.new(0, 0, 'noemul', 0, False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.new(0, 0, 'noemul', 0, False) assert(str(excinfo.value) == 'El Torito Entry already initialized') def test_eltorito_entry_new_invalid_media_name(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entry.new(0, 0, 'foo', 0, False) assert(str(excinfo.value) == "Invalid media name 'foo'") def test_eltorito_entry_get_rba_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.get_rba() assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_set_data_location_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_data_location(0, 0) assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_set_inode_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_inode(None) assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_record_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.record() assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_length_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.length() assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_set_data_length_not_initialized(): entry = pycdlib.eltorito.EltoritoEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_data_length(0) assert(str(excinfo.value) == 'El Torito Entry not initialized') def test_eltorito_entry_set_data_length(): entry = pycdlib.eltorito.EltoritoEntry() entry.new(0, 0, 'noemul', 0, False) entry.set_data_length(1) assert(entry.sector_count == 1) # Section Header def test_eltorito_section_header_parse_initialized_twice(): sh = pycdlib.eltorito.EltoritoSectionHeader() sh.parse(b'\x00\x00\x00\x00' + b'\x00'*28) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.parse(b'\x00\x00\x00\x00' + b'\x00'*28) assert(str(excinfo.value) == 'El Torito Section Header already initialized') def test_eltorito_section_header_new_initialized_twice(): sh = pycdlib.eltorito.EltoritoSectionHeader() sh.new(b'', 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.new(b'', 0) assert(str(excinfo.value) == 'El Torito Section Header already initialized') def test_eltorito_section_header_add_parsed_entry_not_initialized(): sh = pycdlib.eltorito.EltoritoSectionHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.add_parsed_entry(None) assert(str(excinfo.value) == 'El Torito Section Header not initialized') def test_eltorito_section_header_add_parsed_entry_too_many(): sh = pycdlib.eltorito.EltoritoSectionHeader() sh.new(b'', 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sh.add_parsed_entry(None) assert(str(excinfo.value) == 'El Torito section had more entries than expected by section header; ISO is corrupt') def test_eltorito_section_header_add_new_entry_not_initialized(): sh = pycdlib.eltorito.EltoritoSectionHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.add_new_entry(None) assert(str(excinfo.value) == 'El Torito Section Header not initialized') def test_eltorito_section_header_set_record_not_last_not_initialized(): sh = pycdlib.eltorito.EltoritoSectionHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.set_record_not_last() assert(str(excinfo.value) == 'El Torito Section Header not initialized') def test_eltorito_section_header_record_not_initialized(): sh = pycdlib.eltorito.EltoritoSectionHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sh.record() assert(str(excinfo.value) == 'El Torito Section Header not initialized') # Boot Catalog def test_eltorito_boot_catalog_parse_initialized_twice(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) bc.parse(b'\x01\x00\x00\x00' + b'\x00'*24 + b'\xaa\x55\x55\xaa') bc.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*19) bc.parse(b'\x00'*32) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.parse(b'\x01\x00\x00\x00' + b'\x00'*24 + b'\xaa\x55\x55\xaa') assert(str(excinfo.value) == 'El Torito Boot Catalog already initialized') def test_eltorito_boot_catalog_parse_invalid_entry(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) bc.parse(b'\x01\x00\x00\x00' + b'\x00'*24 + b'\xaa\x55\x55\xaa') bc.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*19) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: bc.parse(b'\x01'*32) assert(str(excinfo.value) == 'Invalid El Torito Boot Catalog entry') def test_eltorito_boot_catalog_new_initialized_twice(): ino = pycdlib.inode.Inode() ino.new(0, None, False, 0) bc = pycdlib.eltorito.EltoritoBootCatalog(None) bc.new(None, ino, 1, 0, 'noemul', 0, 0, False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.new(None, ino, 1, 0, 'noemul', 0, 0, False) assert(str(excinfo.value) == 'El Torito Boot Catalog already initialized') def test_eltorito_boot_catalog_add_section_not_initialized(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.add_section(None, 0, 0, 'noemul', 0, False, False) assert(str(excinfo.value) == 'El Torito Boot Catalog not initialized') def test_eltorito_boot_catalog_add_section_too_many(): ino = pycdlib.inode.Inode() ino.new(0, None, False, 0) bc = pycdlib.eltorito.EltoritoBootCatalog(None) bc.new(None, ino, 1, 0, 'noemul', 0, 0, False) for i in range(0, 31): bc.add_section(ino, 0, 0, 'noemul', 0, False, False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: bc.add_section(ino, 0, 0, 'noemul', 0, False, False) assert(str(excinfo.value) == 'Too many El Torito sections') def test_eltorito_boot_catalog_record_not_initialized(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.record() assert(str(excinfo.value) == 'El Torito Boot Catalog not initialized') def test_eltorito_boot_catalog_add_dirrecord_not_initialized(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.add_dirrecord(None) assert(str(excinfo.value) == 'El Torito Boot Catalog not initialized') def test_eltorito_boot_catalog_add_extent_location_not_initialized(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.extent_location() assert(str(excinfo.value) == 'El Torito Boot Catalog not initialized') def test_eltorito_boot_catalog_add_extent_location_not_initialized(): bc = pycdlib.eltorito.EltoritoBootCatalog(None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bc.update_catalog_extent(0) assert(str(excinfo.value) == 'El Torito Boot Catalog not initialized') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600482436.2923381 pycdlib-1.11.0/tests/unit/test_headervd.py0000664000175000017500000003777000000000000023335 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.headervd # PrimaryOrSupplementaryVolumeDescriptor def test_pvd_parse_initialized_twice(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) pvd.parse(b'\x01CD001\x01' + b'\x00'*2041, 16) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.parse(b'\x01CD001\x01' + b'\x00'*2041, 16) assert(str(excinfo.value) == 'This Primary Volume Descriptor is already initialized') def test_pvd_parse_invalid_vd_type(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*2048, 16) assert(str(excinfo.value) == 'Invalid volume descriptor') def test_pvd_parse_invalid_identifier(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x01CD002' + b'\x00'*2042, 16) assert(str(excinfo.value) == 'invalid CD isoIdentification') def test_pvd_new_initialized_twice(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) pvd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'') assert(str(excinfo.value) == 'This Primary Volume Descriptor is already initialized') def test_pvd_new_pvd_invalid_flags(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: pvd.new(1, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'') assert(str(excinfo.value) == 'Non-zero flags not allowed for a PVD') def test_pvd_new_pvd_invalid_escape_sequence(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: pvd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 1, b'\x00') assert(str(excinfo.value) == 'Non-empty escape sequence not allowed for a PVD') def test_pvd_new_pvd_invalid_version(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(1) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: pvd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 2, b'') assert(str(excinfo.value) == 'Only version 1 supported for a PVD') def test_pvd_new_svd_invalid_version(): svd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: svd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 3, b'') assert(str(excinfo.value) == 'Only version 1 and version 2 supported for a Supplementary Volume Descriptor') def test_pvd_copy_initialized_twice(): svd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) svd.new(0, b'', b'', 0, 0, 0, b'', b'', b'', b'', b'', b'', b'', 0.0, b'', False, 2, b'') svd2 = pycdlib.headervd.PrimaryOrSupplementaryVD(2) svd2.copy(svd) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: svd2.copy(svd) assert(str(excinfo.value) == 'This Volume Descriptor is already initialized') def test_pvd_record_not_initialized(): svd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: svd.record() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_track_rr_ce_entry_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.track_rr_ce_entry(0, 0, 0) assert(str(excinfo.value) == 'This Primary Volume Descriptor is not initialized') def test_pvd_add_rr_ce_entry_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.add_rr_ce_entry(0) assert(str(excinfo.value) == 'This Primary Volume Descriptor is not initialized') def test_pvd_clear_rr_ce_entries_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.clear_rr_ce_entries() assert(str(excinfo.value) == 'This Primary Volume Descriptor is not initialized') def test_pvd_path_table_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.path_table_size() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_add_to_space_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.add_to_space_size(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_remove_from_space_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.remove_from_space_size(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_root_directory_record_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.root_directory_record() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_logical_block_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.logical_block_size() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_add_to_ptr_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.add_to_ptr_size(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_remove_from_ptr_size_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.remove_from_ptr_size(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_sequence_number_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.sequence_number() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_copy_sizes_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.copy_sizes(None) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_extent_location_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.extent_location() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_set_extent_location_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') def test_pvd_is_pvd_not_initialized(): pvd = pycdlib.headervd.PrimaryOrSupplementaryVD(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.is_pvd() assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # FileOrTextIdentifer def test_fot_parse_initialized_twice(): fot = pycdlib.headervd.FileOrTextIdentifier() fot.parse(b'a'*128) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fot.parse(b'a'*128) assert(str(excinfo.value) == 'This File or Text identifier is already initialized') def test_fot_new_initialized_twice(): fot = pycdlib.headervd.FileOrTextIdentifier() fot.new(b'a'*128) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fot.new(b'a'*128) assert(str(excinfo.value) == 'This File or Text identifier is already initialized') def test_fot_new_bad_length(): fot = pycdlib.headervd.FileOrTextIdentifier() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: fot.new(b'a'*127) assert(str(excinfo.value) == 'Length of text must be 128') def test_fot_record_not_initialized(): fot = pycdlib.headervd.FileOrTextIdentifier() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fot.record() assert(str(excinfo.value) == 'This File or Text identifier is not initialized') # Volume Descriptor Set Terminator def test_vdst_parse_initialized_twice(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() vdst.parse(b'\xffCD001\x01' + b'\x00'*2041, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdst.parse(b'\xffCD001\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Volume Descriptor Set Terminator already initialized') def test_vdst_parse_bad_descriptor_type(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: vdst.parse(b'\xfeCD001\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid VDST descriptor type') def test_vdst_parse_bad_identifier(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: vdst.parse(b'\xffCD002\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid VDST identifier') def test_vdst_new_initialized_twice(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() vdst.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdst.new() assert(str(excinfo.value) == 'Volume Descriptor Set Terminator already initialized') def test_vdst_record_not_initialized(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdst.record() assert(str(excinfo.value) == 'Volume Descriptor Set Terminator not initialized') def test_vdst_extent_location_not_initialized(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdst.extent_location() assert(str(excinfo.value) == 'Volume Descriptor Set Terminator not initialized') def test_vdst_set_extent_location_not_initialized(): vdst = pycdlib.headervd.VolumeDescriptorSetTerminator() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdst.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # Boot Record def test_br_parse_initialized_twice(): br = pycdlib.headervd.BootRecord() br.parse(b'\x00CD001\x01' + b'\x00'*32 + b'\x00'*32 + b'\x00'*1977, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.parse(b'\x00CD001\x01' + b'\x00'*32 + b'\x00'*32 + b'\x00'*1977, 0) assert(str(excinfo.value) == 'Boot Record already initialized') def test_br_parse_bad_descriptor_type(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: br.parse(b'\x01CD001\x01' + b'\x00'*32 + b'\x00'*32 + b'\x00'*1977, 0) assert(str(excinfo.value) == 'Invalid boot record descriptor type') def test_br_parse_bad_identifier(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: br.parse(b'\x00CD002\x01' + b'\x00'*32 + b'\x00'*32 + b'\x00'*1977, 0) assert(str(excinfo.value) == 'Invalid boot record identifier') def test_br_new_initialized_twice(): br = pycdlib.headervd.BootRecord() br.new(b'foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.new(b'foo') assert(str(excinfo.value) == 'Boot Record already initialized') def test_br_record_not_initialized(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.record() assert(str(excinfo.value) == 'Boot Record not initialized') def test_br_update_boot_system_use_not_initialized(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.update_boot_system_use(b'\x00'*1977) assert(str(excinfo.value) == 'Boot Record not initialized') def test_br_update_boot_system_use_invalid_length(): br = pycdlib.headervd.BootRecord() br.new(b'foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.update_boot_system_use(b'\x00'*1976) assert(str(excinfo.value) == 'Boot system use field must be 1977 bytes') def test_br_extent_location_not_initialized(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.extent_location() assert(str(excinfo.value) == 'Boot Record not initialized') def test_br_set_extent_location_not_initialized(): br = pycdlib.headervd.BootRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: br.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # Version Volume Descriptor def test_version_parse_initialized_twice(): version = pycdlib.headervd.VersionVolumeDescriptor() version.parse(b'\x00'*2048, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: version.parse(b'\x00'*2048, 0) assert(str(excinfo.value) == 'This Version Volume Descriptor is already initialized') def test_version_parse_not_version(): version = pycdlib.headervd.VersionVolumeDescriptor() assert(not(version.parse(b'\x00'*2047, 0))) def test_version_new_initialized_twice(): version = pycdlib.headervd.VersionVolumeDescriptor() version.new(2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: version.new(2048) assert(str(excinfo.value) == 'This Version Volume Descriptor is already initialized') def test_version_record_not_initialized(): version = pycdlib.headervd.VersionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: version.record() assert(str(excinfo.value) == 'This Version Volume Descriptor is not initialized') def test_version_extent_location_not_initialized(): version = pycdlib.headervd.VersionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: version.extent_location() assert(str(excinfo.value) == 'This Version Volume Descriptor is not initialized') def test_version_set_extent_location_not_initialized(): version = pycdlib.headervd.VersionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: version.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600637147.3276062 pycdlib-1.11.0/tests/unit/test_inode.py0000664000175000017500000000440000000000000022631 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct import time prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.inode import pycdlib.pycdlibexception def test_inode_new_initialized_twice(): ino = pycdlib.inode.Inode() ino.new(0, '', False, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.new(0, '', False, 0) assert(str(excinfo.value) == 'Inode is already initialized') def test_inode_parse_initialized_twice(): ino = pycdlib.inode.Inode() ino.parse(0, 0, None, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.parse(0, 0, None, 0) assert(str(excinfo.value) == 'Inode is already initialized') def test_inode_extent_location_not_initialized(): ino = pycdlib.inode.Inode() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.extent_location() assert(str(excinfo.value) == 'Inode is not initialized') def test_inode_set_extent_location_not_initialized(): ino = pycdlib.inode.Inode() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.set_extent_location(0) assert(str(excinfo.value) == 'Inode is not initialized') def test_inode_get_data_length_not_initialized(): ino = pycdlib.inode.Inode() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.get_data_length() assert(str(excinfo.value) == 'Inode is not initialized') def test_inode_add_boot_info_table_not_initialized(): ino = pycdlib.inode.Inode() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.add_boot_info_table(None) assert(str(excinfo.value) == 'Inode is not initialized') def test_inode_update_fp_not_initialized(): ino = pycdlib.inode.Inode() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ino.update_fp(None, 0) assert(str(excinfo.value) == 'Inode is not initialized') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600635504.2736216 pycdlib-1.11.0/tests/unit/test_isohybrid.py0000664000175000017500000004514600000000000023543 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.isohybrid # APMPartHeader def test_apm_part_header_parse_initialized_twice(): apm_part = pycdlib.isohybrid.APMPartHeader() apm_part.parse(b'\x50\x4d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00'*10 + b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*372) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: apm_part.parse(b'\x50\x4d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00'*10 + b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*372) assert(str(excinfo.value) == 'This APMPartHeader object is already initialized') def test_apm_part_header_parse_not_magic(): apm_part = pycdlib.isohybrid.APMPartHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: apm_part.parse(b'\x40\x4d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00'*10 + b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*372) assert(str(excinfo.value) == 'Invalid APM signature') def test_apm_part_header_new_initialized_twice(): apm_part = pycdlib.isohybrid.APMPartHeader() apm_part.new('foo', 'type', 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: apm_part.new('foo', 'type', 0) assert(str(excinfo.value) == 'This APMPartHeader object is already initialized') def test_apm_part_header_record_not_initialized(): apm_part = pycdlib.isohybrid.APMPartHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: apm_part.record() assert(str(excinfo.value) == 'This APMPartHeader object is not initialized') # GPTPartHeader def test_gpt_part_header_parse_initialized_twice(): gpt_part = pycdlib.isohybrid.GPTPartHeader() gpt_part.parse(b'\xa2\xa0\xd0\xeb\xe5\xb9\x33\x44\x87\xc0\x68\xb6\xb7\x26\x99\xc7' + b'\x00'*16 + b'\x00'*16 + b'\x00'*8 + b'\x00'*72) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_part.parse(b'\xa2\xa0\xd0\xeb\xe5\xb9\x33\x44\x87\xc0\x68\xb6\xb7\x26\x99\xc7' + b'\x00'*16 + b'\x00'*16 + b'\x00'*8 + b'\x00'*72) assert(str(excinfo.value) == 'This GPTPartHeader object is already initialized') def test_gpt_part_header_parse_invalid_guid(): gpt_part = pycdlib.isohybrid.GPTPartHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: gpt_part.parse(b'\x00'*16 + b'\x00'*16 + b'\x00'*16 + b'\x00'*8 + b'\x00'*72) assert(str(excinfo.value) == 'Invalid Partition Type UUID') def test_gpt_part_header_new_initialized_twice(): gpt_part = pycdlib.isohybrid.GPTPartHeader() gpt_part.new(True, 'foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_part.new(True, 'foo') assert(str(excinfo.value) == 'This GPTPartHeader object is already initialized') def test_gpt_part_header_record_not_initialized(): gpt_part = pycdlib.isohybrid.GPTPartHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_part.record() assert(str(excinfo.value) == 'This GPTPartHeader object is not initialized') # GPTHeader def test_gpt_header_parse_initialized_twice(): gpt_header = pycdlib.isohybrid.GPTHeader() gpt_header.parse(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_header.parse(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'This GPTHeader object is already initialized') def test_gpt_header_parse_bad_gpt_sig(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: gpt_header.parse(b'\x44\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'Failed to find GPT signature while parsing GPT Header') def test_gpt_header_parse_bad_gpt_rev(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: gpt_header.parse(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x01\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'Failed to find GPT revision while parsing GPT Header') def test_gpt_header_parse_bad_header_size(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: gpt_header.parse(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5b\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'Invalid GPT Header size while parsing GPT Header') def test_gpt_header_new_initialized_twice(): gpt_header = pycdlib.isohybrid.GPTHeader() gpt_header.new(False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_header.new(False) assert(str(excinfo.value) == 'This GPTHeader object is already initialized') def test_gpt_header_set_lbas_not_initialized(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_header.set_lbas(0, 0) assert(str(excinfo.value) == 'This GPTHeader object is not initialized') def test_gpt_header_set_last_usable_lba_not_initialized(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_header.set_last_usable_lba(0) assert(str(excinfo.value) == 'This GPTHeader object is not initialized') def test_gpt_header_record_not_initialized(): gpt_header = pycdlib.isohybrid.GPTHeader() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt_header.record(0) assert(str(excinfo.value) == 'This GPTHeader object is not initialized') # GPT def test_gpt_parse_primary_initialized_twice(): gpt = pycdlib.isohybrid.GPT(True) gpt.parse_primary(b'\x00'*512 + b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420, False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.parse_primary(b'\x00'*512 + b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420, False) assert(str(excinfo.value) == 'This GPT object is already initialized') def test_gpt_parse_primary_with_secondary_GPT(): gpt = pycdlib.isohybrid.GPT(False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.parse_primary(b'\x00'*512 + b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420, False) assert(str(excinfo.value) == 'Cannot parse primary with a secondary GPT') def test_gpt_parse_secondary_header_initialized_twice(): gpt = pycdlib.isohybrid.GPT(False) gpt.parse_secondary_header(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) gpt.parse_secondary_partitions(b'') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.parse_secondary_header(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'This GPT object is already initialized') def test_gpt_parse_secondary_header_with_primary_gpt(): gpt = pycdlib.isohybrid.GPT(True) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.parse_secondary_header(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) assert(str(excinfo.value) == 'Cannot parse secondary header with a primary GPT') def test_gpt_parse_secondary_header_with_primary_gpt(): gpt = pycdlib.isohybrid.GPT(False) gpt.parse_secondary_header(b'\x45\x46\x49\x20\x50\x41\x52\x54' + b'\x00\x00\x01\x00' + b'\x5c\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*420) gpt.parse_secondary_partitions(b'') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.parse_secondary_partitions(b'') assert(str(excinfo.value) == 'This GPT object is already initialized') def test_gpt_new_initialized_twice(): gpt = pycdlib.isohybrid.GPT(True) gpt.new(False) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.new(False) assert(str(excinfo.value) == 'This GPT object is already initialized') def test_gpt_record_not_initialized(): gpt = pycdlib.isohybrid.GPT(True) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: gpt.record() assert(str(excinfo.value) == 'This GPT object is not initialized') # IsoHybrid def test_isohybrid_parse_initialized_twice(): isohybrid = pycdlib.isohybrid.IsoHybrid() isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80' + b'\x00'*63 + b'\x55\xaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80' + b'\x00'*63 + b'\x55\xaa') assert(str(excinfo.value) == 'This IsoHybrid object is already initialized') def test_isohybrid_parse_invalid_size(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.parse(b'') assert(str(excinfo.value) == 'Invalid IsoHybrid MBR passed') def test_isohybrid_parse_invalid_unused1(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80' + b'\x00'*63 + b'\x55\xaa') assert(str(excinfo.value) == 'Invalid IsoHybrid unused1') def test_isohybrid_parse_invalid_unused2(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00' + b'\x80' + b'\x00'*63 + b'\x55\xaa') assert(str(excinfo.value) == 'Invalid IsoHybrid unused2') def test_isohybrid_parse_no_part_entry(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00' + b'\x00'*63 + b'\x55\xaa') assert(str(excinfo.value) == 'No valid partition found in IsoHybrid!') def test_isohybrid_parse_bad_tail(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: isohybrid.parse(b'\x33\xed' + b'\x90' * 30 + b'\x00'*400 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80' + b'\x00'*63 + b'\xaa\xaa') assert(str(excinfo.value) == 'Invalid tail on isohybrid section') def test_isohybrid_parse_secondary_gpt_header_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.parse_secondary_gpt_header(b'') assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_parse_secondary_gpt_partitions_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.parse_secondary_gpt_partitions(b'') assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_new_initialized_twice(): isohybrid = pycdlib.isohybrid.IsoHybrid() isohybrid.new(False, False, 0, 0, 0, 1, 1, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.new(False, False, 0, 0, 0, 1, 1, 0) assert(str(excinfo.value) == 'This IsoHybrid object is already initialized') def test_isohybrid_new_bad_sectors(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: isohybrid.new(False, False, 0, 0, 0, 0, 1, 0) assert(str(excinfo.value) == 'Geometry sectors can only be between 1 and 63, inclusive') def test_isohybrid_new_bad_heads(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: isohybrid.new(False, False, 0, 0, 0, 1, 0, 0) assert(str(excinfo.value) == 'Geometry heads can only be between 1 and 256, inclusive') def test_isohybrid_new_bad_mac_part_type(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: isohybrid.new(False, True, 0, 0, 0, 1, 1, 1) assert(str(excinfo.value) == 'When generating for Mac, partition type must be 0') def test_isohybrid_record_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.record(1) assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_record_padding_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.record_padding(1) assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_update_rba_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.update_rba(1) assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_update_efi_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.update_efi(1, 1, 1) assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_update_efi_not_efi(): isohybrid = pycdlib.isohybrid.IsoHybrid() isohybrid.new(False, False, 0, 0, 0, 1, 1, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.update_efi(1, 1, 1) assert(str(excinfo.value) == 'Attempted to set EFI lba on a non-EFI ISO') def test_isohybrid_update_mac_not_initialized(): isohybrid = pycdlib.isohybrid.IsoHybrid() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.update_mac(1, 1) assert(str(excinfo.value) == 'This IsoHybrid object is not initialized') def test_isohybrid_update_mac_not_mac(): isohybrid = pycdlib.isohybrid.IsoHybrid() isohybrid.new(False, False, 0, 0, 0, 1, 1, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: isohybrid.update_mac(1, 1) assert(str(excinfo.value) == 'Attempted to set Mac lba on a non-Mac ISO') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1600304845.0873303 pycdlib-1.11.0/tests/unit/test_ptr.py0000664000175000017500000000562200000000000022347 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.path_table_record def test_path_table_record_parse_initialized_twice(): ptr = pycdlib.path_table_record.PathTableRecord() ptr.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Path Table Record already initialized') def test_path_table_record_record_little_endian_not_initialized(): ptr = pycdlib.path_table_record.PathTableRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.record_little_endian() assert(str(excinfo.value) == 'Path Table Record not initialized') def test_path_table_record_record_big_endian_not_initialized(): ptr = pycdlib.path_table_record.PathTableRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.record_big_endian() assert(str(excinfo.value) == 'Path Table Record not initialized') def test_path_table_record_new_root_initialized_twice(): ptr = pycdlib.path_table_record.PathTableRecord() ptr.new_root() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.new_root() assert(str(excinfo.value) == 'Path Table Record already initialized') def test_path_table_record_new_dir_initialized_twice(): ptr = pycdlib.path_table_record.PathTableRecord() ptr.new_dir(b'foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.new_dir(b'foo') assert(str(excinfo.value) == 'Path Table Record already initialized') def test_path_table_record_update_extent_location_not_initialized(): ptr = pycdlib.path_table_record.PathTableRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.update_extent_location(0) assert(str(excinfo.value) == 'Path Table Record not initialized') def test_path_table_record_update_parent_directory_number_not_initialized(): ptr = pycdlib.path_table_record.PathTableRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.update_parent_directory_number(0) assert(str(excinfo.value) == 'Path Table Record not initialized') def test_path_table_record_equal_to_be_not_initialized(): ptr = pycdlib.path_table_record.PathTableRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ptr.equal_to_be(None) assert(str(excinfo.value) == 'Path Table Record not initialized') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602034216.0374286 pycdlib-1.11.0/tests/unit/test_rockridge.py0000664000175000017500000017567100000000000023527 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.rockridge # SP record def test_rrsprecord_parse_double_initialized(): sp = pycdlib.rockridge.RRSPRecord() sp.parse(b'SP\x07\x01\xbe\xef\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sp.parse(b'SP\x07\x01\xbe\xef\x00') assert(str(excinfo.value) == 'SP record already initialized') def test_rrsprecord_parse_bad_length(): sp = pycdlib.rockridge.RRSPRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sp.parse(b'SP\x06\x01\xbe\xef\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrsprecord_parse_bad_check_byte(): sp = pycdlib.rockridge.RRSPRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sp.parse(b'SP\x07\x01\xbf\xef\x00') assert(str(excinfo.value) == 'Invalid check bytes on rock ridge extension') def test_rrsprecord_new_double_initialized(): sp = pycdlib.rockridge.RRSPRecord() sp.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sp.new(0) assert(str(excinfo.value) == 'SP record already initialized') def test_rrsprecord_record_not_initialized(): sp = pycdlib.rockridge.RRSPRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sp.record() assert(str(excinfo.value) == 'SP record not initialized') def test_rrsprecord_record(): sp = pycdlib.rockridge.RRSPRecord() sp.new(0) rec = sp.record() assert(rec == b'SP\x07\x01\xbe\xef\x00') def test_rrsprecord_length(): assert(pycdlib.rockridge.RRSPRecord.length() == 7) # RR record def test_rrrrrecord_parse_double_initialized(): rr = pycdlib.rockridge.RRRRRecord() rr.parse(b'RR\x05\x01\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.parse(b'RR\x05\x01\x00') assert(str(excinfo.value) == 'RR record already initialized') def test_rrrrrecord_parse_bad_length(): rr = pycdlib.rockridge.RRRRRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'RR\x06\x01\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrrrrecord_new_double_initialized(): rr = pycdlib.rockridge.RRRRRecord() rr.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.new() assert(str(excinfo.value) == 'RR record already initialized') def test_rrrrrecord_append_field_not_initialized(): rr = pycdlib.rockridge.RRRRRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.append_field('PX') assert(str(excinfo.value) == 'RR record not initialized') def test_rrrrrecord_append_field_invalid_field(): rr = pycdlib.rockridge.RRRRRecord() rr.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.append_field('AA') assert(str(excinfo.value) == 'Unknown RR field name AA') def test_rrrrrecord_append_field_px(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('PX') assert(rr.rr_flags == 0x1) def test_rrrrrecord_append_field_pn(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('PN') assert(rr.rr_flags == 0x2) def test_rrrrrecord_append_field_sl(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('SL') assert(rr.rr_flags == 0x4) def test_rrrrrecord_append_field_nm(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('NM') assert(rr.rr_flags == 0x8) def test_rrrrrecord_append_field_cl(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('CL') assert(rr.rr_flags == 0x10) def test_rrrrrecord_append_field_pl(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('PL') assert(rr.rr_flags == 0x20) def test_rrrrrecord_append_field_re(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('RE') assert(rr.rr_flags == 0x40) def test_rrrrrecord_append_field_tf(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rr.append_field('TF') assert(rr.rr_flags == 0x80) def test_rrrrrecord_record_not_initialized(): rr = pycdlib.rockridge.RRRRRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.record() assert(str(excinfo.value) == 'RR record not initialized') def test_rrrrrecord_record(): rr = pycdlib.rockridge.RRRRRecord() rr.new() rec = rr.record() assert(rec == b'RR\x05\x01\x00') def test_rrrrrecord_length(): assert(pycdlib.rockridge.RRRRRecord.length() == 5) # CE record def test_rrcerecord_parse_double_initialized(): ce = pycdlib.rockridge.RRCERecord() ce.parse(b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.parse(b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'CE record already initialized') def test_rrcerecord_parse_bad_length(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ce.parse(b'CE\x1a\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrcerecord_parse_bl_le_be_mismatch(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ce.parse(b'CE\x1c\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'CE record big and little endian continuation area do not agree') def test_rrcerecord_parse_offset_le_be_mismatch(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ce.parse(b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'CE record big and little endian continuation area offset do not agree') def test_rrcerecord_parse_len_le_be_mismatch(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ce.parse(b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'CE record big and little endian continuation area length do not agree') def test_rrcerecord_new_double_initialized(): ce = pycdlib.rockridge.RRCERecord() ce.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.new() assert(str(excinfo.value) == 'CE record already initialized') def test_rrcerecord_update_extent_not_initialized(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.update_extent(0) assert(str(excinfo.value) == 'CE record not initialized') def test_rrcerecord_update_offset_not_initialized(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.update_offset(0) assert(str(excinfo.value) == 'CE record not initialized') def test_rrcerecord_update_add_record_not_initialized(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.add_record(0) assert(str(excinfo.value) == 'CE record not initialized') def test_rrcerecord_record_not_initialized(): ce = pycdlib.rockridge.RRCERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ce.record() assert(str(excinfo.value) == 'CE record not initialized') def test_rrcerecord_record(): ce = pycdlib.rockridge.RRCERecord() ce.new() rec = ce.record() assert(rec == b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_rrcerecord_length(): assert(pycdlib.rockridge.RRCERecord.length() == 28) # PX record def test_rrpxrecord_parse_double_initialized(): px = pycdlib.rockridge.RRPXRecord() px.parse(b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: px.parse(b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record already initialized') def test_rrpxrecord_parse_mode_le_be_mismatch(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x24\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record big and little-endian file mode do not agree') def test_rrpxrecord_parse_links_le_be_mismatch(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record big and little-endian file links do not agree') def test_rrpxrecord_parse_user_le_be_mismatch(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record big and little-endian file user ID do not agree') def test_rrpxrecord_parse_group_le_be_mismatch(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record big and little-endian file group ID do not agree') def test_rrpxrecord_parse_serial_le_be_mismatch(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x2C\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'PX record big and little-endian file serial number do not agree') def test_rrpxrecord_parse_bad_length(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: px.parse(b'PX\x23\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on Rock Ridge PX record') def test_rrpxrecord_new_double_initialized(): px = pycdlib.rockridge.RRPXRecord() px.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: px.new(0) assert(str(excinfo.value) == 'PX record already initialized') def test_rrpxrecord_record_not_initialized(): px = pycdlib.rockridge.RRPXRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: px.record('1.12') assert(str(excinfo.value) == 'PX record not initialized') def test_rrpxrecord_record_invalid_version(): px = pycdlib.rockridge.RRPXRecord() px.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: px.record('4.0') assert(str(excinfo.value) == 'Invalid rr_version') def test_rrpxrecord_record(): px = pycdlib.rockridge.RRPXRecord() px.new(0) rec = px.record('1.09') assert(rec == b'PX\x24\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_rrpxrecord_length_oneohnine(): assert(pycdlib.rockridge.RRPXRecord.length('1.09') == 36) def test_rrpxrecord_length_onetwelve(): assert(pycdlib.rockridge.RRPXRecord.length('1.12') == 44) def test_rrpxrecord_length_invalid_version(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pycdlib.rockridge.RRPXRecord.length('4.0') assert(str(excinfo.value) == 'Invalid rr_version') # ER record def test_rrerrecord_parse_double_initialized(): er = pycdlib.rockridge.RRERRecord() er.parse(b'ER\x0b\x01\x01\x01\x01\x01aaa') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: er.parse(b'ER\x0b\x01\x01\x01\x01\x01aaa') assert(str(excinfo.value) == 'ER record already initialized') def test_rrerrecord_parse_bad_length(): er = pycdlib.rockridge.RRERRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: er.parse(b'ER\x19\x01\x01\x01\x01\x01aaa') assert(str(excinfo.value) == 'Length of ER record much too long') def test_rrerrecord_parse_len_gt_su_len(): er = pycdlib.rockridge.RRERRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: er.parse(b'ER\x09\x01\x09\x01\x01\x01aaa') assert(str(excinfo.value) == 'Combined length of ER ID, des, and src longer than record') def test_rrerrecord_new_double_initialized(): er = pycdlib.rockridge.RRERRecord() er.new(b'', b'', b'') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: er.new(b'', b'', b'') assert(str(excinfo.value) == 'ER record already initialized') def test_rrerrecord_record_not_initialized(): er = pycdlib.rockridge.RRERRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: er.record() assert(str(excinfo.value) == 'ER record not initialized') def test_rrerrecord_record(): er = pycdlib.rockridge.RRERRecord() er.new(b'a', b'a', b'a') rec = er.record() assert(rec == b'ER\x0b\x01\x01\x01\x01\x01aaa') def test_rrerrecord_length(): assert(pycdlib.rockridge.RRERRecord.length(b'a', b'a', b'a') == 11) # ES record def test_rresrecord_parse_double_initialized(): es = pycdlib.rockridge.RRESRecord() es.parse(b'ES\x05\x01\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: es.parse(b'ES\x05\x01\x00') assert(str(excinfo.value) == 'ES record already initialized') def test_rresrecord_parse_bad_length(): es = pycdlib.rockridge.RRESRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: es.parse(b'ES\x06\x01\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rresrecord_new_double_initialized(): es = pycdlib.rockridge.RRESRecord() es.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: es.new(0) assert(str(excinfo.value) == 'ES record already initialized') def test_rresrecord_record_not_initialized(): es = pycdlib.rockridge.RRESRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: es.record() assert(str(excinfo.value) == 'ES record not initialized') def test_rresrecord_record(): es = pycdlib.rockridge.RRESRecord() es.new(0) rec = es.record() assert(rec == b'ES\x05\x01\x00') def test_rresrecord_length(): assert(pycdlib.rockridge.RRESRecord.length() == 5) # PN record def test_rrpnrecord_parse_double_initialized(): pn = pycdlib.rockridge.RRPNRecord() pn.parse(b'PN\x14\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pn.parse(b'PN\x14\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PN record already initialized') def test_rrpnrecord_parse_bad_length(): pn = pycdlib.rockridge.RRPNRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pn.parse(b'PN\x13\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrpnrecord_parse_dev_high_be_le_mismatch(): pn = pycdlib.rockridge.RRPNRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pn.parse(b'PN\x14\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Dev_t high little-endian does not match big-endian') def test_rrpnrecord_parse_dev_low_be_le_mismatch(): pn = pycdlib.rockridge.RRPNRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pn.parse(b'PN\x14\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'Dev_t low little-endian does not match big-endian') def test_rrpnrecord_new_double_initialized(): pn = pycdlib.rockridge.RRPNRecord() pn.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pn.new(0, 0) assert(str(excinfo.value) == 'PN record already initialized') def test_rrpnrecord_record_not_initialized(): pn = pycdlib.rockridge.RRPNRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pn.record() assert(str(excinfo.value) == 'PN record not initialized') def test_rrpnrecord_record(): pn = pycdlib.rockridge.RRPNRecord() pn.new(0, 0) rec = pn.record() assert(rec == b'PN\x14\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_rrpnrecord_length(): assert(pycdlib.rockridge.RRPNRecord.length() == 20) # SL.Component def test_rrsl_component_bad_flags(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: com = pycdlib.rockridge.RRSLRecord.Component(0x10, 0, b'') assert(str(excinfo.value) == 'Invalid Rock Ridge symlink flags 0x10') def test_rrsl_component_bad_length(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: com = pycdlib.rockridge.RRSLRecord.Component(0x02, 1, b'') assert(str(excinfo.value) == 'Rock Ridge symlinks to dot, dotdot, or root should have zero length') def test_rrsl_component_name_dot(): com = pycdlib.rockridge.RRSLRecord.Component(0x02, 0, b'') assert(com.name() == b'.') def test_rrsl_component_name_dotdot(): com = pycdlib.rockridge.RRSLRecord.Component(0x04, 0, b'') assert(com.name() == b'..') def test_rrsl_component_name_root(): com = pycdlib.rockridge.RRSLRecord.Component(0x08, 0, b'') assert(com.name() == b'/') def test_rrsl_component_is_continued(): com = pycdlib.rockridge.RRSLRecord.Component(0x01, 0, b'') assert(com.is_continued()) def test_rrsl_component_record_dot(): com = pycdlib.rockridge.RRSLRecord.Component(0x02, 0, b'') assert(com.record() == b'\x02\x00') def test_rrsl_component_record_dotdot(): com = pycdlib.rockridge.RRSLRecord.Component(0x04, 0, b'') assert(com.record() == b'\x04\x00') def test_rrsl_component_record_root(): com = pycdlib.rockridge.RRSLRecord.Component(0x08, 0, b'') assert(com.record() == b'\x08\x00') def test_rrsl_component_set_continued(): com = pycdlib.rockridge.RRSLRecord.Component(0x0, 0, b'') com.set_continued() assert(com.is_continued()) def test_rrsl_component_equal(): com = pycdlib.rockridge.RRSLRecord.Component(0x0, 0, b'') com2 = pycdlib.rockridge.RRSLRecord.Component(0x0, 0, b'') assert(com == com2) def test_rrsl_component_not_equal(): com = pycdlib.rockridge.RRSLRecord.Component(0x0, 0, b'') com2 = pycdlib.rockridge.RRSLRecord.Component(0x1, 0, b'') assert(com != com2) def test_rrsl_component_length_dot(): assert(pycdlib.rockridge.RRSLRecord.Component.length(b'.') == 2) def test_rrsl_component_length_dotdot(): assert(pycdlib.rockridge.RRSLRecord.Component.length(b'..') == 2) def test_rrsl_component_length_root(): assert(pycdlib.rockridge.RRSLRecord.Component.length(b'/') == 2) def test_rrsl_component_length_root(): assert(pycdlib.rockridge.RRSLRecord.Component.length(b'foo') == 5) def test_rrsl_component_factory_dot(): com = pycdlib.rockridge.RRSLRecord.Component.factory(b'.') assert(com.flags == 0x2) assert(com.curr_length == 0) assert(com.data == b'.') def test_rrsl_component_factory_dotdot(): com = pycdlib.rockridge.RRSLRecord.Component.factory(b'..') assert(com.flags == 0x4) assert(com.curr_length == 0) assert(com.data == b'..') def test_rrsl_component_factory_root(): com = pycdlib.rockridge.RRSLRecord.Component.factory(b'/') assert(com.flags == 0x8) assert(com.curr_length == 0) assert(com.data == b'/') def test_rrsl_component_factory(): com = pycdlib.rockridge.RRSLRecord.Component.factory(b'foo') assert(com.flags == 0x0) assert(com.curr_length == 3) assert(com.data == b'foo') def test_rrsl_component_not_equal_bad_type(): com = pycdlib.rockridge.RRSLRecord.Component(0x0, 0, b'') assert(com.__eq__(True) == NotImplemented) # SL record def test_rrslrecord_parse_double_initialized(): sl = pycdlib.rockridge.RRSLRecord() sl.parse(b'SL\x08\x01\x00\x00\x03foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.parse(b'SL\x08\x01\x00\x00\x03foo') assert(str(excinfo.value) == 'SL record already initialized') def test_rrslrecord_new_double_initialized(): sl = pycdlib.rockridge.RRSLRecord() sl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.new() assert(str(excinfo.value) == 'SL record already initialized') def test_rrslrecord_add_component_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.add_component(b'a') assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_add_component_too_long(): sl = pycdlib.rockridge.RRSLRecord() sl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: sl.add_component(b'a'*256) assert(str(excinfo.value) == 'Symlink would be longer than 255') def test_rrslrecord_current_length_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.current_length() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_record_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.record() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_name_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.name() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_name_with_root(): sl = pycdlib.rockridge.RRSLRecord() sl.new() sl.add_component(b'/') assert(sl.name() == b'') def test_rrslrecord_name_with_continued_comp(): sl = pycdlib.rockridge.RRSLRecord() sl.new() sl.add_component(b'foo') sl.set_last_component_continued() sl.add_component(b'bar') assert(sl.name() == b'foobar') def test_rrslrecord_set_continued_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.set_continued() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_set_last_component_continued_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.set_last_component_continued() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_set_last_component_continued_no_components(): sl = pycdlib.rockridge.RRSLRecord() sl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.set_last_component_continued() assert(str(excinfo.value) == 'Trying to set continued on a non-existent component!') def test_rrslrecord_last_component_continued_not_initialized(): sl = pycdlib.rockridge.RRSLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.last_component_continued() assert(str(excinfo.value) == 'SL record not initialized') def test_rrslrecord_last_component_continued_no_components(): sl = pycdlib.rockridge.RRSLRecord() sl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sl.last_component_continued() assert(str(excinfo.value) == 'Trying to get continued on a non-existent component!') # AL Record def test_rralrecord_component_bad_flags(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pycdlib.rockridge.RRALRecord.Component(5, 0, b'') assert(str(excinfo.value) == 'Invalid Arbitrary Attribute flags 0x5') def test_rralrecord_component_set_continued(): comp = pycdlib.rockridge.RRALRecord.Component(0, 0, b'') comp.set_continued() assert(comp.flags == 0x1) def test_rralrecord_component_factory(): comp = pycdlib.rockridge.RRALRecord.Component.factory(b'foo') assert(comp.flags == 0x0) assert(comp.curr_length == 3) assert(comp.data == b'foo') def test_rralrecord_parse_double_initialized(): al = pycdlib.rockridge.RRALRecord() al.parse(b'\x41\x4c\x10\x01\x00\x00\x03\x04\x6e\x74\x00\x04\x01\x01\x01\xff') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.parse(b'\x41\x4c\x10\x01\x00\x00\x03\x04\x6e\x74\x00\x04\x01\x01\x01\xff') assert(str(excinfo.value) == 'AL record already initialized') def test_rralrecord_parse(): al = pycdlib.rockridge.RRALRecord() al.parse(b'\x41\x4c\x10\x01\x00\x00\x03\x04\x6e\x74\x00\x04\x01\x01\x01\xff') assert(al._initialized) assert(al.flags == 0) assert(len(al.components) == 2) assert(al.components[0].flags == 0) assert(al.components[0].curr_length == 3) assert(al.components[0].data == b'\x04nt') assert(al.components[1].flags == 0) assert(al.components[1].curr_length == 4) assert(al.components[1].data == b'\x01\x01\x01\xff') def test_rralrecord_current_length_not_initialized(): al = pycdlib.rockridge.RRALRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.current_length() assert(str(excinfo.value) == 'AL record not initialized') def test_rralrecord_record_not_initialized(): al = pycdlib.rockridge.RRALRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.record() assert(str(excinfo.value) == 'AL record not initialized') def test_rralrecord_record(): al = pycdlib.rockridge.RRALRecord() al.parse(b'\x41\x4c\x10\x01\x00\x00\x03\x04\x6e\x74\x00\x04\x01\x01\x01\xff') assert(al.record() == b'\x41\x4c\x10\x01\x00\x00\x03\x04\x6e\x74\x00\x04\x01\x01\x01\xff') def test_rralrecord_new_initialized_twice(): al = pycdlib.rockridge.RRALRecord() al.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.new() assert(str(excinfo.value) == 'AL record already initialized') def test_rralrecord_set_continued_not_initialized(): al = pycdlib.rockridge.RRALRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.set_continued() assert(str(excinfo.value) == 'AL record not initialized') def test_rralrecord_set_continued(): al = pycdlib.rockridge.RRALRecord() al.new() al.set_continued() assert(al.flags == 0x1) def test_rralrecord_set_last_component_continued_not_initialized(): al = pycdlib.rockridge.RRALRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.set_last_component_continued() assert(str(excinfo.value) == 'AL record not initialized') def test_rralrecord_set_last_component_continued_no_components(): al = pycdlib.rockridge.RRALRecord() al.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.set_last_component_continued() assert(str(excinfo.value) == 'Trying to set continued on a non-existent component!') def test_rralrecord_set_last_component_continued(): al = pycdlib.rockridge.RRALRecord() al.new() al.add_component(b'foo') al.set_last_component_continued() assert(len(al.components) == 1) assert(al.components[0].flags == 0x1) def test_rralrecord_add_component_not_initialized(): al = pycdlib.rockridge.RRALRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: al.add_component(b'foo') assert(str(excinfo.value) == 'AL record not initialized') def test_rralrecord_add_component_too_long(): al = pycdlib.rockridge.RRALRecord() al.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: al.add_component(b'a'*256) assert(str(excinfo.value) == 'Attribute would be longer than 255') # NM record def test_rrnmrecord_parse_double_initialized(): nm = pycdlib.rockridge.RRNMRecord() nm.parse(b'NM\x05\x01\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nm.parse(b'NM\x05\x01\x00') assert(str(excinfo.value) == 'NM record already initialized') def test_rrnmrecord_parse_invalid_flag(): nm = pycdlib.rockridge.RRNMRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: nm.parse(b'NM\x05\x01\x03') assert(str(excinfo.value) == 'Invalid Rock Ridge NM flags') def test_rrnmrecord_parse_invalid_flag_with_name(): nm = pycdlib.rockridge.RRNMRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: nm.parse(b'NM\x06\x01\x02a') assert(str(excinfo.value) == 'Invalid name in Rock Ridge NM entry (0x2 1)') def test_rrnmrecord_new_double_initialized(): nm = pycdlib.rockridge.RRNMRecord() nm.new(b'foo') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nm.new(b'foo') assert(str(excinfo.value) == 'NM record already initialized') def test_rrnmrecord_record_not_initialized(): nm = pycdlib.rockridge.RRNMRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nm.record() assert(str(excinfo.value) == 'NM record not initialized') def test_rrnmrecord_set_continued_not_initialized(): nm = pycdlib.rockridge.RRNMRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nm.set_continued() assert(str(excinfo.value) == 'NM record not initialized') # CL record def test_rrclrecord_parse_double_initialized(): cl = pycdlib.rockridge.RRCLRecord() cl.parse(b'CL\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: cl.parse(b'CL\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'CL record already initialized') def test_rrclrecord_parse_invalid_size(): cl = pycdlib.rockridge.RRCLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: cl.parse(b'CL\x0d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrclrecord_parse_be_le_mismatch(): cl = pycdlib.rockridge.RRCLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: cl.parse(b'CL\x0c\x01\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'Little endian block num does not equal big endian; corrupt ISO') def test_rrclrecord_new_double_initialized(): cl = pycdlib.rockridge.RRCLRecord() cl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: cl.new() assert(str(excinfo.value) == 'CL record already initialized') def test_rrclrecord_record_not_initialized(): cl = pycdlib.rockridge.RRCLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: cl.record() assert(str(excinfo.value) == 'CL record not initialized') def test_rrclrecord_set_log_block_num_not_initialized(): cl = pycdlib.rockridge.RRCLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: cl.set_log_block_num(0) assert(str(excinfo.value) == 'CL record not initialized') # PL record def test_rrplrecord_parse_double_initialized(): pl = pycdlib.rockridge.RRPLRecord() pl.parse(b'PL\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pl.parse(b'PL\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'PL record already initialized') def test_rrplrecord_parse_invalid_size(): pl = pycdlib.rockridge.RRPLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pl.parse(b'PL\x0d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrplrecord_parse_be_le_mismatch(): pl = pycdlib.rockridge.RRPLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pl.parse(b'PL\x0c\x01\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'Little endian block num does not equal big endian; corrupt ISO') def test_rrplrecord_new_double_initialized(): pl = pycdlib.rockridge.RRPLRecord() pl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pl.new() assert(str(excinfo.value) == 'PL record already initialized') def test_rrplrecord_record_not_initialized(): pl = pycdlib.rockridge.RRPLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pl.record() assert(str(excinfo.value) == 'PL record not initialized') def test_rrplrecord_set_log_block_num_not_initialized(): pl = pycdlib.rockridge.RRPLRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pl.set_log_block_num(0) assert(str(excinfo.value) == 'PL record not initialized') # TF record def test_rrtfrecord_parse_double_initialized(): tf = pycdlib.rockridge.RRTFRecord() tf.parse(b'TF\x05\x01\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tf.parse(b'TF\x05\x01\x00') assert(str(excinfo.value) == 'TF record already initialized') def test_rrtfrecord_parse_invalid_size(): tf = pycdlib.rockridge.RRTFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tf.parse(b'TF\x04\x01\x00') assert(str(excinfo.value) == 'Not enough bytes in the TF record') def test_rrtfrecord_parse_use_vol_desc_dates(): tf = pycdlib.rockridge.RRTFRecord() tf.parse(b'TF\x16\x01\x81' + b'\x00'*17) assert(tf.creation_time.date_str == b'0' * 16 + b'\x00') def test_rrtfrecord_new_double_initialized(): tf = pycdlib.rockridge.RRTFRecord() tf.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tf.new(0) assert(str(excinfo.value) == 'TF record already initialized') def test_rrtfrecord_new_use_vol_desc_dates(): tf = pycdlib.rockridge.RRTFRecord() tf.new(0x81) assert(tf.creation_time.date_str == b'0' * 16 + b'\x00') def test_rrtfrecord_record_not_initialized(): tf = pycdlib.rockridge.RRTFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tf.record() assert(str(excinfo.value) == 'TF record not initialized') def test_rrtfrecord_length_use_vol_desc_dates(): assert(pycdlib.rockridge.RRTFRecord.length(0x81) == 0x16) # SF record def test_rrsfrecord_parse_double_initialized(): sf = pycdlib.rockridge.RRSFRecord() sf.parse(b'SF\x0C\x01\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sf.parse(b'SF\x0C\x01\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'SF record already initialized') def test_rrsfrecord_parse_one_ten(): sf = pycdlib.rockridge.RRSFRecord() sf.parse(b'SF\x0C\x01\x00\x00\x00\x00\x00\x00\x00\x00') assert(sf.virtual_file_size_low == 0) def test_rrsfrecord_parse_one_ten_be_le_mismatch(): sf = pycdlib.rockridge.RRSFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sf.parse(b'SF\x0C\x01\x00\x00\x00\x01\x00\x00\x00\x00') assert(str(excinfo.value) == 'Virtual file size little-endian does not match big-endian') def test_rrsfrecord_parse_one_twelve_high_be_le_mismatch(): sf = pycdlib.rockridge.RRSFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sf.parse(b'SF\x15\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Virtual file size high little-endian does not match big-endian') def test_rrsfrecord_parse_one_twelve_low_be_le_mismatch(): sf = pycdlib.rockridge.RRSFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sf.parse(b'SF\x15\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Virtual file size low little-endian does not match big-endian') def test_rrsfrecord_parse_one_twelve(): sf = pycdlib.rockridge.RRSFRecord() sf.parse(b'SF\x15\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(sf.virtual_file_size_low == 0) assert(sf.virtual_file_size_high == 0) assert(sf.table_depth == 0) def test_rrsfrecord_parse_invalid_length(): sf = pycdlib.rockridge.RRSFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: sf.parse(b'SF\x16\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid length on Rock Ridge SF record (expected 12 or 21)') def test_rrsfrecord_new_double_initialized(): sf = pycdlib.rockridge.RRSFRecord() sf.new(None, 0, None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sf.new(None, 0, None) assert(str(excinfo.value) == 'SF record already initialized') def test_rrsfrecord_new_one_ten(): sf = pycdlib.rockridge.RRSFRecord() sf.new(None, 0, None) assert(sf.virtual_file_size_low == 0) def test_rrsfrecord_new_one_twelve(): sf = pycdlib.rockridge.RRSFRecord() sf.new(0, 0, 0) assert(sf.virtual_file_size_low == 0) assert(sf.virtual_file_size_high == 0) assert(sf.table_depth == 0) def test_rrsfrecord_record_not_initialized(): sf = pycdlib.rockridge.RRSFRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: sf.record() assert(str(excinfo.value) == 'SF record not initialized') def test_rrsfrecord_record_one_ten(): sf = pycdlib.rockridge.RRSFRecord() sf.new(None, 0, None) assert(sf.record() == b'SF\x0C\x01\x00\x00\x00\x00\x00\x00\x00\x00') def test_rrsfrecord_record_one_twelve(): sf = pycdlib.rockridge.RRSFRecord() sf.new(0, 0, 0) assert(sf.record() == b'SF\x15\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_rrsfrecord_length_one_ten(): assert(pycdlib.rockridge.RRSFRecord.length('1.10') == 12) def test_rrsfrecord_length_one_twelve(): assert(pycdlib.rockridge.RRSFRecord.length('1.12') == 21) def test_rrsfrecord_length_invalid_version(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pycdlib.rockridge.RRSFRecord.length('foo') assert(str(excinfo.value) == 'Invalid rr_version') # RE record def test_rrrerecord_parse_double_initialized(): re = pycdlib.rockridge.RRRERecord() re.parse(b'RE\x04\x01') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: re.parse(b'RE\x04\x01') assert(str(excinfo.value) == 'RE record already initialized') def test_rrrerecord_parse_bad_length(): re = pycdlib.rockridge.RRRERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: re.parse(b'RE\x06\x01\xbe\xef\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrrerecord_new_double_initialized(): re = pycdlib.rockridge.RRRERecord() re.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: re.new() assert(str(excinfo.value) == 'RE record already initialized') def test_rrrerecord_record_not_initialized(): re = pycdlib.rockridge.RRRERecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: re.record() assert(str(excinfo.value) == 'RE record not initialized') def test_rrrerecord_record(): re = pycdlib.rockridge.RRRERecord() re.new() assert(re.record() == b'RE\x04\x01') def test_rrrerecord_length(): assert(pycdlib.rockridge.RRRERecord.length() == 4) # ST record def test_rrstrecord_parse_double_initialized(): st = pycdlib.rockridge.RRSTRecord() st.parse(b'ST\x04\x01') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: st.parse(b'ST\x04\x01') assert(str(excinfo.value) == 'ST record already initialized') def test_rrstrecord_parse_bad_length(): st = pycdlib.rockridge.RRSTRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: st.parse(b'ST\x06\x01\xbe\xef\x00') assert(str(excinfo.value) == 'Invalid length on rock ridge extension') def test_rrstrecord_new_double_initialized(): st = pycdlib.rockridge.RRSTRecord() st.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: st.new() assert(str(excinfo.value) == 'ST record already initialized') def test_rrstrecord_record_not_initialized(): st = pycdlib.rockridge.RRSTRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: st.record() assert(str(excinfo.value) == 'ST record not initialized') def test_rrstrecord_record(): st = pycdlib.rockridge.RRSTRecord() st.new() assert(st.record() == b'ST\x04\x01') def test_rrstrecord_length(): assert(pycdlib.rockridge.RRSTRecord.length() == 4) # PD record def test_rrpdrecord_parse_double_initialized(): pd = pycdlib.rockridge.RRPDRecord() pd.parse(b'PD\x04\x01\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pd.parse(b'PD\x04\x01\x00') assert(str(excinfo.value) == 'PD record already initialized') def test_rrpdrecord_new_double_initialized(): pd = pycdlib.rockridge.RRPDRecord() pd.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pd.new() assert(str(excinfo.value) == 'PD record already initialized') def test_rrpdrecord_record_not_initialized(): pd = pycdlib.rockridge.RRPDRecord() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pd.record() assert(str(excinfo.value) == 'PD record not initialized') def test_rrpdrecord_record(): pd = pycdlib.rockridge.RRPDRecord() pd.new() assert(pd.record() == b'PD\x04\x01') def test_rrpdrecord_length(): assert(pycdlib.rockridge.RRPDRecord.length(b'') == 4) # RockRidge class def test_rr_parse_bad_padding_byte(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'\x01', False, 0, False) assert(str(excinfo.value) == 'Invalid pad byte') def test_rr_parse_not_enough_bytes(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'\x00\x00\x00', False, 0, False) assert(str(excinfo.value) == 'Not enough bytes left in the System Use field') def test_rr_parse_invalid_rr_version(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'\x00\x00\x00\x00', False, 0, False) assert(str(excinfo.value) == 'Invalid RR version 0!') def test_rr_parse_invalid_rtype(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'\x00\x00\x01\x01', False, 0, False) assert(str(excinfo.value) == 'Unknown SUSP record') def test_rr_parse_invalid_sp_record(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'SP\x01\x01', False, 0, False) assert(str(excinfo.value) == 'Invalid SUSP SP record') def test_rr_parse_double_ce_record(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00CE\x1c\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', False, 0, False) assert(str(excinfo.value) == 'Only single CE record supported') def test_rr_parse_pd_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'PD\x04\x01', False, 0, False) assert(len(rr.dr_entries.pd_records) == 1) def test_rr_parse_st_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'ST\x04\x01', False, 0, False) assert(rr.dr_entries.st_record is not None) def test_rr_parse_double_st_record(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'ST\x04\x01ST\x04\x01', False, 0, False) assert(str(excinfo.value) == 'Only single ST record supported') def test_rr_parse_es_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'ES\x05\x01\x00', False, 0, False) assert(len(rr.dr_entries.es_records) == 1) def test_rr_parse_pn_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'PN\x14\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', False, 0, False) assert(rr.dr_entries.pn_record is not None) def test_rr_parse_oneten(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'SF\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00', False, 0, False) assert(rr.rr_version == '1.10') def test_rr_parse_invalid_size(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.parse(b'PD\x00\x01', False, 0, False) assert(str(excinfo.value) == 'Zero size for Rock Ridge entry length') def test_rr_record_dr_entries_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.record_dr_entries() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_record_ce_entries_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.record_ce_entries() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_record_es_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'ES\x05\x01\x00', False, 0, False) assert(rr.record_dr_entries() == b'ES\x05\x01\x00') def test_rr_record_pd_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'PD\x04\x01', False, 0, False) assert(rr.record_dr_entries() == b'PD\x04\x01') def test_rr_record_st_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'ST\x04\x01', False, 0, False) assert(rr.record_dr_entries() == b'ST\x04\x01') def test_rr_record_sf_record(): rr = pycdlib.rockridge.RockRidge() rr.parse(b'SF\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00', False, 0, False) assert(rr.record_dr_entries() == b'SF\x0c\x01\x00\x00\x00\x00\x00\x00\x00\x00') def test_rr_new_initialize_twice(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) assert(str(excinfo.value) == 'Rock Ridge extension already initialized') def test_rr_new_invalid_rr_version(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rr.new(False, b'foo', 0, None, '1.13', False, False, False, 0, 0, {}) assert(str(excinfo.value) == 'Only Rock Ridge versions 1.09, 1.10, and 1.12 are implemented') def test_rr_new_sprecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(True, b'foo', 0, None, '1.09', False, False, False, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.sp_record is not None) def test_rr_new_rrrecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.rr_record is not None) def test_rr_new_clrecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', True, False, False, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.cl_record is not None) assert(rr.child_link_extent() == 0) def test_rr_new_rerecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, True, False, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.re_record is not None) def test_rr_new_plrecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, True, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.pl_record is not None) assert(rr.parent_link_extent() == 0) def test_rr_new_increase_dr_len_too_far(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 254-7, {}) assert(str(excinfo.value) == 'Rock Ridge entry increased DR length too far') def test_rr_new_alrecord(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, True, 0, 0, {b'name': b'value'}) assert(rr.dr_entries.ce_record is None) assert(len(rr.dr_entries.al_records) == 1) def test_rr_new_alrecord_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 254-50, {b'name': b'value'}) assert(rr.dr_entries.ce_record is not None) assert(len(rr.dr_entries.al_records) == 1) assert(rr.dr_entries.al_records[0].flags == 0x1) assert(len(rr.dr_entries.al_records[0].components) == 1) assert(rr.dr_entries.al_records[0].components[0].flags == 0x1) assert(rr.dr_entries.al_records[0].components[0].curr_length == 2) assert(rr.dr_entries.al_records[0].components[0].data == b'na') assert(len(rr.ce_entries.al_records) == 1) assert(rr.ce_entries.al_records[0].flags == 0) assert(len(rr.ce_entries.al_records[0].components) == 2) assert(rr.ce_entries.al_records[0].components[0].flags == 0) assert(rr.ce_entries.al_records[0].components[0].curr_length == 2) assert(rr.ce_entries.al_records[0].components[0].data == b'me') assert(rr.ce_entries.al_records[0].components[1].flags == 0) assert(rr.ce_entries.al_records[0].components[1].curr_length == 5) assert(rr.ce_entries.al_records[0].components[1].data == b'value') def test_rr_new_alrecord_ce_record_only(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 254-28, {b'name': b'value'}) assert(rr.dr_entries.ce_record is not None) assert(len(rr.dr_entries.al_records) == 0) assert(len(rr.ce_entries.al_records) == 1) assert(rr.ce_entries.al_records[0].flags == 0) assert(len(rr.ce_entries.al_records[0].components) == 2) assert(rr.ce_entries.al_records[0].components[0].flags == 0) assert(rr.ce_entries.al_records[0].components[0].curr_length == 4) assert(rr.ce_entries.al_records[0].components[0].data == b'name') assert(rr.ce_entries.al_records[0].components[1].flags == 0) assert(rr.ce_entries.al_records[0].components[1].curr_length == 5) assert(rr.ce_entries.al_records[0].components[1].data == b'value') def test_rr_get_file_mode_ce_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 254-28, {}) assert(rr.dr_entries.ce_record is not None) assert(rr.ce_entries.px_record is not None) assert(rr.get_file_mode() == 0) def test_rr_add_to_file_links_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.add_to_file_links() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_remove_from_file_links_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.remove_from_file_links() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_copy_file_links_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.copy_file_links(None) assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_get_file_mode_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.get_file_mode() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_name_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.name() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_is_symlink_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.is_symlink() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_symlink_path_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.symlink_path() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_symlink_path_no_end(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, b'bar', '1.09', False, False, False, 0, 0, {}) rr.dr_entries.sl_records[0].set_last_component_continued() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.symlink_path() assert(str(excinfo.value) == 'Saw a continued symlink record with no end; ISO is probably malformed') def test_rr_child_link_record_exists_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.child_link_record_exists() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_child_link_update_from_dirrecord_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.child_link_update_from_dirrecord() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_child_link_update_from_dirrecord_no_child_link(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rr.child_link_update_from_dirrecord() assert(str(excinfo.value) == 'No child link found!') def test_rr_child_link_extent_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.child_link_extent() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_child_link_extent_no_child_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.child_link_extent() assert(str(excinfo.value) == 'Asked for child extent for non-existent child record') def test_rr_parent_link_record_exists_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.parent_link_record_exists() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_parent_link_update_from_dirrecord_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.parent_link_update_from_dirrecord() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_parent_link_update_from_dirrecord_no_parent_link(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: rr.parent_link_update_from_dirrecord() assert(str(excinfo.value) == 'No parent link found!') def test_rr_parent_link_extent_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.parent_link_extent() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_parent_link_extent_no_parent_record(): rr = pycdlib.rockridge.RockRidge() rr.new(False, b'foo', 0, None, '1.09', False, False, False, 0, 0, {}) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.parent_link_extent() assert(str(excinfo.value) == 'Asked for parent extent for non-existent parent record') def test_rr_relocated_record_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.relocated_record() assert(str(excinfo.value) == 'Rock Ridge extension not initialized') def test_rr_update_ce_block_not_initialized(): rr = pycdlib.rockridge.RockRidge() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.update_ce_block(None) assert(str(excinfo.value) == 'Rock Ridge extension not initialized') # RockRidgeContinuationBlock and RockRidgeContinuationEntry def test_rrcontentry_track_into_empty(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(0, 23) assert(len(rr._entries) == 1) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) def test_rrcontentry_track_at_end(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(0, 23) rr.track_entry(23, 33) assert(len(rr._entries) == 2) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 33) def test_rrcontentry_track_at_beginning(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(23, 33) rr.track_entry(0, 23) assert(len(rr._entries) == 2) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 33) def test_rrcontentry_track_overlap(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(0, 23) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.track_entry(22, 33) assert(str(excinfo.value) == 'Overlapping CE regions on the ISO') def test_rrcontentry_track_rest(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(0, 23) rr.track_entry(23, 2025) assert(len(rr._entries) == 2) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 2025) def test_rrcontentry_track_toolarge(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(0, 23) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: rr.track_entry(23, 2026) assert(str(excinfo.value) == 'No room in continuation block to track entry') def test_rrcontentry_add_into_empty(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) assert(rr.add_entry(23) is not None) assert(len(rr._entries) == 1) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) def test_rrcontentry_add_at_end(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) assert(rr.add_entry(23) is not None) assert(rr.add_entry(33) is not None) assert(len(rr._entries) == 2) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 33) def test_rrcontentry_add_at_beginning(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) rr.track_entry(23, 33) assert(rr.add_entry(23) is not None) assert(len(rr._entries) == 2) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 33) def test_rrcontentry_add_multiple(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) assert(rr.add_entry(23) is not None) rr.track_entry(40, 12) assert(rr.add_entry(12) is not None) assert(len(rr._entries) == 3) assert(rr._entries[0].offset == 0) assert(rr._entries[0].length == 23) assert(rr._entries[1].offset == 23) assert(rr._entries[1].length == 12) assert(rr._entries[2].offset == 40) assert(rr._entries[2].length == 12) def test_rrcontblock_remove_entry_no_entry(): rr = pycdlib.rockridge.RockRidgeContinuationBlock(24, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: rr.remove_entry(0, 0) assert(str(excinfo.value) == 'Could not find an entry for the RR CE entry in the CE block!') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1599880065.5919952 pycdlib-1.11.0/tests/unit/test_udf.py0000664000175000017500000037123200000000000022323 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.udf # BEA def test_bea_parse_initialized_twice(): bea = pycdlib.udf.BEAVolumeStructure() bea.parse(b'\x00BEA01\x01' + b'\x00'*2041, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bea.parse(b'\x00BEA01\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'BEA Volume Structure already initialized') def test_bea_parse_bad_structure(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: bea.parse(b'\x01BEA01\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure type') def test_bea_parse_bad_ident(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: bea.parse(b'\x00CEA01\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid standard identifier') def test_bea_parse_bad_version(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: bea.parse(b'\x00BEA01\x02' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure version') def test_bea_record_not_initialized(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bea.record() assert(str(excinfo.value) == 'BEA Volume Structure not initialized') def test_bea_new_initialized_twice(): bea = pycdlib.udf.BEAVolumeStructure() bea.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bea.new() assert(str(excinfo.value) == 'BEA Volume Structure already initialized') def test_bea_extent_location_not_initialized(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bea.extent_location() assert(str(excinfo.value) == 'UDF BEA Volume Structure not initialized') def test_bea_set_extent_location_not_initialized(): bea = pycdlib.udf.BEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bea.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # NSR def test_nsr_parse_initialized_twice(): nsr = pycdlib.udf.NSRVolumeStructure() nsr.parse(b'\x00NSR02\x01' + b'\x00'*2041, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.parse(b'\x00NSR02\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'UDF NSR Volume Structure already initialized') def test_nsr_parse_bad_structure(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: nsr.parse(b'\x01NSR02\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure type') def test_nsr_parse_bad_ident(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: nsr.parse(b'\x00NSR04\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid standard identifier') def test_nsr_parse_bad_version(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: nsr.parse(b'\x00NSR02\x02' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure version') def test_nsr_record_not_initialized(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.record() assert(str(excinfo.value) == 'UDF NSR Volume Structure not initialized') def test_nsr_new_initialized_twice(): nsr = pycdlib.udf.NSRVolumeStructure() nsr.new(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.new(2) assert(str(excinfo.value) == 'UDF NSR Volume Structure already initialized') def test_nsr_new_version_three(): nsr = pycdlib.udf.NSRVolumeStructure() nsr.new(3) assert(nsr.standard_ident == b'NSR03') def test_nsr_new_bad_version(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.new(4) assert(str(excinfo.value) == 'Invalid NSR version requested') def test_nsr_extent_location_not_initialized(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.extent_location() assert(str(excinfo.value) == 'UDF NSR Volume Structure not initialized') def test_nsr_set_extent_location_not_initialized(): nsr = pycdlib.udf.NSRVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: nsr.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # TEA def test_tea_parse_initialized_twice(): tea = pycdlib.udf.TEAVolumeStructure() tea.parse(b'\x00TEA01\x01' + b'\x00'*2041, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tea.parse(b'\x00TEA01\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'TEA Volume Structure already initialized') def test_tea_parse_bad_type(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tea.parse(b'\x01TEA01\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure type') def test_tea_parse_bad_ident(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tea.parse(b'\x00TEA02\x01' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid standard identifier') def test_tea_parse_bad_version(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tea.parse(b'\x00TEA01\x02' + b'\x00'*2041, 0) assert(str(excinfo.value) == 'Invalid structure version') def test_tea_record_not_initialized(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tea.record() assert(str(excinfo.value) == 'UDF TEA Volume Structure not initialized') def test_tea_new_initialized_twice(): tea = pycdlib.udf.TEAVolumeStructure() tea.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tea.new() assert(str(excinfo.value) == 'UDF TEA Volume Structure already initialized') def test_tea_extent_location_not_initialized(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tea.extent_location() assert(str(excinfo.value) == 'UDF TEA Volume Structure not initialized') def test_tea_set_extent_location_not_initialized(): tea = pycdlib.udf.TEAVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tea.set_extent_location(0) assert(str(excinfo.value) == 'This Volume Descriptor is not initialized') # Boot Descriptor def test_boot_descriptor_parse(): boot = pycdlib.udf.UDFBootDescriptor() boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(boot.boot_extent_loc == 0) assert(boot.boot_extent_len == 0) assert(boot.load_address == 0) assert(boot.start_address == 0) assert(boot.flags == 0) def test_boot_descriptor_parse_initialized_twice(): boot = pycdlib.udf.UDFBootDescriptor() boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'UDF Boot Descriptor already initialized') def test_boot_descriptor_parse_bad_type(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x01BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid structure type') def test_boot_descriptor_parse_bad_ident(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x00BOOT1\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid standard identifier') def test_boot_descriptor_parse_bad_version(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x00BOOT2\x02\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid structure version') def test_boot_descriptor_parse_bad_reserved(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x00BOOT2\x01\x01' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid reserved1') def test_boot_descriptor_parse_bad_flags(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x02\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid flags (must be 0 or 1)') def test_boot_descriptor_parse_bad_reserved2(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x01'*32 + b'\x00'*1906, 0) assert(str(excinfo.value) == 'Invalid reserved2') def test_boot_descriptor_new(): boot = pycdlib.udf.UDFBootDescriptor() boot.new() assert(boot.flags == 0) assert(boot.boot_extent_loc == 0) assert(boot.boot_extent_len == 0) assert(boot.load_address == 0) assert(boot.start_address == 0) assert(boot.flags == 0) assert(boot._initialized) def test_boot_descriptor_new_initialized_twice(): boot = pycdlib.udf.UDFBootDescriptor() boot.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: boot.new() assert(str(excinfo.value) == 'UDF Boot Descriptor already initialized') def test_boot_descriptor_record_not_initialized(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: boot.record() assert(str(excinfo.value) == 'UDF Boot Descriptor not initialized') def test_boot_descriptor_record(): boot = pycdlib.udf.UDFBootDescriptor() boot.new() rec = boot.record() assert(rec[0:8] == b'\x00BOOT2\x01\x00') def test_boot_descriptor_extent_location_not_initialized(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: boot.extent_location() assert(str(excinfo.value) == 'UDF Boot Descriptor not initialized') def test_boot_descriptor_extent_location(): boot = pycdlib.udf.UDFBootDescriptor() boot = pycdlib.udf.UDFBootDescriptor() boot.parse(b'\x00BOOT2\x01\x00' + b'\x00'*32 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00' + b'\x00'*32 + b'\x00'*1906, 0) assert(boot.extent_location() == 0) def test_boot_descriptor_set_extent_location_not_initialized(): boot = pycdlib.udf.UDFBootDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: boot.set_extent_location(0) assert(str(excinfo.value) == 'This UDF Boot Descriptor is not initialized') def test_boot_descriptor_set_extent_location(): boot = pycdlib.udf.UDFBootDescriptor() boot.new() boot.set_extent_location(1) assert(boot.extent_location() == 1) # UDFTag def test_tag_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.parse(b'\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tag.parse(b'\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0) assert(str(excinfo.value) == 'UDF Tag already initialized') def test_tag_parse_bad_csum(): tag = pycdlib.udf.UDFTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tag.parse(b'\x00\x00\x02\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0) assert(str(excinfo.value) == 'Tag checksum does not match!') def test_tag_parse_not_enough_crc_data(): tag = pycdlib.udf.UDFTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tag.parse(b'\x00\x00\x02\x00\x03\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00', 0) assert(str(excinfo.value) == 'Not enough bytes to compute CRC') def test_tag_parse_bad_crc(): tag = pycdlib.udf.UDFTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: tag.parse(b'\x00\x00\x02\x00\x03\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\xee', 0) assert(str(excinfo.value) == 'Tag CRC does not match!') def test_tag_parse(): tag = pycdlib.udf.UDFTag() tag.parse(b'\x00\x00\x02\x00\x03\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00', 0) assert(tag.tag_ident == 0) assert(tag.desc_version == 2) assert(tag.tag_serial_number == 0) assert(tag.desc_crc_length == 1) assert(tag.tag_location == 0) assert(tag._initialized) def test_tag_record_not_initialized(): tag = pycdlib.udf.UDFTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tag.record(b'\x00') assert(str(excinfo.value) == 'UDF Descriptor Tag not initialized') def test_tag_new_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: tag.new(0, 0) assert(str(excinfo.value) == 'UDF Tag already initialized') def test_tag_eq(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) tag2 = pycdlib.udf.UDFTag() tag2.new(0, 0) assert(tag == tag2) # Anchor def test_anchor_parse_initialized_twice(): anchor = pycdlib.udf.UDFAnchorVolumeStructure() tag = pycdlib.udf.UDFTag() tag.new(0, 0) anchor.parse(b'\x00'*16 + b'\x00'*8 + b'\x00'*8 + b'\x00'*480, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: anchor.parse(b'\x00'*16 + b'\x00'*8 + b'\x00'*8 + b'\x00'*480, 0, tag) assert(str(excinfo.value) == 'Anchor Volume Structure already initialized') def test_anchor_record_not_initialized(): anchor = pycdlib.udf.UDFAnchorVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: anchor.record() assert(str(excinfo.value) == 'UDF Anchor Volume Descriptor not initialized') def test_anchor_extent_location_not_initialized(): anchor = pycdlib.udf.UDFAnchorVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: anchor.extent_location() assert(str(excinfo.value) == 'UDF Anchor Volume Structure not initialized') def test_anchor_new_initialized_twice(): anchor = pycdlib.udf.UDFAnchorVolumeStructure() anchor.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: anchor.new() assert(str(excinfo.value) == 'UDF Anchor Volume Structure already initialized') def test_anchor_set_extent_location_not_initialized(): anchor = pycdlib.udf.UDFAnchorVolumeStructure() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: anchor.set_extent_location(0, 0, 0) assert(str(excinfo.value) == 'UDF Anchor Volume Structure not initialized') # Volume Descriptor Pointer def test_vdp_parse_initialized_twice(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() tag = pycdlib.udf.UDFTag() tag.new(0, 0) vdp.parse(b'\x00'*16 + b'\x00\x00\x00x\00' + b'\x00'*8 + b'\x00'*484, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdp.parse(b'\x00'*16 + b'\x00\x00\x00x\00' + b'\x00'*8 + b'\x00'*484, 0, b'\x00'*16) assert(str(excinfo.value) == 'UDF Volume Descriptor Pointer already initialized') def test_vdp_parse(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() tag = pycdlib.udf.UDFTag() tag.new(0, 0) vdp.parse(b'\x00'*16 + b'\x00\x00\x00x\00' + b'\x00'*8 + b'\x00'*484, 0, tag) assert(vdp.orig_extent_loc == 0) assert(vdp.initialized) def test_vdp_record_not_initialized(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdp.record() assert(str(excinfo.value) == 'UDF Volume Descriptor Pointer not initialized') def test_vdp_record(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() tag = pycdlib.udf.UDFTag() tag.new(0, 0) vdp.parse(b'\x00'*16 + b'\x00\x00\x00x\00' + b'\x00'*8 + b'\x00'*484, 0, tag) assert(vdp.record() == (b'\x00\x00\x02\x00\x22\x00\x00\x00\x9c\x93\xf0\x01\x00\x00\x00\x00\x00\x00\x00\x78' + b'\x00'*492)) def test_vdp_extent_location_not_initialized(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdp.extent_location() assert(str(excinfo.value) == 'UDF Volume Descriptor Pointer not initialized') def test_vdp_extent_location_parse(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() tag = pycdlib.udf.UDFTag() tag.new(0, 0) vdp.parse(b'\x00'*16 + b'\x00\x00\x00x\00' + b'\x00'*8 + b'\x00'*484, 0, tag) assert(vdp.extent_location() == 0) def test_vdp_extent_location_new(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() vdp.new() assert(vdp.extent_location() == 0) def test_vdp_new_initialized_twice(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() vdp.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdp.new() assert(str(excinfo.value) == 'UDF Volume Descriptor Pointer already initialized') def test_vdp_new(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() vdp.new() assert(vdp.vol_seqnum == 0) assert(vdp.initialized) def test_vdp_set_extent_location_not_initialized(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: vdp.set_extent_location(1) assert(str(excinfo.value) == 'UDF Volume Descriptor Pointer not initialized') def test_vdp_set_extent_location(): vdp = pycdlib.udf.UDFVolumeDescriptorPointer() vdp.new() vdp.set_extent_location(1) assert(vdp.new_extent_loc == 1) # Timestamp def test_timestamp_parse_initialized_twice(): ts = pycdlib.udf.UDFTimestamp() ts.parse(b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ts.parse(b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Timestamp already initialized') def test_timestamp_parse_bad_tz(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x06\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF timezone') def test_timestamp_parse_bad_year(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF year') def test_timestamp_parse_bad_month(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF month') def test_timestamp_parse_bad_day(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF day') def test_timestamp_parse_bad_hour(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x01\x00\x01\x01\x20\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF hour') def test_timestamp_parse_bad_minute(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x01\x00\x01\x01\x00\x40\x00\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF minute') def test_timestamp_parse_bad_second(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: ts.parse(b'\x00\x00\x01\x00\x01\x01\x00\x00\x40\x00\x00\x00') assert(str(excinfo.value) == 'Invalid UDF second') def test_timestamp_record_not_initialized(): ts = pycdlib.udf.UDFTimestamp() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ts.record() assert(str(excinfo.value) == 'UDF Timestamp not initialized') def test_timestamp_new_initialized_twice(): ts = pycdlib.udf.UDFTimestamp() ts.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ts.new() assert(str(excinfo.value) == 'UDF Timestamp already initialized') def test_timestamp_equal(): ts = pycdlib.udf.UDFTimestamp() ts.new() ts2 = pycdlib.udf.UDFTimestamp() ts2.new() assert(ts == ts2) # EntityID def test_entityid_parse_initialized_twice(): entity = pycdlib.udf.UDFEntityID() entity.parse(b'\x00'*32) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entity.parse(b'\x00'*32) assert(str(excinfo.value) == 'UDF Entity ID already initialized') def test_entityid_parse_bad_flags(): entity = pycdlib.udf.UDFEntityID() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: entity.parse(b'\x04' + b'\x00'*31) assert(str(excinfo.value) == 'UDF Entity ID flags must be between 0 and 3') def test_entityid_record_not_initialized(): entity = pycdlib.udf.UDFEntityID() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entity.record() assert(str(excinfo.value) == 'UDF Entity ID not initialized') def test_entityid_new_initialized_twice(): entity = pycdlib.udf.UDFEntityID() entity.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entity.new(0) assert(str(excinfo.value) == 'UDF Entity ID already initialized') def test_entityid_new_bad_flags(): entity = pycdlib.udf.UDFEntityID() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entity.new(4) assert(str(excinfo.value) == 'UDF Entity ID flags must be between 0 and 3') def test_entityid_new_bad_identifier(): entity = pycdlib.udf.UDFEntityID() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entity.new(0, b'\x00'*25) assert(str(excinfo.value) == 'UDF Entity ID identifier must be less than 23 characters') def test_entityid_new_bad_suffix(): entity = pycdlib.udf.UDFEntityID() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entity.new(0, b'', b'\x00'*10) assert(str(excinfo.value) == 'UDF Entity ID suffix must be less than 8 characters') def test_entityid_equals(): entity = pycdlib.udf.UDFEntityID() entity.new(0) entity2 = pycdlib.udf.UDFEntityID() entity2.new(0) assert(entity == entity2) # Charspec def test_charspec_parse_initialized_twice(): charspec = pycdlib.udf.UDFCharspec() charspec.parse(b'\x00'*64) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: charspec.parse(b'\x00'*64) assert(str(excinfo.value) == 'UDF Charspec already initialized') def test_charspec_parse_bad_set_type(): charspec = pycdlib.udf.UDFCharspec() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: charspec.parse(b'\x09' + b'\x00'*63) assert(str(excinfo.value) == 'Invalid charset parsed; only 0-8 supported') def test_charspec_new_initialized_twice(): charspec = pycdlib.udf.UDFCharspec() charspec.new(0, b'') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: charspec.new(0, b'') assert(str(excinfo.value) == 'UDF Charspec already initialized') def test_charspec_new_bad_set_type(): charspec = pycdlib.udf.UDFCharspec() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: charspec.new(9, b'') assert(str(excinfo.value) == 'Invalid charset specified; only 0-8 supported') def test_charspec_new_bad_set_information(): charspec = pycdlib.udf.UDFCharspec() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: charspec.new(0, b'\x00'*64) assert(str(excinfo.value) == 'Invalid charset information; exceeds maximum size of 63') def test_charspec_record_not_initialized(): charspec = pycdlib.udf.UDFCharspec() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: charspec.record() assert(str(excinfo.value) == 'UDF Charspec not initialized') def test_charspec_equal(): charspec = pycdlib.udf.UDFCharspec() charspec.new(0, b'\x00'*63) charspec2 = pycdlib.udf.UDFCharspec() charspec2.new(0, b'\x00'*63) assert(charspec == charspec2) # ExtentAD def test_extentad_parse_initialized_twice(): extentad = pycdlib.udf.UDFExtentAD() extentad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: extentad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Extent descriptor already initialized') def test_extentad_parse_bad_length(): extentad = pycdlib.udf.UDFExtentAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: extentad.parse(b'\x00\x00\x00\xff\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Extent descriptor length must be less than 0x3fffffff') def test_extentad_record_not_initialized(): extentad = pycdlib.udf.UDFExtentAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: extentad.record() assert(str(excinfo.value) == 'UDF Extent AD not initialized') def test_extentad_new_initialized_twice(): extentad = pycdlib.udf.UDFExtentAD() extentad.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: extentad.new(0, 0) assert(str(excinfo.value) == 'UDF Extent AD already initialized') def test_extentad_new_bad_length(): extentad = pycdlib.udf.UDFExtentAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: extentad.new(0xff000000, 0) assert(str(excinfo.value) == 'UDF Extent AD length must be less than 0x3fffffff') def test_extentad_equals(): extentad = pycdlib.udf.UDFExtentAD() extentad.new(0, 0) extentad2 = pycdlib.udf.UDFExtentAD() extentad2.new(0, 0) assert(extentad == extentad2) # PVD def test_pvd_parse_initialized_twice(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'UDF Primary Volume Descriptor already initialized') def test_pvd_parse_bad_volseqnum(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_pvd_parse_bad_maxvolseqnum(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_pvd_parse_bad_interchangelevel(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Unsupported interchange level (only 2 and 3 supported)') def test_pvd_parse_bad_charsetlist(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_pvd_parse_bad_maxcharsetlist(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_pvd_parse_bad_flags(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x02\x00' + b'\x00'*22, 0, tag) assert(str(excinfo.value) == 'Invalid UDF flags') def test_pvd_parse_bad_reserved(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: pvd.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x01\x00\x01\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*128 + b'\x00'*64 + b'\x00'*64 + b'\x00'*8 + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*64 + b'\x00\x00\x00\x00\x00\x00' + b'\x01' + b'\x00'*21, 0, tag) assert(str(excinfo.value) == 'UDF Primary Volume Descriptor reserved data not 0') def test_pvd_record_not_initialized(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.record() assert(str(excinfo.value) == 'UDF Primary Volume Descriptor not initialized') def test_pvd_extent_location_not_initialized(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.extent_location() assert(str(excinfo.value) == 'UDF Primary Volume Descriptor not initialized') def test_pvd_new_initialized_twice(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() pvd.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.new() assert(str(excinfo.value) == 'UDF Primary Volume Descriptor already initialized') def test_pvd_set_extent_location_not_initialized(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pvd.set_extent_location(0) assert(str(excinfo.value) == 'UDF Primary Volume Descriptor not initialized') def test_pvd_equals(): pvd = pycdlib.udf.UDFPrimaryVolumeDescriptor() pvd.new() pvd2 = pycdlib.udf.UDFPrimaryVolumeDescriptor() pvd2.new() pvd2.vol_set_ident = pvd.vol_set_ident pvd2.recording_date = pvd.recording_date assert(pvd == pvd2) # Implementation Use Volume Descriptor Implementation Use def test_impl_use_impl_use_parse_initialized_twice(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptorImplementationUse() impl.parse(b'\x00'*64 + b'\x00'*128 + b'\x00'*36 + b'\x00'*36 + b'\x00'*36 + b'\x00'*32 + b'\x00'*128) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.parse(b'\x00'*64 + b'\x00'*128 + b'\x00'*36 + b'\x00'*36 + b'\x00'*36 + b'\x00'*32 + b'\x00'*128) assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor Implementation Use field already initialized') def test_impl_use_impl_use_record_not_initialized(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptorImplementationUse() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.record() assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor Implementation Use field not initialized') def test_impl_use_impl_use_new_initialized_twice(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptorImplementationUse() impl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.new() assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor Implementation Use field already initialized') def test_impl_use_impl_use_new_equals(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptorImplementationUse() impl.new() impl2 = pycdlib.udf.UDFImplementationUseVolumeDescriptorImplementationUse() impl2.new() assert(impl == impl2) # Implementation Use def test_impl_use_parse_initialized_twice(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) impl.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00*UDF LV Info' + b'\x00'*19 + b'\x00'*460, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00*UDF LV Info' + b'\x00'*19 + b'\x00'*460, 0, tag) assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor already initialized') def test_impl_use_parse_bad_ident(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: impl.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00*MDF LV Info' + b'\x00'*19 + b'\x00'*460, 0, tag) assert(str(excinfo.value) == "Implementation Use Identifier not '*UDF LV Info'") def test_impl_use_record_not_initialized(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.record() assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor not initialized') def test_impl_use_extent_location_not_initialized(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.extent_location() assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor not initialized') def test_impl_use_new_initialized_twice(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() impl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.new() assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor already initialized') def test_impl_use_set_extent_location_not_initialized(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.set_extent_location(0) assert(str(excinfo.value) == 'UDF Implementation Use Volume Descriptor not initialized') def test_impl_use_equals(): impl = pycdlib.udf.UDFImplementationUseVolumeDescriptor() impl.new() impl2 = pycdlib.udf.UDFImplementationUseVolumeDescriptor() impl2.new() assert(impl == impl2) # Partition Header Descriptor def test_part_header_parse_initialized_twice(): header = pycdlib.udf.UDFPartitionHeaderDescriptor() header.parse(b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*88) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.parse(b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*8 + b'\x00'*88) assert(str(excinfo.value) == 'UDF Partition Header Descriptor already initialized') def test_part_header_record_not_initialized(): header = pycdlib.udf.UDFPartitionHeaderDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.record() assert(str(excinfo.value) == 'UDF Partition Header Descriptor not initialized') def test_part_header_new_initialized_twice(): header = pycdlib.udf.UDFPartitionHeaderDescriptor() header.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.new() assert(str(excinfo.value) == 'UDF Partition Header Descriptor already initialized') def test_part_header_equals(): header = pycdlib.udf.UDFPartitionHeaderDescriptor() header.new() header2 = pycdlib.udf.UDFPartitionHeaderDescriptor() header2.new() assert(header == header2) # Partition Volume def test_part_parse_initialized_twice(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00+NSR02' + b'\x00'*25 + b'\x00'*128 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*156, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00+NSR02' + b'\x00'*25 + b'\x00'*128 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*156, 0, tag) assert(str(excinfo.value) == 'UDF Partition Volume Descriptor already initialized') def test_part_parse_bad_flags(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x02\x00\x00\x00' + b'\x00+NSR02' + b'\x00'*25 + b'\x00'*128 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*156, 0, tag) assert(str(excinfo.value) == 'Invalid partition flags') def test_part_parse_bad_entity(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00+NSR04' + b'\x00'*25 + b'\x00'*128 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*156, 0, tag) assert(str(excinfo.value) == "Partition Contents Identifier not '+FDC01', '+CD001', '+CDW02', '+NSR02', or '+NSR03'") def test_part_parse_bad_access_type(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00+NSR02' + b'\x00'*25 + b'\x00'*128 + b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*156, 0, tag) assert(str(excinfo.value) == 'Invalid UDF partition access type') def test_part_record_not_initialized(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.record() assert(str(excinfo.value) == 'UDF Partition Volume Descriptor not initialized') def test_part_extent_location_not_initialized(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.extent_location() assert(str(excinfo.value) == 'UDF Partition Volume Descriptor not initialized') def test_part_new_initialized_twice(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() part.new(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.new(2) assert(str(excinfo.value) == 'UDF Partition Volume Descriptor already initialized') def test_part_new_initialized_version3(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() part.new(3) assert(part.part_contents.identifier[:6] == b'+NSR03') def test_part_new_bad_version(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.new(4) assert(str(excinfo.value) == 'Invalid NSR version requested') def test_part_set_extent_location_not_initialized(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.set_extent_location(0) assert(str(excinfo.value) == 'UDF Partition Volume Descriptor not initialized') def test_part_set_start_location_not_initialized(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.set_start_location(0) assert(str(excinfo.value) == 'UDF Partition Volume Descriptor not initialized') def test_part_equals(): part = pycdlib.udf.UDFPartitionVolumeDescriptor() part.new(3) part2 = pycdlib.udf.UDFPartitionVolumeDescriptor() part2.new(3) assert(part == part2) # Type 0 Partition Map def test_type_zero_part_map_parse_initialized_twice(): partmap = pycdlib.udf.UDFType0PartitionMap() partmap.parse(b'\x00\x02') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.parse(b'\x00\x02') assert(str(excinfo.value) == 'UDF Type 0 Partition Map already initialized') def test_type_zero_part_map_parse_bad_map_type(): partmap = pycdlib.udf.UDFType0PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x01\x00') assert(str(excinfo.value) == 'UDF Type 0 Partition Map type is not 0') def test_type_zero_part_map_parse_bad_map_length(): partmap = pycdlib.udf.UDFType0PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x00\x01') assert(str(excinfo.value) == 'UDF Type 0 Partition Map length does not equal data length') def test_type_zero_part_map_parse(): partmap = pycdlib.udf.UDFType0PartitionMap() partmap.parse(b'\x00\x02') assert(partmap._initialized) def test_type_zero_part_map_record_not_initialized(): partmap = pycdlib.udf.UDFType0PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.record() assert(str(excinfo.value) == 'UDF Type 0 Partition Map not initialized') def test_type_zero_part_map_record(): partmap = pycdlib.udf.UDFType0PartitionMap() partmap.parse(b'\x00\x02') assert(partmap.record() == b'\x00\x02') def test_type_zero_part_map_new_initialized_twice(): partmap = pycdlib.udf.UDFType0PartitionMap() partmap.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.new() assert(str(excinfo.value) == 'UDF Type 0 Partition Map already initialized') def test_type_zero_part_map_new(): partmap = pycdlib.udf.UDFType0PartitionMap() partmap.new() assert(partmap._initialized) # Type 1 Partition Map def test_type_one_part_map_parse_initialized_twice(): partmap = pycdlib.udf.UDFType1PartitionMap() partmap.parse(b'\x01\x06\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.parse(b'\x01\x06\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Type 1 Partition Map already initialized') def test_type_one_part_map_parse_bad_map_type(): partmap = pycdlib.udf.UDFType1PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x00\x06\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Type 1 Partition Map type is not 1') def test_type_one_part_map_parse_bad_map_length(): partmap = pycdlib.udf.UDFType1PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x01\x05\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Type 1 Partition Map length is not 6') def test_type_one_part_map_record_not_initialized(): partmap = pycdlib.udf.UDFType1PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.record() assert(str(excinfo.value) == 'UDF Type 1 Partition Map not initialized') def test_type_one_part_map_new_initialized_twice(): partmap = pycdlib.udf.UDFType1PartitionMap() partmap.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.new() assert(str(excinfo.value) == 'UDF Type 1 Partition Map already initialized') # Type 2 Partition Map def test_type_two_part_map_parse_initialized_twice(): partmap = pycdlib.udf.UDFType2PartitionMap() partmap.parse(b'\x02\x40' + b'\x00'*62) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.parse(b'\x02\x40' + b'\x00'*62) assert(str(excinfo.value) == 'UDF Type 2 Partition Map already initialized') def test_type_two_part_map_parse_bad_map_type(): partmap = pycdlib.udf.UDFType2PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x00\x40' + b'\x00'*62) assert(str(excinfo.value) == 'UDF Type 2 Partition Map type is not 2') def test_type_two_part_map_parse_bad_map_length(): partmap = pycdlib.udf.UDFType2PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: partmap.parse(b'\x02\x20' + b'\x00'*62) assert(str(excinfo.value) == 'UDF Type 2 Partition Map length is not 64') def test_type_two_part_map_parse(): partmap = pycdlib.udf.UDFType2PartitionMap() partmap.parse(b'\x02\x40' + b'\x00'*62) assert(partmap._initialized) def test_type_two_part_map_record_not_initialized(): partmap = pycdlib.udf.UDFType2PartitionMap() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.record() assert(str(excinfo.value) == 'UDF Type 2 Partition Map not initialized') def test_type_two_part_map_record(): partmap = pycdlib.udf.UDFType2PartitionMap() partmap.parse(b'\x02\x40' + b'\x00'*62) assert(partmap.record() == (b'\x02\x40' + b'\x00'*62)) def test_type_two_part_map_new_initialized_twice(): partmap = pycdlib.udf.UDFType2PartitionMap() partmap.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: partmap.new() assert(str(excinfo.value) == 'UDF Type 2 Partition Map already initialized') def test_type_two_part_map_new(): partmap = pycdlib.udf.UDFType2PartitionMap() partmap.new() assert(partmap._initialized) # Extended AD def test_extendedad_parse_initialized_twice(): ad = pycdlib.udf.UDFExtendedAD() ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*6 + b'\x00'*2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*6 + b'\x00'*2) assert(str(excinfo.value) == 'UDF Extended Allocation descriptor already initialized') def test_extendedad_parse(): ad = pycdlib.udf.UDFExtendedAD() ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*6 + b'\x00'*2) assert(ad._initialized) assert(ad.extent_length == 0) def test_extendedad_record_not_initialized(): ad = pycdlib.udf.UDFExtendedAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.record() assert(str(excinfo.value) == 'UDF Extended Allocation Descriptor not initialized') def test_extendedad_record(): ad = pycdlib.udf.UDFExtendedAD() ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*6 + b'\x00'*2) rec = ad.record() assert(rec == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*6 + b'\x00'*2) def test_extendedad_new_initialized_twice(): ad = pycdlib.udf.UDFExtendedAD() ad.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.new() assert(str(excinfo.value) == 'UDF Extended Allocation Descriptor already initialized') def test_extendedad_new(): ad = pycdlib.udf.UDFExtendedAD() ad.new() assert(ad._initialized) assert(ad.extent_length == 0) # Short AD def test_shortad_parse_initialized_twice(): ad = pycdlib.udf.UDFShortAD() ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Short Allocation descriptor already initialized') def test_shortad_record_not_initialized(): ad = pycdlib.udf.UDFShortAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.record() assert(str(excinfo.value) == 'UDF Short AD not initialized') def test_shortad_new_initialized_twice(): ad = pycdlib.udf.UDFShortAD() ad.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.new(0) assert(str(excinfo.value) == 'UDF Short AD already initialized') def test_shortad_new_bad_length(): ad = pycdlib.udf.UDFShortAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.new(0x40000000) assert(str(excinfo.value) == 'UDF Short AD length must be less than or equal to 0x3fffffff') def test_shortad_set_extent_location_not_initialized(): ad = pycdlib.udf.UDFShortAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.set_extent_location(0, 0) assert(str(excinfo.value) == 'UDF Short AD not initialized') def test_shortad_set_extent_location(): ad = pycdlib.udf.UDFShortAD() ad.new(0) ad.set_extent_location(0, 1) assert(ad.log_block_num == 1) # Long AD def test_longad_parse_initialized_twice(): ad = pycdlib.udf.UDFLongAD() ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.parse(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Long Allocation descriptor already initialized') def test_longad_record_not_initialized(): ad = pycdlib.udf.UDFLongAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.record() assert(str(excinfo.value) == 'UDF Long AD not initialized') def test_longad_new_initialized_twice(): ad = pycdlib.udf.UDFLongAD() ad.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.new(0, 0) assert(str(excinfo.value) == 'UDF Long AD already initialized') def test_longad_set_extent_location_not_initialized(): ad = pycdlib.udf.UDFLongAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.set_extent_location(0, 0) assert(str(excinfo.value) == 'UDF Long AD not initialized') def test_longad_length(): ad = pycdlib.udf.UDFLongAD() assert(ad.length() == 16) def test_longad_equals(): ad = pycdlib.udf.UDFLongAD() ad.new(0, 0) ad2 = pycdlib.udf.UDFLongAD() ad2.new(0, 0) assert(ad == ad2) # Inline AD def test_inlinead_parse_initialized_twice(): ad = pycdlib.udf.UDFInlineAD() ad.parse(0, 0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.parse(0, 0, 0) assert(str(excinfo.value) == 'UDF Inline Allocation Descriptor already initialized') def test_inlinead_record_not_initialized(): ad = pycdlib.udf.UDFInlineAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.record() assert(str(excinfo.value) == 'UDF Inline AD not initialized') def test_inlinead_record(): ad = pycdlib.udf.UDFInlineAD() ad.parse(0, 0, 0) assert(ad.record() == b'') def test_inlinead_new_initialized_twice(): ad = pycdlib.udf.UDFInlineAD() ad.new(0, 0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.new(0, 0, 0) assert(str(excinfo.value) == 'UDF Inline AD already initialized') def test_inlinead_set_extent_location_not_initialized(): ad = pycdlib.udf.UDFInlineAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.set_extent_location(1, 1) assert(str(excinfo.value) == 'UDF Inline AD not initialized') def test_inlinead_set_extent_location(): ad = pycdlib.udf.UDFInlineAD() ad.new(0, 0, 0) ad.set_extent_location(1, 1) assert(ad.log_block_num == 1) def test_inlinead_length_not_initialized(): ad = pycdlib.udf.UDFInlineAD() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ad.length() assert(str(excinfo.value) == 'UDF Inline AD not initialized') def test_inlinead_length(): ad = pycdlib.udf.UDFInlineAD() ad.new(1, 0, 0) assert(ad.length() == 1) # Logical Volume Descriptor def test_logvoldesc_parse_initialized_twice(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00'*72, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00'*72, 0, tag) assert(str(excinfo.value) == 'UDF Logical Volume Descriptor already initialized') def test_logvoldesc_parse_bad_logical_block_size(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x07\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00'*72, 0, tag) assert(str(excinfo.value) == 'Volume Descriptor block size is not 2048') def test_logvoldesc_parse_bad_domain_ident(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00$OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00'*72, 0, tag) assert(str(excinfo.value) == "Volume Descriptor Identifier not '*OSTA UDF Compliant'") def test_logvoldesc_parse_bad_map_table_length(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x10\x00\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00'*72, 0, tag) assert(str(excinfo.value) == 'Map table length greater than size of partition map data; ISO corrupt') def test_logvoldesc_parse_bad_map_data(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00\x80' + b'\x00'*70, 0, tag) assert(str(excinfo.value) == 'Partition map goes beyond end of data, ISO corrupt') def test_logvoldesc_parse_not_enough_map_data_left(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00\x20' + b'\x00'*70, 0, tag) assert(str(excinfo.value) == 'Partition map goes beyond map_table_length left, ISO corrupt') def test_logvoldesc_parse_map_type_0(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x02\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x00\x02' + b'\x00'*70, 0, tag) assert(len(logvol.partition_maps) == 1) assert(isinstance(logvol.partition_maps[0], pycdlib.udf.UDFType0PartitionMap)) def test_logvoldesc_parse_map_type_2(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x42\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x02\x40' + b'\x00'*70, 0, tag) assert(len(logvol.partition_maps) == 1) assert(isinstance(logvol.partition_maps[0], pycdlib.udf.UDFType2PartitionMap)) def test_logvoldesc_parse_bad_map_type(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: logvol.parse(b'\x00'*16 + b'\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00\x08\x00\x00' + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x42\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*32 + b'\x00'*128 + b'\x00'*8 + b'\x03\x40' + b'\x00'*70, 0, tag) assert(str(excinfo.value) == 'Unsupported partition map type') def test_logvoldesc_record_not_initialized(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.record() assert(str(excinfo.value) == 'UDF Logical Volume Descriptor not initialized') def test_logvoldesc_extent_location_not_initialized(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.extent_location() assert(str(excinfo.value) == 'UDF Logical Volume Descriptor not initialized') def test_logvoldesc_new_initialized_twice(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.new() assert(str(excinfo.value) == 'UDF Logical Volume Descriptor already initialized') def test_logvoldesc_add_partition_map_not_initialized(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.add_partition_map(1) assert(str(excinfo.value) == 'UDF Logical Volume Descriptor not initialized') def test_logvoldesc_add_partition_map_type_0(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() logvol.add_partition_map(0) assert(len(logvol.partition_maps) == 1) def test_logvoldesc_add_partition_map_type_2(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() logvol.add_partition_map(2) assert(len(logvol.partition_maps) == 1) def test_logvoldesc_add_partition_map_invalid_type(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.add_partition_map(3) assert(str(excinfo.value) == 'UDF Partition map type must be 0, 1, or 2') def test_logvoldesc_add_partition_map_too_many_maps(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() logvol.add_partition_map(2) logvol.add_partition_map(2) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.add_partition_map(2) assert(str(excinfo.value) == 'Too many UDF partition maps') def test_logvoldesc_set_extent_location_not_initialized(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.set_extent_location(0) assert(str(excinfo.value) == 'UDF Logical Volume Descriptor not initialized') def test_logvoldesc_set_integrity_location_not_initialized(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: logvol.set_integrity_location(0) assert(str(excinfo.value) == 'UDF Logical Volume Descriptor not initialized') def test_logvoldesc_equals(): logvol = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol.new() logvol2 = pycdlib.udf.UDFLogicalVolumeDescriptor() logvol2.new() assert(logvol == logvol2) # Unallocated Space def test_unallocated_parse_initialized_twice(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) un.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*488, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: un.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*488, 0, tag) assert(str(excinfo.value) == 'UDF Unallocated Space Descriptor already initialized') def test_unallocated_parse_too_many_alloc_descs(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: un.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x3e\x00\x00\x00' + b'\x00'*488, 0, tag) assert(str(excinfo.value) == 'Too many allocation descriptors') def test_unallocated_parse_alloc_descs(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) un.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*488, 0, tag) assert(len(un.alloc_descs) == 1) def test_unallocated_record_not_initialized(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: un.record() assert(str(excinfo.value) == 'UDF Unallocated Space Descriptor not initialized') def test_unallocated_record_alloc_descs(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) un.parse(b'\x00\x00\x02\x00\x05\x00\x00\x00\x52\xc0\xf0\x01\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*488, 0, tag) assert(un.record() == b'\x00\x00\x02\x00\x05\x00\x00\x00\x52\xc0\xf0\x01\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*488) def test_unallocated_extent_location_not_initialized(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: un.extent_location() assert(str(excinfo.value) == 'UDF Unallocated Space Descriptor not initialized') def test_unallocated_new_initialized_twice(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() un.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: un.new() assert(str(excinfo.value) == 'UDF Unallocated Space Descriptor already initialized') def test_unallocated_set_extent_location_not_initialized(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: un.set_extent_location(0) assert(str(excinfo.value) == 'UDF Unallocated Space Descriptor not initialized') def test_unallocated_equals(): un = pycdlib.udf.UDFUnallocatedSpaceDescriptor() un.new() un2 = pycdlib.udf.UDFUnallocatedSpaceDescriptor() un2.new() assert(un == un2) # Terminating Descriptor def test_terminating_parse_initialized_twice(): term = pycdlib.udf.UDFTerminatingDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) term.parse(0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.parse(0, tag) assert(str(excinfo.value) == 'UDF Terminating Descriptor already initialized') def test_terminating_record_not_initialized(): term = pycdlib.udf.UDFTerminatingDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.record() assert(str(excinfo.value) == 'UDF Terminating Descriptor not initialized') def test_terminating_extent_location_not_initialized(): term = pycdlib.udf.UDFTerminatingDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.extent_location() assert(str(excinfo.value) == 'UDF Terminating Descriptor not initialized') def test_terminating_new_initialized_twice(): term = pycdlib.udf.UDFTerminatingDescriptor() term.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.new() assert(str(excinfo.value) == 'UDF Terminating Descriptor already initialized') def test_terminating_set_extent_location_not_initialized(): term = pycdlib.udf.UDFTerminatingDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.set_extent_location(0) assert(str(excinfo.value) == 'UDF Terminating Descriptor not initialized') # Logical Volume Header def test_logvol_header_parse_initialized_twice(): header = pycdlib.udf.UDFLogicalVolumeHeaderDescriptor() header.parse(b'\x00'*8 + b'\x00'*24) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.parse(b'\x00'*8 + b'\x00'*24) assert(str(excinfo.value) == 'UDF Logical Volume Header Descriptor already initialized') def test_logvol_header_record_not_initialized(): header = pycdlib.udf.UDFLogicalVolumeHeaderDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.record() assert(str(excinfo.value) == 'UDF Logical Volume Header Descriptor not initialized') def test_logvol_header_new_initialized_twice(): header = pycdlib.udf.UDFLogicalVolumeHeaderDescriptor() header.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: header.new() assert(str(excinfo.value) == 'UDF Logical Volume Header Descriptor already initialized') # Logical Volume Implementation Use def test_logvol_impl_parse_initialized_twice(): impl = pycdlib.udf.UDFLogicalVolumeImplementationUse() impl.parse(b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.parse(b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF Logical Volume Implementation Use already initialized') def test_logvol_impl_record_not_initialized(): impl = pycdlib.udf.UDFLogicalVolumeImplementationUse() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.record() assert(str(excinfo.value) == 'UDF Logical Volume Implementation Use not initialized') def test_logvol_impl_new_initialized_twice(): impl = pycdlib.udf.UDFLogicalVolumeImplementationUse() impl.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: impl.new() assert(str(excinfo.value) == 'UDF Logical Volume Implementation Use already initialized') # Logical Volume Integrity def test_logvol_integrity_parse_initialized_twice(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) integrity.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*432, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: integrity.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*432, 0, tag) assert(str(excinfo.value) == 'UDF Logical Volume Integrity Descriptor already initialized') def test_logvol_integrity_parse_bad_integrity_type(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: integrity.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x02\x00\x00\x00' + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*432, 0, tag) assert(str(excinfo.value) == 'Logical Volume Integrity Type not 0 or 1') def test_logvol_integrity_parse_bad_impl_use(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: integrity.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*8 + b'\x00'*32 + b'\x00\x00\x00\x00\xb1\x01\x00\x00' + b'\x00'*432, 0, tag) assert(str(excinfo.value) == 'UDF Logical Volume Integrity specified an implementation use that is too large') def test_logvol_integrity_record_not_initialized(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: integrity.record() assert(str(excinfo.value) == 'UDF Logical Volume Integrity Descriptor not initialized') def test_logvol_integrity_extent_location_not_initialized(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: integrity.extent_location() assert(str(excinfo.value) == 'UDF Logical Volume Integrity Descriptor not initialized') def test_logvol_integrity_new_initialized_twice(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() integrity.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: integrity.new() assert(str(excinfo.value) == 'UDF Logical Volume Integrity Descriptor already initialized') def test_logvol_integrity_set_extent_location_not_initialized(): integrity = pycdlib.udf.UDFLogicalVolumeIntegrityDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: integrity.set_extent_location(0) assert(str(excinfo.value) == 'UDF Logical Volume Integrity Descriptor not initialized') # File Set def test_file_set_parse_initialized_twice(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'UDF File Set Descriptor already initialized') def test_file_set_parse_bad_interchange(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x02\x00\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_file_set_parse_bad_max_interchange(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x02\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_file_set_parse_bad_char_set_list(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_file_set_parse_bad_max_char_set_list(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_file_set_parse_bad_file_set_desc_num(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*OSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == 'Only DVD Read-Only disks are supported') def test_file_set_parse_bad_domain_ident(): fileset = pycdlib.udf.UDFFileSetDescriptor() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fileset.parse(b'\x00'*16 + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x03\x00\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*64 + b'\x00'*128 + b'\x00'*64 + b'\x00'*32 + b'\x00'*32 + b'\x00'*32 + b'\x00'*16 + b'\x00*MSTA UDF Compliant' + b'\x00'*12 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32, 0, tag) assert(str(excinfo.value) == "File Set Descriptor Identifier not '*OSTA UDF Compliant'") def test_file_set_record_not_initialized(): fileset = pycdlib.udf.UDFFileSetDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fileset.record() assert(str(excinfo.value) == 'UDF File Set Descriptor not initialized') def test_file_set_extent_location_not_initialized(): fileset = pycdlib.udf.UDFFileSetDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fileset.extent_location() assert(str(excinfo.value) == 'UDF File Set Descriptor not initialized') def test_file_set_new_initialized_twice(): fileset = pycdlib.udf.UDFFileSetDescriptor() fileset.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fileset.new() assert(str(excinfo.value) == 'UDF File Set Descriptor already initialized') def test_file_set_set_extent_location_not_initialized(): fileset = pycdlib.udf.UDFFileSetDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fileset.set_extent_location(0) assert(str(excinfo.value) == 'UDF File Set Descriptor not initialized') # LBAddr def test_lbaddr_parse_initialized_twice(): lb = pycdlib.udf.UDFLBAddr() lb.parse(b'\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: lb.parse(b'\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF LBAddr already initialized') def test_lbaddr_record_not_initialized(): lb = pycdlib.udf.UDFLBAddr() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: lb.record() assert(str(excinfo.value) == 'UDF LBAddr not initialized') def test_lbaddr_new_initialized_twice(): lb = pycdlib.udf.UDFLBAddr() lb.new(0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: lb.new(0) assert(str(excinfo.value) == 'UDF LBAddr already initialized') # ICBTag def test_icbtag_parse_initialized_twice(): icb = pycdlib.udf.UDFICBTag() icb.parse(b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: icb.parse(b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') assert(str(excinfo.value) == 'UDF ICB Tag already initialized') def test_icbtag_record_not_initialized(): icb = pycdlib.udf.UDFICBTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: icb.record() assert(str(excinfo.value) == 'UDF ICB Tag not initialized') def test_icbtag_new_initialized_twice(): icb = pycdlib.udf.UDFICBTag() icb.new('dir') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: icb.new('dir') assert(str(excinfo.value) == 'UDF ICB Tag already initialized') def test_icbtag_new_bad_file_type(): icb = pycdlib.udf.UDFICBTag() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: icb.new('foo') assert(str(excinfo.value) == "Invalid file type for ICB; must be one of 'dir', 'file', or 'symlink'") # File Entry def test_file_entry_parse_initialized_twice(): entry = pycdlib.udf.UDFFileEntry() tag = pycdlib.udf.UDFTag() tag.new(0, 0) entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) assert(str(excinfo.value) == 'UDF File Entry already initialized') def test_file_entry_parse_bad_record_format(): entry = pycdlib.udf.UDFFileEntry() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) assert(str(excinfo.value) == 'File Entry record format is not 0') def test_file_entry_parse_bad_record_display_attrs(): entry = pycdlib.udf.UDFFileEntry() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) assert(str(excinfo.value) == 'File Entry record display attributes is not 0') def test_file_entry_parse_bad_record_len(): entry = pycdlib.udf.UDFFileEntry() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) assert(str(excinfo.value) == 'File Entry record length is not 0') def test_file_entry_parse_bad_checkpoint(): entry = pycdlib.udf.UDFFileEntry() tag = pycdlib.udf.UDFTag() tag.new(0, 0) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: entry.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, None, tag) assert(str(excinfo.value) == 'Only DVD Read-only disks supported') def test_file_entry_record_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.record() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_extent_location_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.extent_location() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_new_initialized_twice(): entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.new(0, 'dir', None, 2048) assert(str(excinfo.value) == 'UDF File Entry already initialized') def test_file_entry_new_bad_file_type(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.new(0, 'foo', None, 2048) assert(str(excinfo.value) == "UDF File Entry file type must be one of 'dir', 'file', or 'symlink'") def test_file_entry_set_extent_location_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_extent_location(0, 0) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_add_file_ident_desc_not_initialized(): entry = pycdlib.udf.UDFFileEntry() desc = pycdlib.udf.UDFFileIdentifierDescriptor() desc.new(False, False, b'foo', None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.add_file_ident_desc(desc, 2048) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_add_file_ident_desc_bad_file_type(): entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'file', None, 2048) desc = pycdlib.udf.UDFFileIdentifierDescriptor() desc.new(False, False, b'foo', None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entry.add_file_ident_desc(desc, 2048) assert(str(excinfo.value) == 'Can only add a UDF File Identifier to a directory') def test_file_entry_remove_file_ident_desc_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.remove_file_ident_desc_by_name(b'foo', 2048) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_remove_file_ident_desc_file_not_found(): entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) desc = pycdlib.udf.UDFFileIdentifierDescriptor() desc.new(False, False, b'foo', None) entry.add_file_ident_desc(desc, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as excinfo: entry.remove_file_ident_desc_by_name(b'bar', 2048) assert(str(excinfo.value) == 'Cannot find file to remove') def test_file_entry_set_data_location_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_data_location(0, 0) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_get_data_length_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.get_data_length() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_set_data_length_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.set_data_length(0) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_is_file_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.is_file() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_is_symlink_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.is_symlink() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_is_dir_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.is_dir() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_file_identifier_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.file_identifier() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_find_file_ident_desc_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.find_file_ident_desc_by_name(b'') assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_track_file_ident_desc_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.track_file_ident_desc(None) assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_is_dot_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.is_dot() assert(str(excinfo.value) == 'UDF File Entry not initialized') def test_file_entry_is_dotdot_not_initialized(): entry = pycdlib.udf.UDFFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: entry.is_dotdot() assert(str(excinfo.value) == 'UDF File Entry not initialized') # File Identifier def test_file_ident_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.parse(b'\x00'*16 + b'\x01\x00\x08\x00' + b'\x00'*16 + b'\x00\x00', 0, tag, entry) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.parse(b'\x00'*16 + b'\x01\x00\x08\x00' + b'\x00'*16 + b'\x00\x00', 0, tag, entry) assert(str(excinfo.value) == 'UDF File Identifier Descriptor already initialized') def test_file_ident_parse_bad_file_version(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fi.parse(b'\x00'*16 + b'\x00\x00\x08\x00' + b'\x00'*16 + b'\x00\x00', 0, tag, entry) assert(str(excinfo.value) == 'File Identifier Descriptor file version number not 1') def test_file_ident_parse_bad_encoding(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: fi.parse(b'\x00'*16 + b'\x01\x00\x00\x01' + b'\x00'*16 + b'\x00\x00\x00', 0, tag, entry) assert(str(excinfo.value) == 'Only UDF File Identifier Descriptor Encodings 8 or 16 are supported') def test_file_ident_is_dir_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.is_dir() assert(str(excinfo.value) == 'UDF File Identifier Descriptor not initialized') def test_file_ident_is_parent_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.is_parent() assert(str(excinfo.value) == 'UDF File Identifier Descriptor not initialized') def test_file_ident_record_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.record() assert(str(excinfo.value) == 'UDF File Identifier Descriptor not initialized') def test_file_ident_record_bad_encoding(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) entry = pycdlib.udf.UDFFileEntry() entry.new(0, 'dir', None, 2048) fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.parse(b'\x00'*16 + b'\x01\x00\x08\x01' + b'\x00'*16 + b'\x00\x00\x00', 0, tag, entry) fi.encoding = 'bad' with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.record() assert(str(excinfo.value) == 'Invalid UDF encoding; this should not happen') def test_file_ident_extent_location_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.extent_location() assert(str(excinfo.value) == 'UDF File Identifier not initialized') def test_file_ident_new_initialized_twice(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, False, b'foo', None) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.new(False, False, b'foo', None) assert(str(excinfo.value) == 'UDF File Identifier already initialized') def test_file_ident_set_extent_location_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.set_extent_location(0, 0) assert(str(excinfo.value) == 'UDF File Identifier not initialized') def test_file_ident_set_icb_not_initialized(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: fi.set_icb(0, 0) assert(str(excinfo.value) == 'UDF File Identifier not initialized') def test_file_ident_lt(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, False, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, False, b'hoo', None) assert(fi < fi2) def test_file_ident_lt_both_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, True, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, True, b'hoo', None) assert(not(fi < fi2)) def test_file_ident_lt_one_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, True, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, False, b'hoo', None) assert(fi < fi2) def test_file_ident_lt_other_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, False, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, True, b'hoo', None) assert(not(fi < fi2)) def test_file_ident_equals(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, False, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, False, b'foo', None) assert(fi == fi2) def test_file_ident_equals_both_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, True, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, True, b'foo', None) assert(fi == fi2) def test_file_ident_equals_both_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, True, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, True, b'foo', None) assert(fi == fi2) def test_file_ident_equals_other_parent(): fi = pycdlib.udf.UDFFileIdentifierDescriptor() fi.new(False, False, b'foo', None) fi2 = pycdlib.udf.UDFFileIdentifierDescriptor() fi2.new(False, True, b'foo', None) assert(fi != fi2) # Space Bitmap def test_space_bitmap_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*24, 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bitmap.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*24, 0, tag) assert(str(excinfo.value) == 'UDF Space Bitmap Descriptor already initialized') def test_space_bitmap_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*24, 0, tag) assert(bitmap.num_bits == 0) assert(bitmap.num_bytes == 0) def test_space_bitmap_record_not_initialized(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bitmap.record() assert(str(excinfo.value) == 'UDF Space Bitmap Descriptor not initialized') def test_space_bitmap_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*24, 0, tag) assert(bitmap.record() == b'\x00\x00\x02\x00\x22\x00\x00\x00\x00\x00\x20' + b'\x00'*37) def test_space_bitmap_new_initialized_twice(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bitmap.new() assert(str(excinfo.value) == 'UDF Space Bitmap Descriptor already initialized') def test_space_bitmap_new(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.new() assert(bitmap.num_bits == 0) assert(bitmap.num_bytes == 0) def test_space_bitmap_extent_location_not_initialized(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bitmap.extent_location() assert(str(excinfo.value) == 'UDF Space Bitmap Descriptor not initialized') def test_space_bitmap_extent_location_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*24, 0, tag) assert(bitmap.extent_location() == 0) def test_space_bitmap_extent_location_new(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.new() bitmap.set_extent_location(0) assert(bitmap.extent_location() == 0) def test_space_bitmap_set_extent_location_not_initialized(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: bitmap.set_extent_location(0) assert(str(excinfo.value) == 'UDF Space Bitmap Descriptor not initialized') def test_space_bitmap_set_extent_location(): bitmap = pycdlib.udf.UDFSpaceBitmapDescriptor() bitmap.new() bitmap.set_extent_location(1) assert(bitmap.extent_location() == 1) # Allocation Extent def test_alloc_extent_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(str(excinfo.value) == 'UDF Allocation Extent Descriptor already initialized') def test_alloc_extent_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(alloc.prev_allocation_extent_loc == 0) assert(alloc.len_allocation_descs == 0) def test_alloc_extent_record_not_initialized(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc.record() assert(str(excinfo.value) == 'UDF Allocation Extent Descriptor not initialized') def test_alloc_extent_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(alloc.record() == b'\x00\x00\x02\x00\x0a\x00\x00\x00\x00\x00\x08' + b'\x00'*13) def test_alloc_extent_new_initialized_twice(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc.new() assert(str(excinfo.value) == 'UDF Allocation Extent Descriptor already initialized') def test_alloc_extent_new(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.new() assert(alloc.prev_allocation_extent_loc == 0) assert(alloc.len_allocation_descs == 0) def test_alloc_extent_extent_location_not_initialized(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc.extent_location() assert(str(excinfo.value) == 'UDF Allocation Extent Descriptor not initialized') def test_alloc_extent_extent_location_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(alloc.extent_location() == 0) def test_alloc_extent_extent_location_new(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.new() alloc.set_extent_location(0) assert(alloc.extent_location() == 0) def test_alloc_extent_set_extent_location_not_initialized(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc.set_extent_location(0) assert(str(excinfo.value) == 'UDF Allocation Extent Descriptor not initialized') def test_alloc_extent_set_extent_location(): alloc = pycdlib.udf.UDFAllocationExtentDescriptor() alloc.new() alloc.set_extent_location(1) assert(alloc.extent_location() == 1) # Indirect Entry def test_indirect_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) indirect = pycdlib.udf.UDFIndirectEntry() indirect.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: indirect.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16, tag) assert(str(excinfo.value) == 'UDF Indirect Entry already initialized') def test_indirect_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) indirect = pycdlib.udf.UDFIndirectEntry() indirect.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16, tag) assert(indirect.desc_tag.tag_ident == 0) def test_indirect_record_not_initialized(): indirect = pycdlib.udf.UDFIndirectEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: indirect.record() assert(str(excinfo.value) == 'UDF Indirect Entry not initialized') def test_indirect_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) indirect = pycdlib.udf.UDFIndirectEntry() indirect.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16, tag) assert(indirect.record() == b'\x00\x00\x02\x00\xd4\x00\x00\x00\x39\x75\x24' + b'\x00'*5 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00'*16) def test_indirect_new_initialized_twice(): indirect = pycdlib.udf.UDFIndirectEntry() indirect.new('dir') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: indirect.new('dir') assert(str(excinfo.value) == 'UDF Indirect Entry already initialized') def test_indirect_new(): indirect = pycdlib.udf.UDFIndirectEntry() indirect.new('dir') assert(indirect.desc_tag.tag_ident == 259) # Terminating def test_terminating_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) term = pycdlib.udf.UDFTerminalEntry() term.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.parse(b'\x00'*16 + b'\x00'*20, tag) assert(str(excinfo.value) == 'UDF Terminal Entry already initialized') def test_terminating_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) term = pycdlib.udf.UDFTerminalEntry() term.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', tag) assert(term.desc_tag.tag_ident == 0) def test_terminating_record_not_initialized(): term = pycdlib.udf.UDFTerminalEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.record() assert(str(excinfo.value) == 'UDF Terminal Entry not initialized') def test_terminating_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) term = pycdlib.udf.UDFTerminalEntry() term.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', tag) assert(term.record() == b'\x00\x00\x02\x00\x68\x00\x00\x00\xd2\x80\x14\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_terminating_new_initialized_twice(): term = pycdlib.udf.UDFTerminalEntry() term.new('dir') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: term.new('dir') assert(str(excinfo.value) == 'UDF Terminal Entry already initialized') def test_terminating_new(): term = pycdlib.udf.UDFTerminalEntry() term.new('dir') assert(term.desc_tag.tag_ident == 260) # Extended Attribute Header def test_ext_header_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() ext.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ext.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', tag) assert(str(excinfo.value) == 'UDF Extended Attribute Header Descriptor already initialized') def test_ext_header_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() ext.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', tag) assert(ext.desc_tag.tag_ident == 0) def test_ext_header_record_not_initialized(): ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ext.record() assert(str(excinfo.value) == 'UDF Extended Attribute Header Descriptor not initialized') def test_ext_header_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() ext.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x00\x00\x00\x00', tag) assert(ext.record() == b'\x00\x00\x02\x00\x0a\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00' + b'\x00'*8) def test_ext_header_new_initialized_twice(): ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() ext.new() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ext.new() assert(str(excinfo.value) == 'UDF Extended Attribute Header Descriptor already initialized') def test_ext_header_new(): ext = pycdlib.udf.UDFExtendedAttributeHeaderDescriptor() ext.new() assert(ext.desc_tag.tag_ident == 262) # Unallocated Space def test_unalloc_space_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() unalloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00', 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: unalloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00', 0, tag) assert(str(excinfo.value) == 'UDF Unallocated Space Entry already initialized') def test_unalloc_space_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() unalloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00', 0, tag) assert(unalloc.desc_tag.tag_ident == 0) def test_unalloc_space_record_not_initialized(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: unalloc.record() assert(str(excinfo.value) == 'UDF Unallocated Space Entry not initialized') def test_unalloc_space_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() unalloc.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(unalloc.record() == b'\x00\x00\x02\x00\xa3\x00\x00\x00\xf8\x89 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_unalloc_space_new_initialized_twice(): unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() unalloc.new('dir') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: unalloc.new('dir') assert(str(excinfo.value) == 'UDF Unallocated Space Entry already initialized') def test_unalloc_space_new(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) unalloc = pycdlib.udf.UDFUnallocatedSpaceEntry() unalloc.new('dir') assert(unalloc.desc_tag.tag_ident == 263) # Partition Integrity def test_part_integrity_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) part = pycdlib.udf.UDFPartitionIntegrityEntry() part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00' + b'\x00'*175 + b'\x00'*32 + b'\x00'*256, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00' + b'\x00'*175 + b'\x00'*32 + b'\x00'*256, tag) assert(str(excinfo.value) == 'UDF Partition Integrity Entry already initialized') def test_part_integrity_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) part = pycdlib.udf.UDFPartitionIntegrityEntry() part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00' + b'\x00'*175 + b'\x00'*32 + b'\x00'*256, tag) assert(part.desc_tag.tag_ident == 0) def test_part_integrity_record_not_initialized(): part = pycdlib.udf.UDFPartitionIntegrityEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.record() assert(str(excinfo.value) == 'UDF Partition Integrity Entry not initialized') def test_part_integrity_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) part = pycdlib.udf.UDFPartitionIntegrityEntry() part.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00' + b'\x00'*175 + b'\x00'*32 + b'\x00'*256, tag) assert(part.record() == b'\x00\x00\x02\x00\x00\x00\x00\x00\xbe\x4f\xf0\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_part_integrity_new_initialized_twice(): part = pycdlib.udf.UDFPartitionIntegrityEntry() part.new('dir') with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: part.new('dir') assert(str(excinfo.value) == 'UDF Partition Integrity Entry already initialized') def test_part_integrity_new(): part = pycdlib.udf.UDFPartitionIntegrityEntry() part.new('dir') assert(part.desc_tag.tag_ident == 265) # Extended File def test_extended_file_parse_initialized_twice(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ef = pycdlib.udf.UDFExtendedFileEntry() ef.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*4 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ef.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*4 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(str(excinfo.value) == 'UDF Extended File Entry already initialized') def test_extended_file_parse(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ef = pycdlib.udf.UDFExtendedFileEntry() ef.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*4 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(ef.desc_tag.tag_ident == 0) def test_extended_file_record_not_initialized(): ef = pycdlib.udf.UDFExtendedFileEntry() with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ef.record() assert(str(excinfo.value) == 'UDF Extended File Entry not initialized') def test_extended_file_record(): tag = pycdlib.udf.UDFTag() tag.new(0, 0) ef = pycdlib.udf.UDFExtendedFileEntry() ef.parse(b'\x00'*16 + b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00' + b'\x00'*4 + b'\x00'*16 + b'\x00'*16 + b'\x00'*32 + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00', 0, tag) assert(ef.record() == b'\x00\x00\x02\x00\xf1\x00\x00\x00\x52\xcd\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_extended_file_new_initialized_twice(): ef = pycdlib.udf.UDFExtendedFileEntry() ef.new('dir', 0, 2048) with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: ef.new('dir', 0, 2048) assert(str(excinfo.value) == 'UDF Extended File Entry already initialized') def test_extended_file_new_dir(): ef = pycdlib.udf.UDFExtendedFileEntry() ef.new('dir', 0, 2048) assert(ef.desc_tag.tag_ident == 266) def test_extended_file_new_file(): ef = pycdlib.udf.UDFExtendedFileEntry() ef.new('file', 5, 2048) assert(ef.desc_tag.tag_ident == 266) # parse_allocation_descriptors def test_parse_allocation_descriptors_long(): alloc_descs = pycdlib.udf._parse_allocation_descriptors(1, b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 16, 0, 0) assert(len(alloc_descs) == 1) assert(isinstance(alloc_descs[0], pycdlib.udf.UDFLongAD)) def test_parse_allocation_descriptors_extended(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: alloc_descs = pycdlib.udf._parse_allocation_descriptors(2, b'', 0, 0, 0) assert(str(excinfo.value) == 'UDF Allocation Descriptor of type 2 (Extended) not yet supported') def test_parse_allocation_descriptors_inline(): alloc_descs = pycdlib.udf._parse_allocation_descriptors(3, b'', 0, 0, 0) assert(len(alloc_descs) == 1) assert(isinstance(alloc_descs[0], pycdlib.udf.UDFInlineAD)) def test_parse_allocation_descriptors_invalid(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidISO) as excinfo: alloc_descs = pycdlib.udf._parse_allocation_descriptors(4, b'', 0, 0, 0) assert(str(excinfo.value) == 'UDF Allocation Descriptor type invalid') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4961057 pycdlib-1.11.0/tests/unit/test_utils.py0000664000175000017500000001420000000000000022672 0ustar00clalancetteclalancette00000000000000from __future__ import absolute_import import pytest import os import sys try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import struct import time prefix = '.' for i in range(0, 3): if os.path.isdir(os.path.join(prefix, 'pycdlib')): sys.path.insert(0, prefix) break else: prefix = '../' + prefix import pycdlib.utils import pycdlib.pycdlibexception def test_swab_32bit(): assert(pycdlib.utils.swab_32bit(0x89) == 0x89000000) def test_swab_32bit_bad_input(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pycdlib.utils.swab_32bit(-1) assert(str(excinfo.value) == 'Invalid integer passed to swab; must be unsigned 32-bits!') def test_swab_16bit(): assert(pycdlib.utils.swab_16bit(0x55aa) == 0xaa55) def test_swab_16bit_bad_input(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInternalError) as excinfo: pycdlib.utils.swab_16bit(-1) assert(str(excinfo.value) == 'Invalid integer passed to swab; must be unsigned 16-bits!') def test_ceiling_div(): assert(pycdlib.utils.ceiling_div(0, 2048) == 0) def test_ceiling_div2(): assert(pycdlib.utils.ceiling_div(2048, 2048) == 1) def test_ceiling_div3(): assert(pycdlib.utils.ceiling_div(2049, 2048) == 2) def test_ceiling_div_nan(): with pytest.raises(ZeroDivisionError) as exc_info: pycdlib.utils.ceiling_div(2048, 0) def test_copy_data(): infp = BytesIO() outfp = BytesIO() infp.write(b'\x00'*1) infp.seek(0) pycdlib.utils.copy_data(1, 8192, infp, outfp) assert(outfp.getvalue() == b'\x00') def test_copy_data_short(): infp = BytesIO() outfp = BytesIO() infp.write(b'\x00'*10) infp.seek(0) pycdlib.utils.copy_data(100, 8192, infp, outfp) assert(outfp.getvalue() == b'\x00'*10) def test_encode_space_pad_too_short(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as exc_info: pycdlib.utils.encode_space_pad(b'hello', 4, 'ascii') assert(str(exc_info.value) == 'Input string too long!') def test_encode_space_pad_no_pad(): assert(pycdlib.utils.encode_space_pad(b'hello', 5, 'ascii') == b'hello') def test_encode_space_pad_one(): assert(pycdlib.utils.encode_space_pad(b'hello', 6, 'ascii') == b'hello ') def test_encode_space_pad_extra(): assert(pycdlib.utils.encode_space_pad(b'hello', 11, 'utf-16_be') == b'\x00h\x00e\x00l\x00l\x00o\x00') def test_normpath_double_slashes_beginning(): assert(pycdlib.utils.normpath('//') == b'/') def test_normpath_double_slashes_middle(): assert(pycdlib.utils.normpath('/foo//bar') == b'/foo/bar') def test_normpath_with_dotdot(): assert(pycdlib.utils.normpath('/foo/bar/../baz') == b'/foo/baz') def test_normpath_with_dotdot_after_slash(): assert(pycdlib.utils.normpath('/../foo') == b'/foo') def test_normpath_empty(): assert(pycdlib.utils.normpath('') == b'.') def save_and_set_tz(newtz): if 'TZ' in os.environ: oldtz = os.environ['TZ'] else: oldtz = None os.environ['TZ'] = newtz time.tzset() return oldtz def restore_tz(oldtz): if oldtz is not None: os.environ['TZ'] = oldtz else: del os.environ['TZ'] time.tzset() def test_gmtoffset_from_tm(): oldtz = save_and_set_tz('US/Eastern') now = 1546914300.0 assert(pycdlib.utils.gmtoffset_from_tm(now, time.localtime(now)) == -20) restore_tz(oldtz) def test_gmtoffset_from_tm_day_rollover(): # Setup the timezone to Tokyo oldtz = save_and_set_tz('Asia/Tokyo') # This tm is carefully chosen so that the day of the week is the next day # in the Tokyo region. now = 1550417871 local = time.localtime(now) assert(pycdlib.utils.gmtoffset_from_tm(now, local) == 36) restore_tz(oldtz) def test_zero_pad(): fp = BytesIO() pycdlib.utils.zero_pad(fp, 5, 10) assert(fp.getvalue() == b'\x00'*5) def test_zero_pad_no_pad(): fp = BytesIO() pycdlib.utils.zero_pad(fp, 5, 5) assert(fp.getvalue() == b'') def test_zero_pad_negative_pad(): fp = BytesIO() pycdlib.utils.zero_pad(fp, 5, 4) assert(fp.getvalue() == b'\x00'*3) def test_starts_with_slash(): assert(pycdlib.utils.starts_with_slash(b'/')) def test_starts_with_slash_no_slash(): assert(not pycdlib.utils.starts_with_slash(b'foo/bar')) def test_starts_with_slash_double_slash(): assert(pycdlib.utils.starts_with_slash(b'//foo/bar')) def test_split_path(): assert(pycdlib.utils.split_path(b'/foo/bar') == [b'foo', b'bar']) def test_split_path_no_leading_slash(): with pytest.raises(pycdlib.pycdlibexception.PyCdlibInvalidInput) as exc_info: pycdlib.utils.split_path(b'foo/bar') assert(str(exc_info.value) == 'Must be a path starting with /') def test_split_path_single(): assert(pycdlib.utils.split_path(b'/foo') == [b'foo']) def test_split_path_slash_only(): assert(pycdlib.utils.split_path(b'/') == [b'']) def test_split_path_trailing_slash(): assert(pycdlib.utils.split_path(b'/foo/') == [b'foo', b'']) def test_file_object_supports_binary_bytesio(): fp = BytesIO() assert(pycdlib.utils.file_object_supports_binary(fp)) def test_truncate_basename_isolevel4(): assert(pycdlib.utils.truncate_basename('foo', 4, False) == 'foo') def test_truncate_basename_isolevel3(): assert(pycdlib.utils.truncate_basename('foo', 3, False) == 'FOO') def test_mangle_file_for_iso9660_isolevel4_no_ext(): assert(pycdlib.utils.mangle_file_for_iso9660('foo', 4) == ('foo', '')) def test_mangle_file_for_iso9660_isolevel4_with_ext(): assert(pycdlib.utils.mangle_file_for_iso9660('foo.txt', 4) == ('foo', 'txt')) def test_mangle_file_for_iso9660_isolevel3_with_empty_ext(): assert(pycdlib.utils.mangle_file_for_iso9660('foo.', 3) == ('FOO_', ';1')) def test_file_object_supports_binary_real_file(tmpdir): testout = tmpdir.join('foo') with open(str(testout), 'wb') as outfp: assert(pycdlib.utils.file_object_supports_binary(outfp)) def test_file_object_does_not_support_binary_real_file(tmpdir): testout = tmpdir.join('foo') with open(str(testout), 'w') as outfp: assert(not pycdlib.utils.file_object_supports_binary(outfp)) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1602123483.0744355 pycdlib-1.11.0/tools/0000775000175000017500000000000000000000000017143 5ustar00clalancetteclalancette00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6641116 pycdlib-1.11.0/tools/pycdlib-explorer0000775000175000017500000004206200000000000022361 0ustar00clalancetteclalancette00000000000000#!/usr/bin/env python3 ''' The main code for the pycdlib-explorer tool, which can open, read, write, and otherwise manipulate ISOs in an interactive way. ''' from __future__ import print_function import cmd import collections import os import sys import pycdlib class PyCdlibCmdLoop(cmd.Cmd): ''' The main class dealing with the pycdlib-explorer command loop. ''' PRINT_MODES = ('iso9660', 'rr', 'joliet', 'udf') def __init__(self, iso): cmd.Cmd.__init__(self) self.iso = iso self.cwds = {} for mode in self.PRINT_MODES: self.cwds[mode] = '/' self.print_mode = 'iso9660' self.pathname = 'iso_path' prompt = '(pycdlib) ' def help_exit(self): # pylint: disable=no-self-use ''' The help method for the 'exit' command. ''' print('> exit') print('Exit the program.') def do_exit(self, line): # pylint: disable=no-self-use ''' The command to quit the program. ''' if line: print('No parameters allowed for exit') return False return True def help_quit(self): # pylint: disable=no-self-use ''' The help method for the 'quit' command. ''' print('> quit') print('Exit the program.') def do_quit(self, line): # pylint: disable=no-self-use ''' The other command to quit the program. ''' if line: print('No parameters allowed for quit') return False return True def do_EOF(self, line): # pylint: disable=unused-argument,no-self-use ''' Handel EOF on the terminal. ''' print() return True def help_print_mode(self): # pylint: disable=no-self-use ''' The help method for the 'print_mode' command. ''' print('> print_mode [iso9660|rr|joliet|udf]') print("Change which 'mode' of filenames are printed out. There are four main\n" 'modes: ISO9660 (iso9660, the default), Rock Ridge (rr), Joliet (joliet), and\n' 'UDF (udf). The original iso9660 mode only allows filenames of 8 characters,\n' 'plus 3 for the extension. The Rock Ridge extensions allow much longer\n' 'filenames and much deeper directory structures. The Joliet extensions also\n' 'allow longer filenames and deeper directory structures, but in an entirely\n' 'different namespace (though in most circumstances, the Joliet namespace will\n' 'mirror the ISO9660/Rock Ridge namespace). The UDF Bridge extensions add an\n' 'entirely parallel UDF namespace to the ISO as well. Any given ISO will always\n' 'have ISO9660 mode, but may have any combination of Rock Ridge, Joliet, and UDF\n' '(including none of them). Running this command with no arguments prints out\n' "the current mode. Passing 'iso9660' as an argument sets it to the original\n" "ISO9660 mode. Passing 'rr' as an argument sets it to Rock Ridge mode. Passing\n" "'joliet' as an argument sets it to Joliet mode. Passing 'udf' as an argument\n" 'sets it to UDF mode.') def do_print_mode(self, line): # pylint: disable=no-self-use ''' The command to change whether the explorer is printing in iso9660, joliet, rr, or udf (see help for more details). ''' split = line.split() splitlen = len(split) if splitlen == 0: print(self.print_mode) return False if splitlen != 1: print('Only a single parameter allowed for print_mode') return False if split[0] not in self.PRINT_MODES: print("Parameter for print_mode must be one of '" + "', ".join(self.PRINT_MODES) + "'") return False if split[0] == 'rr' and not self.iso.has_rock_ridge(): print('Can only enable Rock Ridge names for Rock Ridge ISOs') return False if split[0] == 'joliet' and not self.iso.has_joliet(): print('Can only enable Joliet names for Joliet ISOs') return False if split[0] == 'udf' and not self.iso.has_udf(): print('Can only enable UDF names for UDF ISOs') return False self.print_mode = split[0] pathstart = split[0] if split[0] == 'iso9660': pathstart = 'iso' self.pathname = pathstart + '_path' return False def help_ls(self): # pylint: disable=no-self-use ''' The help method for the 'ls' command. ''' print('> ls') print('Show the contents of the current working directory. The format of the output is:\n') print('TYPE(F=file, D=directory) NAME') def do_ls(self, line): # pylint: disable=no-self-use ''' The command to list the contents of a directory. ''' if line: print('No parameters allowed for ls') return False diriter = self.iso.list_children(**{self.pathname: self.cwds[self.print_mode]}) for child in diriter: if child is None: prefix = 'D' name = '..' else: prefix = 'F' if child.is_dir(): prefix = 'D' name = child.file_identifier().decode('utf-8') if self.print_mode == 'rr': name = '' if child.is_dot(): name = '.' elif child.is_dotdot(): name = '..' else: if child.rock_ridge is not None and child.rock_ridge.name() != '': name = child.rock_ridge.name().decode('utf-8') if child.rock_ridge.is_symlink(): name += ' -> %s' % (child.rock_ridge.symlink_path()) prefix += 'S' print('%2s %s' % (prefix, name)) return False def help_cd(self): # pylint: disable=no-self-use ''' The help method for the 'cd' command. ''' print('> cd ') print('Change directory to on the ISO.') def do_cd(self, line): # pylint: disable=no-self-use ''' The command to change the current working directory. ''' split = line.split() if len(split) != 1: print('The cd command supports one and only one parameter') return False directory = split[0] if directory == '/': tmp = '/' else: tmp = os.path.normpath(os.path.join(self.cwds[self.print_mode], directory)) rec = self.iso.get_record(**{self.pathname: tmp}) if not rec.is_dir(): print('Entry %s is not a directory' % (directory)) return False self.cwds[self.print_mode] = tmp return False def help_get(self): # pylint: disable=no-self-use ''' The help method for the 'get' command. ''' print('> get ') print('Get the contents of from the ISO and write them to .') def do_get(self, line): # pylint: disable=no-self-use ''' The command to extract a file from the ISO. ''' split = line.split() if len(split) != 2: print('The get command must be passed two parameters.') return False iso_file = split[0] outfile = split[1] if iso_file[0] == '/': path = iso_file else: path = os.path.join(self.cwds[self.print_mode], iso_file) self.iso.get_file_from_iso(outfile, **{self.pathname: path}) return False def help_cwd(self): # pylint: disable=no-self-use ''' The help method for the 'cwd' command. ''' print('> cwd') print('Show the current working directory.') def do_cwd(self, line): # pylint: disable=no-self-use ''' The command to find out what the current working directory is. ''' if line: print('No parameters allowed for cwd') return False print(self.cwds[self.print_mode]) return False def help_tree(self): # pylint: disable=no-self-use ''' The help method for the 'tree' command. ''' print('> tree') print("Print all files and subdirectories below the current directory (similar to the Unix 'tree' command).") def do_tree(self, line): # pylint: disable=no-self-use ''' The command to print out all of the files and directories below the current working directory in a convenient tree-like form. ''' if line: print('No parameters allowed for tree') return False utf8_corner = '└──' utf8_middlebar = '├──' utf8_vertical_line = '│' entry = self.iso.get_record(**{self.pathname: self.cwds[self.print_mode]}) dirs = collections.deque([(entry, [])]) while dirs: dir_record, lasts = dirs.popleft() prefix = '' for index, last in enumerate(lasts): if last: if index == (len(lasts) - 1): prefix += utf8_corner + ' ' else: prefix += ' ' else: if index == (len(lasts) - 1): prefix += utf8_middlebar + ' ' else: prefix += utf8_vertical_line + ' ' name = dir_record.file_identifier() if self.print_mode == 'rr': if dir_record.rock_ridge is not None and dir_record.rock_ridge.name() != '': name = dir_record.rock_ridge.name() print('%s%s' % (prefix, name.decode('utf-8'))) if dir_record.is_dir(): tmp = [] if self.print_mode == 'udf': for d in dir_record.fi_descs: child = d.file_entry if child is None: continue tmp.append((child, lasts + [False])) else: for child in dir_record.children: if child.is_dot() or child.is_dotdot(): continue tmp.append((child, lasts + [False])) if tmp: tmp.pop() tmp.append((child, lasts + [True])) dirs.extendleft(reversed(tmp)) return False def help_write(self): # pylint: disable=no-self-use ''' The help method for the 'write' command. ''' print('> write ') print('Write the current ISO contents to .') def do_write(self, line): # pylint: disable=no-self-use ''' The command to write the ISO out to a new file. ''' split = line.split() if len(split) != 1: print('The write command supports one and only one parameter.') return False out_name = split[0] self.iso.write(out_name) return False def help_add_file(self): # pylint: disable=no-self-use ''' The help method for the 'add_file' command. ''' print('> add_file [rr_name=] [joliet_path=]') print('Add the contents of to the ISO at the location specified in .') print('If the ISO is a Rock Ridge ISO, must be specified; otherwise, it must not be.') print('If the ISO is not a Joliet ISO, must not be specified. If the ISO is a') print('Joliet ISO, is optional, but highly recommended to supply.') def do_add_file(self, line): # pylint: disable=no-self-use ''' The command to add a new file to the ISO from the local filesystem. ''' split = line.split() if len(split) < 2 or len(split) > 4: self.help_add_file() return False iso_path = split[0] src_path = split[1] rr_name = None joliet_path = None for arg in split[2:]: keyval = arg.split('=') if len(keyval) != 2: print('Invalid key/val pair, must be rr_name= or joliet_path=') return False key = keyval[0] val = keyval[1] if key == 'rr_name': rr_name = val elif key == 'joliet_path': joliet_path = val else: print('Unknown key, must be rr_name= or joliet_path=') return False if self.iso.has_rock_ridge() and rr_name is None: print('The ISO is Rock Ridge, so a parameter must be specified.') return False if iso_path[0] != '/': iso_path = os.path.join(self.cwds['iso9660'], iso_path) self.iso.add_file(src_path, iso_path, rr_name=rr_name, joliet_path=joliet_path) return False def help_rm_file(self): # pylint: disable=no-self-use ''' The help method for the 'rm_file' command. ''' print('> rm_file ') print('Remove the contents of from the ISO.') def do_rm_file(self, line): # pylint: disable=no-self-use ''' The command to remove a file from the ISO. ''' split = line.split() if len(split) != 1: print('The rm_file command takes one and only one parameter (iso path).') return False iso_path = split[0] if iso_path[0] != '/': iso_path = os.path.join(self.cwds['iso9660'], iso_path) self.iso.rm_file(iso_path) return False def help_mkdir(self): # pylint: disable=no-self-use ''' The help method for the 'mkdir' command. ''' print('> mkdir [rr_name=] [joliet_path=]') print('Make a new directory called .') print('If the ISO is a Rock Ridge ISO, must be specified; otherwise, it must not be.') print('If the ISO is not a Joliet ISO, must not be specified. If the ISO is a') print('Joliet ISO, is optional, but highly recommended to supply.') def do_mkdir(self, line): # pylint: disable=no-self-use ''' The command to make a new directory on the ISO. ''' split = line.split() if not split or len(split) > 3: self.help_mkdir() return False iso_path = split[0] rr_name = None joliet_path = None for arg in split[1:]: keyval = arg.split('=') if len(keyval) != 2: print('Invalid key/val pair, must be rr_name= or joliet_path=') return False key = keyval[0] val = keyval[1] if key == 'rr_name': rr_name = val elif key == 'joliet_path': joliet_path = val else: print('Unknown key, must be rr_name= or joliet_path=') return False if self.iso.has_rock_ridge() and rr_name is None: print('The ISO is Rock Ridge, so a parameter must be specified.') return False if iso_path[0] != '/': iso_path = os.path.join(self.cwds['iso9660'], iso_path) self.iso.add_directory(iso_path, rr_name=rr_name, joliet_path=joliet_path) return False def help_rmdir(self): # pylint: disable=no-self-use ''' The help method for the 'rmdir' command. ''' print('> rmdir ') print('Remove the directory at . Note that the directory must be empty for the command to succeed.') def do_rmdir(self, line): # pylint: disable=no-self-use ''' The command to remove a directory from the ISO. ''' split = line.split() if len(split) != 1: print('The rmdir command takes one and only one parameter (iso path).') return False iso_path = split[0] if iso_path[0] != '/': iso_path = os.path.join(self.cwds['iso9660'], iso_path) self.iso.rm_directory(iso_path) return False def main(): ''' The main function. ''' if len(sys.argv) != 2: print('Usage: %s ' % (sys.argv[0])) sys.exit(1) iso = pycdlib.PyCdlib() fp = open(sys.argv[1], 'rb') iso.open_fp(fp) done = False cmdloop = PyCdlibCmdLoop(iso) while not done: try: cmdloop.cmdloop() done = True except Exception as e: # pylint: disable=broad-except print(e) iso.close() fp.close() if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1570403507.6651115 pycdlib-1.11.0/tools/pycdlib-extract-files0000775000175000017500000001055000000000000023270 0ustar00clalancetteclalancette00000000000000#!/usr/bin/env python3 # Copyright (C) 2018 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' The main code for the pycdlib-extract-files tool, which can extract all or a subset of files from an ISO. ''' from __future__ import print_function import argparse import collections import os import sys import pycdlib def parse_arguments(): ''' A function to parse all of the arguments passed to the executable. Parameters: None. Returns: An ArgumentParser object with the parsed command-line arguments. ''' parser = argparse.ArgumentParser() parser.add_argument('-path-type', help='Which path type to use for extraction', action='store', choices=['auto', 'iso', 'joliet', 'rockridge', 'udf'], default='auto') parser.add_argument('-start-path', help='Path on ISO to start extraction from', action='store', default='/') parser.add_argument('-extract-to', help='Path to extract ISO contents to', action='store', default='.') parser.add_argument('iso', help='ISO to open', action='store') return parser.parse_args() def main(): ''' The main function for this executable that does the work of extracting files from an ISO given the parameters specified by the user. ''' args = parse_arguments() iso = pycdlib.PyCdlib() print('Opening %s' % (args.iso)) iso.open(args.iso) if args.path_type == 'auto': if iso.has_udf(): pathname = 'udf_path' elif iso.has_rock_ridge(): pathname = 'rr_path' elif iso.has_joliet(): pathname = 'joliet_path' else: pathname = 'iso_path' elif args.path_type == 'rockridge': if not iso.has_rock_ridge(): print('Can only extract Rock Ridge paths from a Rock Ridge ISO') return 1 pathname = 'rr_path' elif args.path_type == 'joliet': if not iso.has_joliet(): print('Can only extract Joliet paths from a Joliet ISO') return 2 pathname = 'joliet_path' elif args.path_type == 'udf': if not iso.has_udf(): print('Can only extract UDF paths from a UDF ISO') return 3 pathname = 'udf_path' else: pathname = 'iso_path' print("Using path type of '%s'" % (pathname)) root_entry = iso.get_record(**{pathname: args.start_path}) dirs = collections.deque([root_entry]) while dirs: dir_record = dirs.popleft() ident_to_here = iso.full_path_from_dirrecord(dir_record, rockridge=pathname == 'rr_path') relname = ident_to_here[len(args.start_path):] if relname and relname[0] == '/': relname = relname[1:] print(relname) if dir_record.is_dir(): if relname != '': os.makedirs(os.path.join(args.extract_to, relname)) child_lister = iso.list_children(**{pathname: ident_to_here}) for child in child_lister: if child is None or child.is_dot() or child.is_dotdot(): continue dirs.append(child) else: if dir_record.is_symlink(): fullpath = os.path.join(args.extract_to, relname) local_dir = os.path.dirname(fullpath) local_link_name = os.path.basename(fullpath) old_dir = os.getcwd() os.chdir(local_dir) os.symlink(dir_record.rock_ridge.symlink_path(), local_link_name) os.chdir(old_dir) else: iso.get_file_from_iso(os.path.join(args.extract_to, relname), **{pathname: ident_to_here}) iso.close() return 0 if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1601427682.4971058 pycdlib-1.11.0/tools/pycdlib-genisoimage0000775000175000017500000013470700000000000023020 0ustar00clalancetteclalancette00000000000000#!/usr/bin/env python3 # Copyright (C) 2017-2018 Chris Lalancette # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # This library 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 # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ''' A drop-in replacement program for the 'genisoimage' executable that uses PyCdlib under the hood. ''' from __future__ import print_function import argparse import collections import fileinput import fnmatch import os import sys import time try: from cStringIO import StringIO as BytesIO except ImportError: from io import BytesIO import pycdlib ################################ MURMER3 HASH FUNCTIONS ############################## if sys.version_info >= (3, 0): def xrange(a, b, c): ''' An xrange that works for Python 3. ''' return range(a, b, c) def xencode(x): ''' A version of encode that works for bytes, bytearrays, or strings. ''' if isinstance(x, (bytearray, bytes)): return x return x.encode() else: def xencode(x): ''' The identity version of xencode for Python 2. ''' return x def mm3hash(key, seed=0x0): ''' Implements 32bit murmur3 hash. ''' key = bytearray(xencode(key)) def fmix(h): ''' A function to mix h. ''' h ^= h >> 16 h = (h * 0x85ebca6b) & 0xFFFFFFFF h ^= h >> 13 h = (h * 0xc2b2ae35) & 0xFFFFFFFF h ^= h >> 16 return h length = len(key) nblocks = int(length / 4) h1 = seed c1 = 0xcc9e2d51 c2 = 0x1b873593 # body for block_start in xrange(0, nblocks * 4, 4): # ??? big endian? k1 = key[block_start + 3] << 24 | key[block_start + 2] << 16 | key[block_start + 1] << 8 | key[block_start + 0] k1 = (c1 * k1) & 0xFFFFFFFF k1 = (k1 << 15 | k1 >> 17) & 0xFFFFFFFF # inlined ROTL32 k1 = (c2 * k1) & 0xFFFFFFFF h1 ^= k1 h1 = (h1 << 13 | h1 >> 19) & 0xFFFFFFFF # inlined ROTL32 h1 = (h1 * 5 + 0xe6546b64) & 0xFFFFFFFF # tail tail_index = nblocks * 4 k1 = 0 tail_size = length & 3 if tail_size >= 3: k1 ^= key[tail_index + 2] << 16 if tail_size >= 2: k1 ^= key[tail_index + 1] << 8 if tail_size >= 1: k1 ^= key[tail_index + 0] if tail_size > 0: k1 = (k1 * c1) & 0xFFFFFFFF k1 = (k1 << 15 | k1 >> 17) & 0xFFFFFFFF # inlined ROTL32 k1 = (k1 * c2) & 0xFFFFFFFF h1 ^= k1 # finalization unsigned_val = fmix(h1 ^ length) if unsigned_val & 0x80000000 == 0: return unsigned_val return -((unsigned_val ^ 0xFFFFFFFF) + 1) def mm3hashfromfile(filename): ''' A function to generate a Murmur3 hash given a filename. ''' with open(filename, 'rb') as infp: done = False seed = 0 while not done: data = infp.read(32 * 1024) if len(data) < 32 * 1024: # EOF done = True seed = mm3hash(data, seed) return seed ################################ HELPER FUNCTIONS ############################## def match_entry_to_list(pattern_list, entry): ''' A function to match a string to a list of filename patterns. If any of them match, returns True, otherwise, returns False. Parameters: pattern_list - The list of filename patterns to check against entry - The string to check Returns: True if the string matches any of the filename patterns, False otherwise. ''' for pattern in pattern_list: if fnmatch.fnmatch(entry, pattern): return True return False def parse_file_list(thelist): ''' A function to take a list of filenames, open each one for reading, and yield each of the lines in the file. Parameters: thelist - The list of files to open Returns: Nothing. ''' for f in thelist: with open(f, 'r') as infp: for line in infp.xreadlines(): yield line.rstrip() def build_joliet_path(root, name): ''' A function to build a complete, valid Joliet path based on a root directory and a name. Parameters: root - The root directory name name - The name for the Joliet entry Returns: A valid, absolute Joliet filename. ''' if root and root[0] == '/': root = root[1:] intermediate = '' for intdir in root.split('/'): if not intdir: continue intermediate += '/' + intdir[:64] return intermediate + '/' + name[:64] def build_udf_path(root, name): ''' A function to build a complete, valid UDF path based on a root directory and a name. Parameters: root - The root directory name. name - The name for the UDF entry. Returns: A valid, absolute UDF filename. ''' if root and root[0] == '/': root = root[1:] intermediate = '' for intdir in root.split('/'): if not intdir: continue intermediate += '/' + intdir return intermediate + '/' + name class EltoritoEntry(object): ''' A class that represents a single El Torito entry on the ISO. There may be more than one of these per-ISO, so each one is tracked separately. ''' __slots__ = ('bootfile_orig', 'bootfile_parts', 'mediatype', 'boot', 'load_size', 'load_seg', 'boot_info_table', 'bootfile_iso_path', 'dirlevel') def __init__(self): self.bootfile_orig = '' self.bootfile_parts = [] self.mediatype = 'floppy' self.boot = True self.load_size = 0 self.load_seg = 0 self.boot_info_table = False self.bootfile_iso_path = '' self.dirlevel = None def set_bootfile(self, bootfile): ''' Set the bootfile for this El Torito entry. Parameters: bootfile - The bootfile to set for this El Torito entry Returns: Nothing. ''' self.bootfile_orig = bootfile self.bootfile_parts = bootfile.split('/') def parse_arguments(): ''' A function to parse all of the arguments passed to the executable. Note that the set of arguments accepted is intentionally compatible with genisoimage. Also note that this executable does not support all flags below, and will silently ignore ones it doesn't understand. This is to keep maximum compatibility with genisoimage. Parameters: None. Returns: An ArgumentParser object with the parsed command-line arguments. ''' parser = argparse.ArgumentParser(add_help=False) parser.add_argument('-nobak', '-no-bak', help='Do not include backup files', action='store_true') parser.add_argument('-abstract', help='Set Abstract filename', action='store', default='') parser.add_argument('-appid', '-A', help='Set Application ID', action='store', default='') parser.add_argument('-biblio', help='Set Bibliographic filename', action='store', default='') parser.add_argument('-cache-inodes', help='Cache inodes (needed to detect hard links)', action='store_true') parser.add_argument('-no-cache-inodes', help='Do not cache inodes (if filesystem has no unique unides)', action='store_true') parser.add_argument('-check-oldnames', help='Check all imported ISO9660 names from old session', action='store_true') parser.add_argument('-check-session', help='Check all ISO9660 names from previous session', action='store') parser.add_argument('-copyright', help='Set Copyright filename', action='store', default='') parser.add_argument('-debug', help='Set debug flag', action='store_true') parser.add_argument('-eltorito-boot', '-b', help='Set El Torito boot image name', action='store') parser.add_argument('-efi-boot', '-e', help='Set EFI boot image name', action='append') parser.add_argument('-eltorito-alt-boot', help='Start specifying alternative El Torito boot parameters', action='append_const', const=True) parser.add_argument('-sparc-boot', '-B', help='Set sparc boot image names', action='store') parser.add_argument('-sunx86-boot', help='Set sunx86 boot image names', action='store') parser.add_argument('-generic-boot', '-G', help='Set generic boot image name', action='store') parser.add_argument('-sparc-label', help='Set sparc boot disk label', action='store', nargs=2) parser.add_argument('-sunx86-label', help='Set sunx86 boot disk label', action='store', nargs=2) parser.add_argument('-eltorito-catalog', '-c', help='Set El Torito boot catalog name', action='store', default=None) parser.add_argument('-cdrecord-params', '-C', help='Magic parameters from cdrecord', action='store') parser.add_argument('-omit-period', '-d', help='Omit trailing periods from filenames (violates ISO9660)', action='store_true') parser.add_argument('-dir-mode', help='Make the mode of all directories this mode', action='store') parser.add_argument('-disable-deep-relocation', '-D', help='Disable deep directory relocation (violates ISO9660)', action='store_true') parser.add_argument('-file-mode', help='Make the mode of all plain files this mode', action='store') parser.add_argument('-follow-links', '-f', help='Follow symbolic links', action='store_true') parser.add_argument('-gid', help='Make the group owner of all files this gid', action='store') parser.add_argument('-graft-points', help='Allow to use graft points for filenames', action='store_true') parser.add_argument('-root', help='Set root directory for all new files and directories', action='store') parser.add_argument('-old-root', help='Set root directory in previous session this is searched for files', action='store') parser.add_argument('-help', help='Print option help', action='help') parser.add_argument('-hide', help='Hide ISO9660/RR file', action='append', default=[]) parser.add_argument('-hide-list', help='File with list of ISO9660/RR files to hide', action='append', default=[]) parser.add_argument('-hidden', help='Set hidden attribute on ISO9660 file', action='append', default=[]) parser.add_argument('-hidden-list', help='File with list of ISO9660 files with hidden attribute', action='append', default=[]) parser.add_argument('-hide-joliet', help='Hide Joliet file', action='append', default=[]) parser.add_argument('-hide-joliet-list', help='File with list of Joliet files to hide', action='append', default=[]) parser.add_argument('-hide-joliet-trans-tbl', help='Hide TRANS.TBL from Joliet tree', action='store_true') parser.add_argument('-hide-rr-moved', help='Rename RR_MOVED to .rr_moved in Rock Ridge tree', action='store_true') parser.add_argument('-gui', help='Switch behavior for GUI', action='store_true') parser.add_argument('-i', help='No longer supported', action='store') parser.add_argument('-input-charset', help='Local input charset for file name conversion', action='store') parser.add_argument('-output-charset', help='Output charset for file name conversion', action='store') parser.add_argument('-iso-level', help='Set ISO9660 conformance level (1..3) or 4 for ISO9660 version 2', action='store', default=1, type=int, choices=range(1, 5)) parser.add_argument('-joliet', '-J', help='Generate Joliet directory information', action='store_true', default=False) parser.add_argument('-joliet-long', help='Allow Joliet file names to be 103 Unicode characters', action='store_true') parser.add_argument('-jcharset', help='Local charset for Joliet directory information', action='store') parser.add_argument('-full-iso9660-filenames', '-l', help='Allow full 31 character filenames for ISO9660 names', action='store_true') parser.add_argument('-max-iso9660-filenames', help='Allow 37 character filenames for ISO9660 names (violates ISO9660)', action='store_true') parser.add_argument('-allow-limited-size', help='Allow different file sizes in ISO9660/UDF on large files', action='store_true') parser.add_argument('-allow-leading-dots', '-ldots', '-L', help="Allow ISO9660 filenames to start with '.' (violates ISO9660)", action='store_true') parser.add_argument('-log-file', help='Re-direct messages to LOG_FILE', action='store') parser.add_argument('-exclude', '-m', help='Exclude file name', action='append', default=[]) parser.add_argument('-exclude-list', help='File with list of file names to exclude', action='append', default=[]) parser.add_argument('-pad', help='Pad output to a multiple of 32k (default)', action='store_true') parser.add_argument('-no-pad', help='Do not pad output to a multiple of 32k', action='store_true') parser.add_argument('-prev-session', '-M', help='Set path to previous session to merge', action='store') parser.add_argument('-dev', help='Device', action='store') parser.add_argument('-omit-version-number', '-N', help='Omit version number from ISO9660 filename (violates ISO9660)', action='store_true') parser.add_argument('-new-dir-mode', help='Mode used when creating new directories', action='store') parser.add_argument('-force-rr', help='Inhibit automatic Rock Ridge detection for previous session', action='store_true') parser.add_argument('-no-rr', help='Inhibit reading of Rock Ridge attributes from previous session', action='store_true') parser.add_argument('-no-split-symlink-components', help='Inhibit splitting symlink components', action='store_true') parser.add_argument('-no-split-symlink-fields', help='Inhibit splitting symlink fields', action='store_true') parser.add_argument('-output', '-o', help='Set output file name', action='store') parser.add_argument('-path-list', help='File with list of pathnames to process', action='store') parser.add_argument('-preparer', '-p', help='Set Volume preparer', action='store', default='') parser.add_argument('-print-size', help='Print estimated filesystem size and exit', action='store_true') parser.add_argument('-publisher', '-P', help='Set Volume publisher', action='store', default='') parser.add_argument('-quiet', help='Run quietly', action='store_true') parser.add_argument('-rational-rock', '-r', help='Generate rationalized Rock Ridge directory information', action='store_true', default=False) parser.add_argument('-rock', '-R', help='Generate Rock Ridge directory information', action='store_true', default=False) parser.add_argument('-sectype', '-s', help='Set output sector type to e.g. data/xa1/raw', action='store') parser.add_argument('-alpha-boot', help='Set alpha boot image name (relative to image root)', action='store') parser.add_argument('-hppa-cmdline', help='Set hppa boot command line (relative to image root)', action='store') parser.add_argument('-hppa-kernel-32', help='Set hppa 32-bit image name (relative to image root)', action='store') parser.add_argument('-hppa-kernel-64', help='Set hppa 64-bit image name (relative to image root)', action='store') parser.add_argument('-hppa-bootloader', help='Set hppa boot loader file name (relative to image root)', action='store') parser.add_argument('-hppa-ramdisk', help='Set hppa ramdisk file name (relative to image root)', action='store') parser.add_argument('-mips-boot', help='Set mips boot image name (relative to image root)', action='store') parser.add_argument('-mipsel-boot', help='Set mipsel boot image name (relative to image root)', action='store') parser.add_argument('-jigdo-jigdo', help='Produce a jigdo .jigdo file as well as the .iso', action='store') parser.add_argument('-jigdo-template', help='Produce a jigdo .template file as well as the .iso', action='store') parser.add_argument('-jigdo-min-file-size', help='Minimum size for a file to be listed in the jigdo file', action='store') parser.add_argument('-jigdo-force-md5', help='Pattern(s) where files MUST match an externally-supplied MD5Sum', action='store') parser.add_argument('-jigdo-exclude', help='Pattern(s) to exclude from the jigdo file', action='store') parser.add_argument('-jigdo-map', help='Pattern(s) to map paths (e.g. Debian=/mirror/debian)', action='store') parser.add_argument('-md5-list', help='File containing MD5 sums of the files that should be checked', action='store') parser.add_argument('-jigdo-template-compress', help='Choose to use gzip or bzip2 compression for template data; default is gzip', action='store') parser.add_argument('-checksum_algorithm_iso', help='Specify the checksum types desired for the output image', action='store') parser.add_argument('-checksum_algorithm_template', help='Specify the checksum types desired for the output jigdo template', action='store') parser.add_argument('-sort', help='Sort file content locations according to rules in FILE', action='store') parser.add_argument('-split-output', help='Split output into files of approx. 1GB size', action='store_true') parser.add_argument('-stream-file-name', help='Set the stream file ISO9660 name (incl. version)', action='store') parser.add_argument('-stream-media-size', help='Set the size of your CD media in sectors', action='store') parser.add_argument('-sysid', help='Set System ID', action='store', default='') parser.add_argument('-translation-table', '-T', help="Generate translation tables for systems that don't understand long filenames", action='store_true') parser.add_argument('-table-name', help='Translation table file name', action='store') parser.add_argument('-ucs-level', help='Set Joliet UCS level (1..3)', action='store') parser.add_argument('-udf', help='Generate UDF file system', action='store_true') parser.add_argument('-dvd-video', help='Generate DVD-Video compliant UDF file system', action='store_true') parser.add_argument('-uid', help='Make the owner of all files this uid', action='store') parser.add_argument('-untranslated-filenames', '-U', help='Allow Untranslated filenames (for HPUX & AIX - violates ISO9660). Forces -l, -d, -N, -allow-leading-dots, -relaxed-filenames, -allow-lowercase, -allow-multidot', action='store_true') parser.add_argument('-relaxed-filenames', help='Allow 7 bit ASCII except lower case characters (violates ISO9660)', action='store_true') parser.add_argument('-no-iso-translate', help="Do not translate illegal ISO characters '~', '-', and '#' (violates ISO9660)", action='store_true') parser.add_argument('-allow-lowercase', help='Allow lower case characters in addition to the current character set (violates ISO9660)', action='store_true') parser.add_argument('-allow-multidot', help='Allow more than one dot in filenames (e.g. .tar.gz) (violates ISO9660)', action='store_true') parser.add_argument('-use-fileversion', help='Use fileversion # from filesystem', action='store') parser.add_argument('-verbose', '-v', help='Verbose', action='store_true') parser.add_argument('-version', help='Print the current version', action='store_true') parser.add_argument('-volid', '-V', help='Set Volume ID', action='store', default='') parser.add_argument('-volset', help='Set Volume set ID', action='store', default='') parser.add_argument('-volset-size', help='Set Volume set size', action='store', default=1) parser.add_argument('-volset-seqno', help='Set Volume set sequence number', action='store', default=1) parser.add_argument('-old-exclude', '-x', help='Exclude file name (deprecated)', action='append', default=[]) parser.add_argument('-hard-disk-boot', help='Boot image is a hard disk image', action='append_const', const=True) parser.add_argument('-no-emul-boot', help="Boot image is a 'no emulation' image", action='append_const', const=True) parser.add_argument('-no-boot', help='Boot image is not bootable', action='append_const', const=True) parser.add_argument('-boot-load-seg', help='Set load segment for boot image', action='append') parser.add_argument('-boot-load-size', help='Set number of load sectors', action='append') parser.add_argument('-boot-info-table', help='Patch boot image with info table', action='append_const', const=True) parser.add_argument('-XA', help='Generate XA directory attributes', action='store_true') parser.add_argument('-xa', help='Generate rationalized XA directory attributes', action='store_true') parser.add_argument('-transparent-compression', '-z', help='Enable transparent compression of files', action='store_true') parser.add_argument('-hfs-type', help='Set HFS default TYPE', action='store') parser.add_argument('-hfs-creator', help='Set HFS default CREATOR', action='store') parser.add_argument('-apple', '-g', help='Add Apple ISO9660 extensions', action='store_true') parser.add_argument('-hfs', '-h', help='Create ISO9660/HFS hybrid', action='store_true') parser.add_argument('-map', '-H', help='Map file extensions to HFS TYPE/CREATOR', action='store') parser.add_argument('-magic', help='Magic file for HFS TYPE/CREATOR', action='store') parser.add_argument('-probe', help='Probe all files for Apple/Unix file types', action='store_true') parser.add_argument('-mac-name', help='Use Macintosh name for ISO9660/Joliet/RockRidge file name', action='store_true') parser.add_argument('-no-mac-files', help='Do not look for Unix/Mac files (deprecated)', action='store_true') parser.add_argument('-boot-hfs-file', help='Set HFS boot image name', action='store') parser.add_argument('-part', help='Generate HFS partition table', action='store_true') parser.add_argument('-cluster-size', help='Cluster size for PC Exchange Macintosh files', action='store') parser.add_argument('-auto', help='Set HFS AutoStart file name', action='store') parser.add_argument('-no-desktop', help='Do not create the HFS (empty) Desktop files', action='store_true') parser.add_argument('-hide-hfs', help='Hide HFS file', action='append', default=[]) parser.add_argument('-hide-hfs-list', help='List of HFS files to hide', action='append', default=[]) parser.add_argument('-hfs-volid', help='Volume name for the HFS partition', action='store') parser.add_argument('-icon-position', help='Keep HFS icon position', action='store_true') parser.add_argument('-root-info', help='finderinfo for root folder', action='store') parser.add_argument('-input-hfs-charset', help='Local input charset for HFS file name conversion', action='store') parser.add_argument('-output-hfs-charset', help='Output charset for HFS file name conversion', action='store') parser.add_argument('-hfs-unlock', help='Leave HFS volume unlocked', action='store_true') parser.add_argument('-hfs-bless', help='Name of Folder to be blessed', action='store') parser.add_argument('-hfs-parms', help='Comma separated list of HFS parameters', action='store') parser.add_argument('-prep-boot', help='PReP boot image file -- up to 4 are allowed', action='store') # FIXME: we need to allow between 1 and 4 arguments parser.add_argument('-chrp-boot', help='Add CHRP boot header', action='store_true') parser.add_argument('--cap', help='Look for AUFS CAP Macintosh files', action='store_true') parser.add_argument('--netatalk', help='Look for NETATALK Macintosh files', action='store_true') parser.add_argument('--double', help='Look for AppleDouble Macintosh files', action='store_true') parser.add_argument('--ethershare', help='Look for Helios EtherShare Macintosh files', action='store_true') parser.add_argument('--exchange', help='Look for PC Exchange Macintosh files', action='store_true') parser.add_argument('--sgi', help='Look for SGI Macintosh files', action='store_true') parser.add_argument('--macbin', help='Look for MacBinary Macintosh files', action='store_true') parser.add_argument('--single', help='Look for AppleSingle Macintosh files', action='store_true') parser.add_argument('--ushare', help='Look for IPT UShare Macintosh files', action='store_true') parser.add_argument('--xinet', help='Look for XINET Macintosh files', action='store_true') parser.add_argument('--dave', help='Look for DAVE Macintosh files', action='store_true') parser.add_argument('--sfm', help='Look for SFM Macintosh files', action='store_true') parser.add_argument('--osx-double', help='Look for MacOS X AppleDouble Macintosh files', action='store_true') parser.add_argument('--osx-hfs', help='Look for MacOS X HFS Macintosh files', action='store_true') parser.add_argument('-find', help='Option separator: Use find command line to the right', action='store') parser.add_argument('-posix-H', help='Follow symbolic links encountered on command line', action='store_true') parser.add_argument('-posix-L', help='Follow all symbolic links', action='store_true') parser.add_argument('-posix-P', help='Do not follow symbolic links (default)', action='store_true') parser.add_argument('-rrip110', help='Create old Rock Ridge V 1.10', action='store_true') parser.add_argument('-rrip112', help='Create new Rock Ridge V 1.12 (default)', action='store_true') parser.add_argument('-ignore-error', help='Ignore errors', action='store_true') parser.add_argument('-eltorito-platform', help='Set El Torito platform id for the next boot entry', action='store') parser.add_argument('-data-change-warn', help='Treat data/size changes as warning only', action='store_true') parser.add_argument('-errctl', help='Read error control defs from file or inline.', action='store') parser.add_argument('-hide-udf', help='Hide UDF file', action='append', default=[]) parser.add_argument('-hide-udf-list', help='File with list of UDF files to hide', action='append', default=[]) parser.add_argument('-long-rr-time', help='Use long Rock Ridge time format', action='store_true') parser.add_argument('-modification-date', help='Set the modification date field of the PVD', action='store') parser.add_argument('-no-limit-pathtables', help='Allow more than 65535 parent directories (violates ISO9660)', action='store_true') parser.add_argument('-no-long-rr-time', '-short-rr-time', help='Use short Rock Ridge time format', action='store_true') parser.add_argument('-UDF', help='Generate UDF file system', action='store_true') parser.add_argument('-udf-symlinks', help='Create symbolic links on UDF image (default)', action='store_true') parser.add_argument('-no-udf-symlinks', help='Do not create symbolic links on UDF image', action='store_true') parser.add_argument('-no-hfs', help='Do not create ISO9660/HFS hybrid', action='store_true') parser.add_argument('-scan-for-duplicates', help='Aggressively try to find duplicate files to reduce size (very slow!)', action='store_true') parser.add_argument('paths', help='Paths to get data from', action='store', nargs=argparse.REMAINDER) return parser.parse_args() def determine_eltorito_entries(args): ''' A function to build up the list of EltoritoEntry objects for this ISO. Parameters: args - The ArgumentParser object returned from parse_arguments() Returns: A list of EltoritoEntry objects for this ISO (which may be empty). ''' eltorito_entries = [] efi_boot_index = 0 load_seg_index = 0 load_size_index = 0 for arg in sys.argv[1:]: if arg == '-eltorito-alt-boot': eltorito_entries.append(EltoritoEntry()) continue if arg not in ('-b', '-eltorito-boot', '-e', '-efi-boot', '-no-emul-boot', '-hard-disk-boot', '-no-boot', '-boot-load-seg', '-boot-load-size', '-boot-info-table'): continue if not eltorito_entries: entry = EltoritoEntry() eltorito_entries.append(entry) else: entry = eltorito_entries[-1] if arg in ('-b', '-eltorito-boot'): entry.set_bootfile(args.eltorito_boot) elif arg in ('-e', '-efi-boot'): entry.set_bootfile(args.efi_boot[efi_boot_index]) efi_boot_index += 1 elif arg == '-no-emul-boot': entry.mediatype = 'noemul' elif arg == '-hard-disk-boot': entry.mediatype = 'hdemul' elif arg == '-no-boot': entry.boot = False elif arg == '-boot-load-seg': entry.load_seg = int(args.boot_load_seg[load_seg_index]) load_seg_index += 1 elif arg == '-boot-load-size': entry.load_size = int(args.boot_load_size[load_size_index]) load_size_index += 1 elif arg == '-boot-info-table': entry.boot_info_table = True return eltorito_entries class DirLevel(object): ''' A class to hold information about one directory level of the directory hierarchy. Each level has an iso_path, a joliet_path, and a set of mangled maps for mangling filenames as appropriate. ''' __slots__ = ('iso_path', 'joliet_path', 'udf_path', 'mangled_children') def __init__(self, iso_path, joliet_path, udf_path): self.iso_path = iso_path self.joliet_path = joliet_path self.udf_path = udf_path self.mangled_children = {} def build_iso_path(parent_dirlevel, nameonly, iso_level, is_dir): ''' A function to build an absolute ISO path from a DirLevel object, a name, an ISO interchange level, and whether this is a directory or not. Parameters: parent_dirlevel - The DirLevel object representing the parent. nameonly - The basename of the ISO path. iso_level - The ISO interchange level to use. is_dir - Whether this is a directory or not. Returns: A string representing the ISO absolute path. ''' # Mangling the name and keeping track is a complicated affair. First off, # we mangle the incoming dirname so it conforms to ISO9660 rules (see # mangle_dir_for_iso9660() for more info on that). Once we have the # mangled name, we see if that name has been used at this directory level # yet. If it has not been used, we mark it as now used, and return it # unmolested (beyond the ISO9660 mangle). If it has been used, then we # need to strip it down to its prefix (the first 4 characters), add a # 3-digit number starting at zero, then iterate until we find a free one. # Once we have found a free one, we mark it as now used, and return what # we figured out. if is_dir: filemangle = pycdlib.utils.mangle_dir_for_iso9660(nameonly, iso_level) else: filename, ext = pycdlib.utils.mangle_file_for_iso9660(nameonly, iso_level) if ext == '': filemangle = filename else: filemangle = '.'.join([filename, ext]) if filemangle in parent_dirlevel.mangled_children: currnum = 0 prefix = filemangle[:5] while True: if is_dir: tmp = '%s%.03d' % (prefix, currnum) else: tmp = '%s%.03d.%s' % (prefix, currnum, ext) if tmp not in parent_dirlevel.mangled_children: filemangle = tmp break currnum += 1 if currnum == 1000: return None parent_dirlevel.mangled_children[filemangle] = True parent_iso_path = parent_dirlevel.iso_path if parent_dirlevel.iso_path == '/': parent_iso_path = parent_dirlevel.iso_path[1:] return parent_iso_path + '/' + filemangle ################################### MAIN ####################################### def main(): ''' The main function for this executable that does the main work of generating an ISO given the parameters specified by the user. ''' args = parse_arguments() eltorito_entries = determine_eltorito_entries(args) if args.quiet: logfp = open(os.devnull, 'w') else: if args.log_file is not None: print('re-directing all messages to %s' % (args.log_file)) logfp = open(args.log_file, 'w') else: logfp = sys.stdout print('pycdlib-genisoimage 1.0.0', file=logfp) # Check out all of the arguments we can here. if args.version: sys.exit(0) rock_version = None if args.rational_rock or args.rock: rock_version = '1.09' if args.rrip110: rock_version = '1.10' if args.rrip112: rock_version = '1.12' udf_version = None if args.udf or args.UDF: udf_version = '2.60' if args.joliet and rock_version is None: print('Warning: creating filesystem with Joliet extensions but without Rock Ridge', file=logfp) print(' extensions. It is highly recommended to add Rock Ridge.', file=logfp) if args.eltorito_catalog is not None and not eltorito_entries: print('genisoimage: No boot image specified.', file=logfp) sys.exit(255) if args.i is not None: print('genisoimage: -i option no longer supported.', file=logfp) sys.exit(255) hidden_patterns = args.hidden for pattern in parse_file_list(args.hidden_list): hidden_patterns.append(pattern) exclude_patterns = args.exclude + args.old_exclude for pattern in parse_file_list(args.exclude_list): exclude_patterns.append(pattern) hide_patterns = args.hide for pattern in parse_file_list(args.hide_list): hide_patterns.append(pattern) hide_joliet_patterns = args.hide_joliet for pattern in parse_file_list(args.hide_joliet_list): hide_joliet_patterns.append(pattern) hide_udf_patterns = args.hide_udf for pattern in parse_file_list(args.hide_udf_list): hide_udf_patterns.append(pattern) ignore_patterns = [] if args.nobak: ignore_patterns.extend(('*~*', '*#*', '*.bak')) if args.print_size: fp = BytesIO() else: if args.output is None: print('Output file must be specified (use -o)', file=logfp) sys.exit(1) fp = open(args.output, 'wb') # Figure out Joliet flag, which is the combination of args.joliet # and args.ucs_level. joliet_level = None if args.joliet: joliet_level = 3 if args.ucs_level is not None: joliet_level = int(args.ucs_level) eltorito_catalog_path = '' eltorito_catalog_parts = [] if args.eltorito_catalog is not None: eltorito_catalog_parts = args.eltorito_catalog.split('/') # Create a new PyCdlib object. iso = pycdlib.PyCdlib() if args.hide_rr_moved: iso.set_relocated_name('_RR_MOVE', '.rr_moved') # Create a new ISO. iso.new(interchange_level=args.iso_level, sys_ident=args.sysid, vol_ident=args.volid, set_size=args.volset_size, seqnum=args.volset_seqno, vol_set_ident=args.volset, pub_ident_str=args.publisher, preparer_ident_str=args.preparer, app_ident_str=args.appid, copyright_file=args.copyright, abstract_file=args.abstract, bibli_file=args.biblio, joliet=joliet_level, rock_ridge=rock_version, xa=(args.XA or args.xa), udf=udf_version) path_list = args.paths if args.path_list is not None: for line in fileinput.input(args.path_list): path_list.append(line.strip()) size_to_name_and_hashes = {} for path in path_list: check_eltorito_catalog = len(eltorito_catalog_parts) > 0 root_level = DirLevel('/', '/', '/') for eltorito_entry in eltorito_entries: eltorito_entry.dirlevel = root_level entries = collections.deque([(os.path.normpath(path), root_level, False, check_eltorito_catalog)]) while entries: localpath, parent_level, add_dir, check_eltorito_catalog = entries.popleft() basename = os.path.basename(localpath) if check_eltorito_catalog and len(eltorito_catalog_parts) == 1: filename, ext = pycdlib.utils.mangle_file_for_iso9660(eltorito_catalog_parts.pop(), args.iso_level) eltorito_catalog_path += '/' + filename + '.' + ext for eltorito_entry in eltorito_entries: if eltorito_entry.dirlevel == parent_level and len(eltorito_entry.bootfile_parts) == 1: filename, ext = pycdlib.utils.mangle_file_for_iso9660(eltorito_entry.bootfile_parts.pop(), args.iso_level) tail = '.' + ext if ext == '': tail = '' eltorito_entry.bootfile_iso_path += '/' + filename + tail rr_name = None if args.rational_rock or args.rock: rr_name = basename joliet_path = None if args.joliet: joliet_path = build_joliet_path(parent_level.joliet_path, basename) udf_path = None if args.udf or args.UDF: udf_path = build_udf_path(parent_level.udf_path, basename) if os.path.islink(localpath): if (not args.rational_rock or args.rock) and (not args.udf or args.UDF): print('Symlink %s ignored - continuing.' % (localpath), file=logfp) else: iso_path = build_iso_path(parent_level, basename, args.iso_level, False) if iso_path is None: print('Could not find free ISO9660 name for path %s; skipping' % (localpath), file=logfp) continue rr_target = None if args.rational_rock or args.rock: rr_target = os.readlink(localpath) udf_target = None if args.udf or args.UDF: udf_target = os.readlink(localpath) iso.add_symlink(iso_path, rr_symlink_name=rr_name, rr_path=rr_target, udf_symlink_path=udf_path, udf_target=udf_target, joliet_path=joliet_path) elif os.path.isdir(localpath): if add_dir: iso_path = build_iso_path(parent_level, basename, args.iso_level, True) if iso_path is None: print('Could not find free ISO9660 name for path %s; skipping' % (localpath), file=logfp) continue depth = iso_path.count('/') if rr_name is None and depth > 7: print("Directories too deep for '%s' (%d) max is 7; ignored - continuing." % (localpath, depth), file=logfp) continue iso.add_directory(iso_path, rr_name=rr_name, joliet_path=joliet_path, udf_path=udf_path) else: iso_path = parent_level.iso_path joliet_path = parent_level.joliet_path udf_path = parent_level.udf_path on_eltorito_catalog_path = False eltorito_duplicate_check = '' if check_eltorito_catalog and len(eltorito_catalog_parts) > 1 and eltorito_catalog_parts[0] == basename: eltorito_catalog_path += '/' + pycdlib.utils.mangle_dir_for_iso9660(basename, args.iso_level) eltorito_catalog_parts.pop(0) on_eltorito_catalog_path = True if len(eltorito_catalog_parts) == 1: eltorito_duplicate_check = eltorito_catalog_parts[0] parent = DirLevel(iso_path, joliet_path, udf_path) for eltorito_entry in eltorito_entries: if parent_level == eltorito_entry.dirlevel and len(eltorito_entry.bootfile_parts) > 1 and eltorito_entry.bootfile_parts[0] == basename: eltorito_entry.bootfile_iso_path += '/' + pycdlib.utils.mangle_dir_for_iso9660(basename, args.iso_level) eltorito_entry.bootfile_parts.pop(0) eltorito_entry.dirlevel = parent for f in os.listdir(localpath): fullpath = os.path.join(localpath, f) if match_entry_to_list(exclude_patterns, f) or eltorito_duplicate_check == f: print('Excluded by match: %s' % (fullpath), file=logfp) continue if match_entry_to_list(ignore_patterns, f): print('Ignoring file %s' % (fullpath), file=logfp) continue if args.verbose: print('Scanning %s' % (fullpath), file=logfp) entries.append((fullpath, parent, True, on_eltorito_catalog_path)) else: iso_path = build_iso_path(parent_level, basename, args.iso_level, False) if iso_path is None: print('Could not find free ISO9660 name for path %s; skipping' % (localpath), file=logfp) duplicate_name = None if args.scan_for_duplicates: size = os.stat(localpath).st_size if size in size_to_name_and_hashes: thishash = mm3hashfromfile(localpath) for index, name_and_hashes in enumerate(size_to_name_and_hashes[size]): oldlocal, oldiso, oldhash = name_and_hashes if oldhash is None: # The old hash was None, make it, set it, and # test against it oldhash = mm3hashfromfile(oldlocal) size_to_name_and_hashes[size][index][2] = oldhash if thishash == oldhash: duplicate_name = oldiso break else: # Something with the same size and same hash was not # in the list. Add this one. size_to_name_and_hashes[size].append([localpath, iso_path, thishash]) else: # Nothing with the same size was in the list. Put it in # the size list, but defer calculating the hash until # something else tries to match against it. size_to_name_and_hashes[size] = [[localpath, iso_path, None]] if duplicate_name is not None: iso.add_hard_link(iso_old_path=duplicate_name, iso_new_path=iso_path, rr_name=rr_name) if joliet_path is not None: iso.add_hard_link(iso_old_path=duplicate_name, joliet_new_path=joliet_path) if udf_path is not None: iso.add_hard_link(iso_old_path=duplicate_name, udf_new_path=udf_path) else: iso.add_file(localpath, iso_path, rr_name=rr_name, joliet_path=joliet_path, udf_path=udf_path) if match_entry_to_list(hide_patterns, basename): iso.rm_hard_link(iso_path=iso_path) if args.joliet and match_entry_to_list(hide_joliet_patterns, basename): iso.rm_hard_link(joliet_path=joliet_path) if args.udf and match_entry_to_list(hide_udf_patterns, basename): iso.rm_hard_link(udf_path=udf_path) if match_entry_to_list(hidden_patterns, basename): iso.set_hidden(iso_path) print('Hidden ISO9660 attribute: %s' % (localpath), file=logfp) # Add in El Torito if it was requested for entry in eltorito_entries: try: iso.add_eltorito(entry.bootfile_iso_path, bootcatfile=eltorito_catalog_path, bootable=entry.boot, boot_load_size=entry.load_size, boot_info_table=entry.boot_info_table, media_name=entry.mediatype, boot_load_seg=entry.load_seg) except pycdlib.pycdlibexception.PyCdlibInvalidInput as e: if 'Could not find path' in str(e): print("Uh oh, I cant find the boot image '%s' !" % (entry.bootfile_orig), file=logfp) sys.exit(255) else: raise class ProgressData(object): ''' A private class to hold onto the data from the last progress call. ''' __slots__ = ('last_percent', 'logfp', 'begun') def __init__(self, logfp): self.last_percent = '' self.logfp = logfp self.begun = time.time() def progress_cb(done, total, progress_data): ''' A private function that will be passed into the write_fp method of the PyCdlib object, and prints out the current progress of the mastering. Parameters (as prescribe by PyCdlib): done - The amount of data written so far total - The total amount of data to write progress_data - An object of type ProgressData to track progress Returns: Nothing. ''' frac = float(done) / float(total) percent = '%.2f%%' % (frac * 100) if percent != progress_data.last_percent: the_end = time.time() if frac > 0: the_end = progress_data.begun + (the_end - progress_data.begun) / frac print('%s done, estimate finish %s' % (percent, time.ctime(the_end)), file=progress_data.logfp) progress_data.last_percent = percent iso.write_fp(fp, progress_cb=progress_cb, progress_opaque=ProgressData(logfp)) if args.print_size: print('Total extents scheduled to be written = %d' % (len(fp.getvalue()) / 2048), file=logfp) iso.close() if __name__ == '__main__': main()