From cb4a54b91bf2e7a3315722ad77c5a13a1809f7f4 Mon Sep 17 00:00:00 2001 From: Tom Callaway Date: Apr 26 2011 21:29:26 +0000 Subject: Initial commit --- diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/multiboot-media-creator.py b/multiboot-media-creator.py new file mode 100755 index 0000000..c9216d2 --- /dev/null +++ b/multiboot-media-creator.py @@ -0,0 +1,738 @@ +#!/usr/bin/python +# This is a reimimplemtation of the MultiImage-Media-Creator in Python. +# Initial work on the bash version of this was done by Dave Riches, Bob Jensen +# and Dennis Johnson +# +# Copyright (C) 2011 Tom Callaway +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. See http://www.gnu.org/copyleft/gpl.html for +# the full text of the license. +# +# Inspiration and implementation ideas borrowed from fedpkg by Jesse Keating +# which is also available under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. + +import argparse +import glob +import locale +import os +import re +import shutil +import subprocess +import sys + +name = 'Multiboot Media Creator' +version = 0.5 +my_description = '{0} {1}'.format(name, version) + +def makehelperdirs(imagedir, iso_basename, type, verbose): + if type == "grub": + dirs = ['boot', 'images', 'CHECKSUM'] + else: + dirs = ['isolinux', 'images', 'CHECKSUM'] + for dir in dirs: + if os.path.isdir(os.path.join(imagedir, iso_basename, dir)): + # The directory exists, do nothing. + if verbose: + print '{0} exists, skipping directory creation.'.format(os.path.join(imagedir, iso_basename, dir)) + else: + if verbose: + print 'Creating directory: {0}'.format(os.path.join(imagedir, iso_basename, dir)) + try: + os.makedirs(os.path.join(imagedir, iso_basename, dir)) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make an image directory as {0}, but I failed with error {1}. Exiting.'.format(os.makedirs(os.path.join(imagedir, iso_basename, dir)), error)) + +def makeisolinuximage(isolist, imagedir, mountdir, timeout, bootdefaultnum, targetiso, targetname, isolinuxsplash, isodir, nomultiarch, verbose): + # If the nomultiarch flag is set to true, disable multiarch. Otherwise, enable multiarch. + if nomultiarch: + multiarch = False + else: + multiarch = True + + # In the specific case where we have a valid multiarch pair target, and we set it as the default, we don't want to set the individual + # item as default. In all other cases, we do. So we use this variable to track it. + pairedtarget_is_default = False + + # Make the isolinux directory + isolinuxdir = os.path.join(imagedir, 'isolinux') + isolinuxconf = os.path.join(isolinuxdir, 'isolinux.cfg') + if verbose: + print 'Making the directory for isolinux in IMAGEDIR: {0}'.format(isolinuxdir) + try: + os.makedirs(isolinuxdir) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make the isolinux directory as {0}, but I failed with error {1}. Exiting.'.format(isolinuxdir, error)) + # Handle splash image + isolinuxsplash_basename = os.path.basename(isolinuxsplash) + if verbose: + print 'Copying {0} splash file to {1}.'.format(isolinuxsplash, isolinuxdir) + shutil.copy2(isolinuxsplash, isolinuxdir) + + # Open our master config file. + masterconf = open(isolinuxconf, 'w') + + # Write our header into the master config file + # TODO: This is based on the Fedora 14 header, probably needs to be cleaned up. + + masterconf.write('default vesamenu.c32\n') + masterconf.write('timeout {0}\n'.format(timeout)) + masterconf.write('\n') + # masterconf.write('display boot.msg\n') + # masterconf.write('\n') + masterconf.write('menu background {0}\n'.format(isolinuxsplash_basename)) + masterconf.write('menu title Welcome to {0}\n'.format(targetname)) + # TODO: Configurable color codes? + masterconf.write('menu color border 0 #ffffffff #00000000\n') + masterconf.write('menu color sel 7 #ffffffff #ff000000\n') + masterconf.write('menu color title 0 #ffffffff #00000000\n') + masterconf.write('menu color tabmsg 0 #ffffffff #00000000\n') + masterconf.write('menu color unsel 0 #ffffffff #00000000\n') + masterconf.write('menu color hotsel 0 #ff000000 #ffffffff\n') + masterconf.write('menu color hotkey 7 #ffffffff #ff000000\n') + masterconf.write('menu color scrollbar 0 #ffffffff #00000000\n') + masterconf.write('\n') + + # This file is where we store the normal target entries + ffile = os.path.join(imagedir, 'normaltargets.part') + f = open(ffile, 'w') + + # This file is where we store the isolinux config bits for Live Images + bvtfile = os.path.join(imagedir, 'basicvideotargets.part') + bvt = open(bvtfile, 'w') + bvt.write('menu begin\n') + bvt.write('menu title Boot (Basic Video)\n') + bvt.write('\n') + + # This file is where we store the Verify Media targets + # If all our targets are non-live, we won't have anything here. + # Hence, we default to false. + verifyentries = False + vtfile = os.path.join(imagedir, 'verifytargets.part') + vt = open(vtfile, 'w') + vt.write('menu begin\n') + vt.write('menu title Verify and Boot...\n') + vt.write('\n') + + # This file is where we store any multiarch targets + # If we're not in multiarch mode, or we don't find any multiarch targets, + # this file will be empty. + multiarchentries = False + matfile = os.path.join(imagedir, 'multiarchtargets.part') + mat = open(matfile, 'w') + + for (counter, iso) in enumerate(isolist): + basename = os.path.basename(iso) + iso_basename = os.path.splitext(basename)[0] + pretty_iso_basename = re.sub(r'-', ' ', iso_basename) + + # isolinux can't read directories or files longer than 31 characters. + # Truncate if we need to. (Yes, this could cause issues. :P) + if len(iso_basename) > 31: + small_iso_basename = iso_basename[:31] + if verbose: + print '{0} is {1}, this is longer than the isolinux 31 character max.'.format(iso_basename, len(iso_basename)) + print 'In the isolinux.cfg, we will refer to it as {0}.'.format(small_iso_basename) + else: + small_iso_basename = iso_basename + + # Are we in multiarch mode? + if multiarch: + if verbose: + print 'Multiarch mode enabled. Checking {0} to to see if it matches i[3456]+86'.format(iso) + # Okay we are. Lets see if this string is i*86. + ia32_pattern = re.compile('i[3456]+86') + if ia32_pattern.search(iso): + if verbose: + print '{0} matches the i[3456]+86 pattern, looking for the x86_64 partner...'.format(iso) + # Hey, it matches! Lets make the x86_64 iso name. + x86_64_iso = ia32_pattern.sub('x86_64', iso) + # Now, we check for it in the list of isos + if isolist.count(x86_64_iso): + # Found it + if verbose: + print 'Found {0} partner iso for {1}!. Writing out multiarch entry for the pair.'.format(x86_64_iso, iso) + pairfound = True + multiarchentries = True + else: + # Did not find it. :( + if verbose: + print 'Could not find {0} partner iso for {1}. Going to write out a non-multiarch entry for {1}.'.format(x86_64_iso, iso) + pairfound = False + else: + if verbose: + print '{0} did not match i[3456]+86'.format(iso) + # If it isn't i[3456]+86, then it has no pair. + pairfound = False + else: + if verbose: + print 'Multiarch mode disabled.' + # pairfound is always false when multiarch is disabled + pairfound = False + + # Now, we need to loopback mount this ISO. + if verbose: + print 'Loopback mounting {0} on {1}'.format(iso, mountdir) + mount_command = 'mount -o loop "{0}" "{1}"'.format(iso, mountdir) + result = os.system(mount_command) + if result: + sys.exit('I tried to run {0}, but it failed. Exiting.'.format(mount_command)) + isolinux_config = open(os.path.join(mountdir, 'isolinux/isolinux.cfg'), 'r') + isolinux_config_text = isolinux_config.read() + isolinux_config.close() + search_string = 'live' + index = isolinux_config_text.find(search_string) + # If index is -1, then the search_string is not in the text. + if index >= 0: + # Okay, this is a Live ISO. + if verbose: + print '{0} is a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + shutil.copytree(mountdir, os.path.join(imagedir, iso_basename)) + + if pairfound: + x86_64_basename = os.path.basename(x86_64_iso) + x86_64_iso_basename = os.path.splitext(x86_64_basename)[0] + + pairname = re.sub(r'i[3456]+86-', '', iso_basename) + pretty_pairname = re.sub(r'-', ' ', pairname) + mat.write('label {0}\n'.format(pairname)) + mat.write(' menu label Autoselect x86_64 / i686 {0}\n'.format(pretty_pairname)) + mat.write(' kernel ifcpu64.c32\n') + # This only works if the ia32 target is set as the default, since it it will trigger the target creation. + if counter == bootdefaultnum: + mat.write(' menu default\n') + # We need to know that we set the paired target as the default + pairedtarget_is_default = True + mat.write(' append {0} -- {1}\n'.format(x86_64_iso_basename, iso_basename)) + mat.write('\n') + + # Write out non-multiboot items + if verbose: + print 'Writing ISO specific entry for {0} into isolinux configs.'.format(iso_basename) + f.write('label {0}\n'.format(iso_basename)) + f.write(' menu label Boot {0}\n'.format(pretty_iso_basename)) + if counter == bootdefaultnum: + if pairedtarget_is_default: + # The paired target inherited the default setting, so we don't set it here. + if verbose: + print 'Since the default is in the paired target, we are not setting this individual item as the default.' + else: + f.write(' menu default\n') + # Note that we only need the small_iso_basename for pathing that isolinux will use (kernel and initrd path). All other pathing should use iso_basename. + f.write(' kernel /{0}/isolinux/vmlinuz0\n'.format(small_iso_basename)) + f.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM\n'.format(small_iso_basename, targetname, iso_basename)) + f.write('\n') + + # Now, we write out the basic video entry + bvt.write('label {0}_basicvideo\n'.format(iso_basename)) + bvt.write(' menu label {0} (Basic Video)\n'.format(pretty_iso_basename)) + if counter == bootdefaultnum: + bvt.write(' menu default\n') + # Note that we only need the small_iso_basename for pathing that isolinux will use (kernel and initrd path). All other pathing should use iso_basename. + bvt.write(' kernel /{0}/isolinux/vmlinuz0\n'.format(small_iso_basename)) + bvt.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM xdriver=vesa nomodeset\n'.format(small_iso_basename, targetname, iso_basename)) + bvt.write('\n') + + # And last, we write out the verify entry + verifyentries = True + vt.write('label {0}_verify\n'.format(iso_basename)) + vt.write(' menu label Verify and Boot {0}\n'.format(pretty_iso_basename)) + if counter == bootdefaultnum: + vt.write(' menu default\n') + # Note that we only need the small_iso_basename for pathing that isolinux will use (kernel and initrd path). All other pathing should use iso_basename. + vt.write(' kernel /{0}/isolinux/vmlinuz0\n'.format(small_iso_basename)) + vt.write(' append initrd=/{0}/isolinux/initrd0.img root=live:CDLABEL={1} live_dir=/{2}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb rd_NO_LUKS rd_NO_MD rd_NO_DM check\n'.format(small_iso_basename, targetname, iso_basename)) + vt.write('\n') + + makehelperdirs(imagedir, iso_basename, "isolinux", verbose) + checksum_name = '{0}-CHECKSUM'.format(iso_basename) + checksum_file = os.path.join(isodir, checksum_name) + if os.path.isfile(checksum_file): + if verbose: + print 'Copying {0} checksum file to {1}.'.format(checksum_file, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + shutil.copy2(checksum_file, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + else: + print 'Could not locate {0} in isodir ({1}). Continuing, but {2} will always fail verification.\n'.format(checksum_name, isodir, iso_basename) + + else: + # Not a Live ISO. + if verbose: + print '{0} is NOT a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + makehelperdirs(imagedir, iso_basename, "isolinux", verbose) + shutil.copy2(os.path.join(mountdir, 'isolinux/vmlinuz'), os.path.join(imagedir, iso_basename, 'isolinux/vmlinuz')) + shutil.copy2(os.path.join(mountdir, 'isolinux/initrd.img'), os.path.join(imagedir, iso_basename, 'isolinux/initrd.img')) + if os.path.isfile(os.path.join(mountdir, 'images/install.img')): + shutil.copy2(os.path.join(mountdir, 'images/install.img'), os.path.join(imagedir, iso_basename, 'images/install.img')) + + if pairfound: + x86_64_basename = os.path.basename(x86_64_iso) + x86_64_iso_basename = os.path.splitext(x86_64_basename)[0] + + pairname = re.sub(r'i[3456]+86-', '', iso_basename) + pretty_pairname = re.sub(r'-', ' ', pairname) + mat.write('label {0}\n'.format(pairname)) + mat.write(' menu label Autoselect x86_64 / i686 {0}\n'.format(pretty_pairname)) + mat.write(' kernel ifcpu64.c32\n') + # This only works if the ia32 target is set as the default, since it it will trigger the target creation. + if counter == bootdefaultnum: + mat.write(' menu default\n') + mat.write(' append {0} -- {1}\n'.format(x86_64_iso_basename, iso_basename)) + mat.write('\n') + + f.write('label {0}\n'.format(iso_basename)) + f.write(' menu label Install {0}\n'.format(pretty_iso_basename)) + if counter == bootdefaultnum: + if pairedtarget_is_default: + # The paired target inherited the default setting, so we don't set it here. + if verbose: + print 'Since the default is in the paired target, we are not setting this individual item as the default.' + else: + f.write(' menu default\n') + f.write(' kernel /{0}/isolinux/vmlinuz\n'.format(small_iso_basename)) + # Note that we only need the small_iso_basename for pathing that isolinux will use (kernel and initrd path). All other pathing should use iso_basename. + f.write(' append initrd=/{0}/isolinux/initrd.img repo=hd:LABEL={1}:/{2}/\n'.format(small_iso_basename, targetname, iso_basename)) + f.write('\n') + + # Now, we write out the basic video entry + bvt.write('label {0}_basicvideo\n'.format(iso_basename)) + bvt.write(' menu label {0} (Basic Video)\n'.format(pretty_iso_basename)) + if counter == bootdefaultnum: + bvt.write(' menu default\n') + # Note that we only need the small_iso_basename for pathing that isolinux will use (kernel and initrd path). All other pathing should use iso_basename. + bvt.write(' kernel /{0}/isolinux/vmlinuz\n'.format(small_iso_basename)) + bvt.write(' append initrd=/{0}/isolinux/initrd.img repo=hd:LABEL={1}:/{2} xdriver=vesa nomodeset\n'.format(small_iso_basename, targetname, iso_basename)) + bvt.write('\n') + + # Only Live ISOs have a verify mode as a command line option. Non-Live ISOs always prompt to check in anaconda. + # So we don't need to write anything to vt here. + + if verbose: + print 'Copying {0} into {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + shutil.copy2(iso, os.path.join(imagedir, iso_basename)) + # Figure out what the CHECKSUM name should be + p = re.compile( '(DVD)') + checksum_name = p.sub( 'CHECKSUM', iso_basename) + for checksum in glob.glob('{0}*'.format(checksum_name)): + if verbose: + print 'Copying {0} checksum file to {1}.'.format(checksum, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + shutil.copy2(checksum, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + # Unmount the iso + unmount_command = 'umount "{0}"'.format(mountdir) + result = os.system(unmount_command) + if result: + sys.exit('I tried to run {0}, but it failed. Exiting.'.format(unmount_command)) + + # We're now done writing to the multiboot and normal configs + mat.close() + f.close() + + # Now, we need to append mat to the master config file + # But only if we found some multiarch entries + if multiarchentries: + masterconf.write(open(matfile).read()) + + # Now, add the normal entries to the master config file + masterconf.write(open(ffile).read()) + + # Add the separator in the master config + masterconf.write('menu separator\n') + masterconf.write('\n') + masterconf.write('menu end\n') + masterconf.write('\n') + + # Write the footers for the menus in vt and bvt + vt.write('menu separator\n') + vt.write('\n') + vt.write('label return\n') + vt.write(' menu label Return to main menu...\n') + vt.write(' menu exit\n') + vt.write('\n') + vt.write('menu end\n') + + bvt.write('menu separator\n') + bvt.write('\n') + bvt.write('label return\n') + bvt.write(' menu label Return to main menu...\n') + bvt.write(' menu exit\n') + bvt.write('\n') + bvt.write('menu end\n') + + # We are now done writing to vt and bvt + vt.close() + bvt.close() + + # We will always have bvt entries, so write them to the master file now. + masterconf.write(open(bvtfile).read()) + + # We might not have vt entries, check to see if we got any first. + if verifyentries: + masterconf.write(open(vtfile).read()) + + # At this point, we no longer need matfile, ffile, vtfile or bvtfile, and we don't want them written on the image + os.remove(matfile) + os.remove(ffile) + os.remove(vtfile) + os.remove(bvtfile) + + # Here's our master footer. + masterconf.write('\n') + masterconf.write('label memtest\n') + masterconf.write(' menu label Memory Test\n') + masterconf.write(' kernel memtest\n') + masterconf.write('\n') + masterconf.write('label local\n') + masterconf.write(' menu label Boot from local drive\n') + masterconf.write(' localboot 0xffff\n') + + # We're done writing to the master isolinux.cfg file! + masterconf.close() + if verbose: + print 'Copying /usr/share/syslinux/isolinux.bin to {0}'.format(isolinuxdir) + shutil.copy2('/usr/share/syslinux/isolinux.bin', isolinuxdir) + if verbose: + print 'Copying /usr/share/syslinux/vesamenu.c32 to {0}'.format(isolinuxdir) + shutil.copy2('/usr/share/syslinux/vesamenu.c32', isolinuxdir) + # We only need to copy the ifcpu64.c32 file if we have multiarchentries in the isolinux.cfg + if multiarchentries: + if verbose: + print 'Copying /usr/share/syslinux/ifcpu64.c32 to {0}'.format(isolinuxdir) + shutil.copy2('/usr/share/syslinux/ifcpu64.c32', isolinuxdir) + mkisofs_command = '/usr/bin/mkisofs -allow-leading-dots -allow-multidot -l -relaxed-filenames -no-iso-translate -R -v -V {0} -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -allow-limited-size -o {1} {2}'.format(targetname, targetiso, imagedir) + if verbose: + print 'Running mkisofs to make {0}:'.format(targetiso) + print mkisofs_command + os.system(mkisofs_command) + # subprocess.Popen(mkisofs_command) + +def makegrubimage(isolist, imagedir, mountdir, timeout, bootdefaultnum, grubarch, targetiso, targetname, isodir, verbose): + # Make the grub directory + grubdir = os.path.join(imagedir, 'boot/grub') + grubconf = os.path.join(grubdir, 'grub.conf') + if verbose: + print 'Making the directory for grub in IMAGEDIR: {0}'.format(grubdir) + try: + os.makedirs(grubdir) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make the grub directory as {0}, but I failed with error {1}. Exiting.'.format(grubdir, error)) + # Write our header + f = open(grubconf, 'w') + f.write('timeout={0}\n'.format(timeout)) + f.write('default={0}\n'.format(bootdefaultnum)) + + for iso in isolist: + basename = os.path.basename(iso) + iso_basename = os.path.splitext(basename)[0] + + # Now, we need to loopback mount this ISO. + if verbose: + print 'Loopback mounting {0} on {1}'.format(iso, mountdir) + mount_command = 'mount -o loop "{0}" "{1}"'.format(iso, mountdir) + result = os.system(mount_command) + if result: + sys.exit('I tried to run {0}, but it failed. Exiting.'.format(mount_command)) + isolinux_config = open(os.path.join(mountdir, 'isolinux/isolinux.cfg'), 'r') + isolinux_config_text = isolinux_config.read() + isolinux_config.close() + search_string = 'live' + index = isolinux_config_text.find(search_string) + # If index is -1, then the search_string is not in the text. + if index >= 0: + # Okay, this is a Live ISO. + if verbose: + print '{0} is a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + shutil.copytree(mountdir, os.path.join(imagedir, iso_basename)) + if verbose: + print 'Writing ISO specific entry for {0} into grub configs.'.format(iso_basename) + f.write('title {0}\n'.format(iso_basename)) + f.write(' kernel /{0}/isolinux/vmlinuz0 root=live:LABEL={1} live_dir=/{0}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb\n'.format(iso_basename, targetname)) + f.write(' initrd /{0}/isolinux/initrd0.img\n'.format(iso_basename)) + submenu = open(os.path.join(grubdir, 'submenu.lst'), 'w') + submenu.write('title {0}\n'.format(iso_basename)) + submenu.write(' kernel /{0}/isolinux/vmlinuz0 root=live:LABEL={1} live_dir=/{0}/LiveOS/ rootfstype=auto ro liveimg quiet rhgb check\n'.format(iso_basename, targetname)) + submenu.write(' initrd /{0}/isolinux/initrd0.img\n'.format(iso_basename)) + submenu.close() + makehelperdirs(imagedir, iso_basename, "grub", verbose) + else: + # Not a Live ISO. + if verbose: + print '{0} is NOT a Live ISO. Copying files to {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + makehelperdirs(imagedir, iso_basename, "grub", verbose) + shutil.copy2(os.path.join(mountdir, 'isolinux/vmlinuz'), os.path.join(imagedir, iso_basename, 'boot/')) + shutil.copy2(os.path.join(mountdir, 'isolinux/initrd.img'), os.path.join(imagedir, iso_basename, 'boot/')) + if os.path.isfile(os.path.join(mountdir, 'images/install.img')): + shutil.copy2(os.path.join(mountdir, 'images/install.img'), os.path.join(imagedir, iso_basename, 'images/')) + f.write('title {0}\n'.format(iso_basename)) + f.write(' kernel /{0}/boot/vmlinuz repo=hd:LABEL={1}:/{0}/\n'.format(iso_basename, targetname)) + f.write(' initrd /{0}/boot/initrd.img\n'.format(iso_basename)) + if verbose: + print 'Copying {0} into {1}.'.format(iso, os.path.join(imagedir, iso_basename)) + shutil.copy2(iso, os.path.join(imagedir, iso_basename)) + # Figure out what the CHECKSUM name should be + p = re.compile( '(DVD)') + checksum_name = p.sub( 'CHECKSUM', iso_basename) + for checksum in glob.glob('{0}*'.format(checksum_name)): + if verbose: + print 'Copying {0} checksum file to {1}.'.format(checksum, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + shutil.copy2(checksum, os.path.join(imagedir, iso_basename, 'CHECKSUM/')) + # Unmount the iso + unmount_command = 'umount "{0}"'.format(mountdir) + result = os.system(unmount_command) + if result: + sys.exit('I tried to run {0}, but it failed. Exiting.'.format(unmount_command)) + + if os.path.isfile(os.path.join(grubdir, 'submenu.lst')): + f.write('title Verify Live Media\n') + f.write(' configfile /boot/grub/submenu.lst\n') + + # We're done writing to the grub file! + f.close() + + os.symlink('grub.conf', os.path.join(grubdir, 'menu.lst')) + shutil.copy2('/usr/share/grub/{0}-redhat/stage2_eltorito'.format(grubarch), grubdir) + mkisofs_command = '/usr/bin/mkisofs -R -v -V {0} -b boot/grub/stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -allow-limited-size -o {1} {2}'.format(targetname, targetiso, imagedir) + if verbose: + print 'Running mkisofs to make {0}:'.format(targetiso) + print mkisofs_command + os.system(mkisofs_command) + # subprocess.Popen(mkisofs_command) + +def parse_isolist(isolist, isodir, parsedisolist, verbose): + for iso in isolist: + # First, lets check if we've been passed a wildcard + # and if so, glob it out to the specific items. + if '*' in iso: + if verbose: + print '{0} is a wildcard, looking for matches ...'.format(iso) + globlist = glob.glob(iso) + # Did we find anything? + if globlist: + if verbose: + print 'Found matches! Adding them now.' + for globbediso in globlist: + parsedisolist.append(globbediso) + else: + # Uhoh. This wildcard didn't match anything. Lets append isodir and try again. + isodir_plus_iso = os.path.join(isodir,iso) + if verbose: + print 'No matches found. Trying again with ISODIR ...' + isodir_plus_iso = os.path.join(isodir,iso) + globlist = glob.glob(isodir_plus_iso) + if globlist: + if verbose: + print 'Found matches! Adding them now.' + for globbediso in globlist: + parsedisolist.append(globbediso) + else: + if verbose: + print 'No matches found.' + # I can't find any matches for this wildcard. Bail out! + sys.exit('Error: You specified {0}, but I could not find any matches. Exiting.'.format(iso)) + else: + # Okay, this is a normal non-wildcard item. + # Check if it exists as is (fullpath) + if verbose: + print 'Checking for {0} ...'.format(iso) + if os.path.isfile(iso): + if verbose: + print 'Found!' + parsedisolist.append(iso) + else: + # Okay, no. Now we'll look in ISODIR + isodir_plus_iso = os.path.join(isodir,iso) + if verbose: + print 'Not found!' + print 'Checking for {0} ...'.format(isodir_plus_iso) + if os.path.isfile(isodir_plus_iso): + if verbose: + print 'Found!' + parsedisolist.append(isodir_plus_iso) + else: + if verbose: + print 'Not found!' + # Uhoh. This item doesn't exist. Evacuate! + sys.exit('Error: You specified {0}, but I could not find it. Exiting.'.format(iso)) + + +# This is where the magic happens +if __name__ == '__main__': + + # Locale magic + locale.setlocale(locale.LC_ALL, '') + + # Create the parser object + parser = argparse.ArgumentParser(description = my_description) + + parser.add_argument('--bootdefault', nargs = '?', metavar='NAMEOF.ISO', + help = 'Selects the default boot target by filename, if not set, defaults to the first item in the ISO list.') + + parser.add_argument('--clean', action = 'store_true', + help = 'Clean out and remove any files found in IMAGEDIR before starting.') + + parser.add_argument('--grub', action = 'store_true', + help = 'Use grub to handle the boot menu. The default is to use isolinux.') + + parser.add_argument('--grubarch', nargs = '?', default = 'i386', metavar = 'i386|x86_64', + help = 'Architecture for grub files. Valid choices are i386 or x86_64. Default is i386.') + + parser.add_argument('-i', '--isos', nargs = '*', + help = 'List of ISOs to use, either with full path or in ISODIR.') + + parser.add_argument('--imagedir', nargs = '?', default = os.path.join(os.curdir, 'image'), + help = 'Working directory for image creation. Defaults to {0}'.format(os.path.join(os.curdir, 'image'))) + + parser.add_argument('--isodir', nargs = '?', default = os.curdir, + help = 'System Directory where source ISOs (and CHECKSUM files) are stored. Defaults to .') + + parser.add_argument('--isolinuxsplash', nargs = 1, default = '/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg', + help = 'Full path to isolinux splash image. Defaults to /usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg') + + parser.add_argument('--mountdir', nargs = '?', default = os.path.join(os.curdir, 'mnt'), + help = 'Working directory for temporarily mounting source ISOs. Defaults to {0}'.format(os.path.join(os.curdir, 'mnt'))) + + parser.add_argument('--nomultiarch', action = 'store_true', + help = 'Disable multiarch support in isolinux.cfg (detect whether system is ia32 or x86_64 and choose correct image). \ + The default is to enable multiarch support. \ + Please note: You must pass matching i*86 and x86_64 isos in -i in order for this to work.') + + parser.add_argument('--nosort', action = 'store_true', + help = 'Do not sort the list of ISOs in locale specific alphabetic order. \ + The default is to perform this sort.') + + parser.add_argument('--target', nargs = '?', default = 'Multi-Boot.iso', metavar = 'Foo.iso', + help = 'Filename of target Multi Boot ISO image. Defaults to Multi-Boot.iso') + + parser.add_argument('--targetname', nargs = '?', metavar = 'NAME', default = 'Multi-Boot', + help = 'Name of Multi Boot ISO, not to be confused with filename. Defaults to Multi-Boot.') + + parser.add_argument('--timeout', metavar = 'INT', type = int, nargs = 1, default = 100, + help = 'Timeout for boot selection, default is 100.') + + # Verbosity, for debugging + parser.add_argument('-v', '--verbose', action = 'store_true', + help = 'Run with verbose debug output') + + parser.add_argument('--version', action = 'version', version = my_description, + help = 'Print program version information and exit') + + # Parse the args + args = parser.parse_args() + + if os.geteuid() != 0: + sys.exit('You must have root privileges to run this script successfully. Exiting.') + + # Are we being verbose? Lets get a summary of what we've learned from our args. + if args.verbose: + print 'Verbose mode is on.' + print 'IMAGEDIR is set to {0}'.format(args.imagedir) + print 'ISODIR is set to {0}'.format(args.isodir) + print 'MOUNTDIR is set to {0}'.format(args.mountdir) + + # Check the target name. Does it already exist? + if os.path.isfile(args.target): + sys.exit('The specified target ISO {0} already exists. Delete it before running this script. Exiting.'.format(args.target)) + + # Check the working directory (IMAGEDIR). Does it already exist? + if os.path.isdir(args.imagedir): + if args.verbose: + print 'Note: IMAGEDIR already exists.' + if os.listdir(args.imagedir): + # Uh oh. There are files in IMAGEDIR. Did the user tell us to clean it out? + if args.clean: + if args.verbose: + print 'Deleting IMAGEDIR and recreating it to ensure a clean and empty directory.' + shutil.rmtree(args.imagedir) + os.makedirs(args.imagedir) + else: + sys.exit('IMAGEDIR ({0}) exists and has files in it. Use --clean to empty it. Exiting.'.format(args.imagedir)) + else: + # IMAGEDIR doesn't exist, so we'll make it now. + if args.verbose: + print 'IMAGEDIR does not exist, creating it now.' + try: + os.makedirs(args.imagedir) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make IMAGEDIR as {0}, but I failed with error {1}. Exiting.'.format(args.imagedir, error)) + + # Check the mount directory (MOUNTDIR). Does it already exist? + if os.path.isdir(args.mountdir): + if args.verbose: + print 'Note: MOUNTDIR already exists.' + # Check to see if the system thinks something is mounted there already. + if os.path.ismount(args.mountdir): + sys.exit('Something is mounted at MOUNTDIR ({0}). Please unmount it and restart.'.format(args.mountdir)) + else: + # MOUNTDIR doesn't exist, so we'll make it now. + if args.verbose: + print 'MOUNTDIR does not exist, creating it now.' + try: + os.makedirs(args.mountdir) + except: + error = sys.exc_info()[1] + sys.exit('I was trying to make MOUNTDIR as {0}, but I failed with error {1}. Exiting.'.format(args.mountdir, error)) + + if args.isos: + isolist = [] + parse_isolist(args.isos, args.isodir, isolist, args.verbose) + else: + sys.exit('No ISOs specified, nothing to do, exiting.') + + if args.nosort: + if args.nomultiarch: + sys.exit('We need sorting to make multiarch support sane. Bailing out. If you really want to disable sorting, also pass --nomultiarch.') + else: + if args.verbose: + print 'Not sorting list of found ISOs, user override.' + else: + if args.verbose: + print 'Sorting list of found ISOs.' + sortedlist = sorted(isolist, cmp=locale.strcoll) + isolist = sortedlist + + # The default boot choice is the first item in the list, unless the user specifies otherwise. + bootdefaultnum = 0 + + if args.bootdefault: + # Let's make sure it is in the isolist first. + if args.bootdefault in isolist: + bootdefaultnum = isolist.index(args.bootdefault) + else: + # Maybe we need to add the ISODIR to it? + if os.path.join(args.isodir,args.bootdefault) in isolist: + bootdefaultnum = isolist.index(os.path.join(args.isodir,args.bootdefault)) + else: + # Bail out since I can't find the boot + sys.exit('Could not find the specified bootdefault ISO ({}) in the list of ISOs. Exiting.'.format(args.bootdefault)) + + if args.verbose: + print 'Here are the ISOs that I am going to add:' + for item in isolist: + print item + print 'The default is {0} in position {1}'.format(isolist[bootdefaultnum], bootdefaultnum) + + + if args.grub: + # Generating multiboot grub config + if args.grubarch != 'i386' and args.grubarch != 'x86_64': + sys.exit('The grubarch you have specified ({0}) is invalid. Exiting.'.format(args.grubarch)) + + if args.verbose: + print 'Generating multiboot grub image' + makegrubimage(isolist, args.imagedir, args.mountdir, args.timeout, bootdefaultnum, args.grubarch, args.target, args.targetname, args.isodir, args.verbose) + else: + if args.verbose: + print 'Generating multiboot isolinux image' + # Sanity check + if os.path.isfile('/usr/share/syslinux/isolinux.bin'): + if args.verbose: + print 'Found /usr/share/syslinux/isolinux.bin. Assuming syslinux is installed properly' + else: + sys.exit('Could not find /usr/share/syslinux/isolinux.bin ? Perhaps syslinux is not installed?') + makeisolinuximage(isolist, args.imagedir, args.mountdir, args.timeout, bootdefaultnum, args.target, args.targetname, args.isolinuxsplash, args.isodir, args.nomultiarch, args.verbose)