#!/usr/bin/perl
#
# This script fixes problems when trying to make a bootable Ubuntu USB stick from an ISO image.
# 
# How to use this script:
#
# 1. Make your bootable USB stick as per usual. I use "Linux Live USB creator".
#
# 2. Install perl 
# 	
# 	* Perl is generally installed by default on Linux boxes. If not, use your
# 	  package manager to install it.
#
# 	* For Windows 7, I use ActivePerl, which you can download from the web. 
#	  ActivePerl may have versions for other versions of Windows as well.
#
# 3. Run this script
#
# 	For Windows 7 and ActivePerl: 
# 		* Copy this file to the USB stick's root directory
# 		* Double click on the script's icon, and it will launch
#
# 	For Linux:
# 		* Copy this file to the USB stick's root directory
# 		* cd to the stick's root directory and run this script ("./fix_USB_boot_problems_v01.pl")
#
# -------------------------------------------------------------------------------------------
# How this script works
#
# I have never gotten Ubuntu on a USB stick to work. I know, I've read tons of comments
# on the internet that people generally get this working, so why does it always fail 
# for me? 
#
# The crux of the issue seems to be that when the USB stick is written from an ISO image, the
# filenames are written "as if" it were a CD-ROM. Now a CD-ROM has limitations as to the file 
# names that can be accomodated. The traditional explanation is that it can only be 64 characters. 
# On the other hand, many of the package names in Ubuntu LTS server (which is what I use) 
# have more characters than that in the filename. 
#
# So here is what I think might be happening. I believe that most ISO Linux images 
# use 64-char (max) filenames when they are created, in the belief that they will be 
# written to a CD-ROM. Perhaps when a *real* CD-ROM file system is given a file name that 
# is too long, the CD-ROM driver truncates that long name to a 64-char name. In that
# event everything will work -- the filename was written to the CD-ROM with only 64 chars
# max, and the CD-ROM driver, when given an overly long file name to read, will automatically
# truncate the filename to 64 chars, and so finds the file under the name it was written.
# However I speculate that the USB driver does not do this truncation, since a USB stick
# (as far as I know) has no limitations on the file name length. So, the USB driver is 
# handed a long file name, the driver does *not* truncate the file name, and the file
# (which was written with a truncated name in anticipation of being written to a CD-ROM)
# is not found. Errors result.
#
# This script fixes the problem (at least, for Ubuntu LTS server 12.04.2, which is what
# I tested it on). It renames the truncated files back to their original, un-truncated 
# names. You might well ask, if the files are written onto the USB stick in the first 
# place in truncated form, how can you possibly know what the original name of the file
# was? Some characters were lost when the filename was truncated. Well, we can get the 
# original names of the files from the md5sum.txt file, which has a md5 checksum of every
# file on the USB key (I think). The algorithm is to go through each filename in the md5
# checksum file; if the file already exists under that name, then we are fine. If the file
# is missing, then we check to see if a file exists with the truncated version of the
# filename. If *that* file exists, then we rename the truncated file back to the original 
# filename.
#
# Obviously this fails with very long file names that differ after 64 characters. For
# example, if you try to write these two filenames to a CD-ROM they will overwrite
# each other:
#
#     package_with_very_long_filename_that_is_more_than_64_chars_long_version-0001.deb
#     package_with_very_long_filename_that_is_more_than_64_chars_long_version-0002.deb
#
# In that case this script will fail, but there is nothing I can do about that. The
# USB key (and a CD-ROM version of it) would both be invalid in that case.
#
# David W. Pierce
# Division of Climate, Atmospheric Sciences, and Physical Oceanography
# Scripps Institution of Oceanography, La Jolla, CA
# dpierce@ucsd.edu
#
#----------------------------------------------------------------------------------
# Version 1 released 27 February 2013
#
#----------------------------------------------------------------------------------
#
# This is free and unencumbered software released into the public domain.
# 
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#  
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
# 
# 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 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.
# 
# For more information, please refer to <http://unlicense.org/>
#
#======================================================================================

print "starting!\n";

$fname_in = "md5sum.txt";

#------------------
# Open the md5 file
#------------------
print "opening input md5 file $fname_in\n";
if( ! open(MD5FILE_IN, "<".$fname_in)) {
	print "\n";
	print "==============================================================================\n";
	print "Error: I cannot find a file named $fname_in ; I cannot handle that case!\n";
	print "Did you copy this script to the root directory of the USB stick?\n";
	print "Are you running this script from the root directory of the USB stick?\n";
	print "==============================================================================\n";
	print "\nDone -- press the ENTER key to exit\n";
	$z = <>;
	exit 0;
	}

#-------------------------------------------------------------------
# Process each line. Each line should have two entries, first one is
# the md5 sum and second is the filename.
#-------------------------------------------------------------------
print "Processing, this might take a minute...\n";
$n_proc 	= 0;
$n_fixed 	= 0;
$n_not_fixed 	= 0;
while( <MD5FILE_IN> ) {
	$n_proc++;
	@t = split " ";
	$md5   = $t[0];
	$fname = $t[1];
	if( ! -e $fname ) {
		print "----------------------------------------------------\n";
		print "Found a problem, file does not exist: $fname\n";

		$nc = length( $fname );		# number chars in the filename

		#--------------------------------------------------------------
		# Find location of last period so we can identify the extension.
		# At same time get loc of last file name separator.
		#--------------------------------------------------------------
		$file_sep = "/";
		$loc_last_period = -1;
		$loc_last_fsep = -1;
		for( $i=0; $i<$nc; $i++ ) {
			if( substr($fname, $i, 1) eq "." ) {
					$loc_last_period = $i;
					}
			if( substr($fname, $i, 1) eq $file_sep ) {
					$loc_last_fsep = $i;
					}
			}

		# ---------------------------------------------------------
		# This is the file name stripped of all leading directories
		# ---------------------------------------------------------
		$fname_after_fsep = substr( $fname, $loc_last_fsep+1 );

		# ----------------------------
		# This is the file's extension
		# ----------------------------
		$ext = "";
		$has_extension = ($loc_last_period != -1);
		if( $has_extension ) {
			$ext = substr( $fname, $loc_last_period+1 );
			}

		# -------------------------------------------------------------
		# Basename is the file name stripped of all leading directories
		# and any extension
		# -------------------------------------------------------------
		if( $loc_last_fsep == -1 ) {
			$basename = $filename;
			$path = "";
			}
		else
			{
			$basename = substr( $fname, $loc_last_fsep+1, 
		       		length($fname_after_fsep) - (length($ext)+1) );
			$path = substr( $fname, 0, $loc_last_fsep );
			}

		$nc_after_fsep = length( $fname_after_fsep );

		# ---------------------------------------------------------
		# If this is a ".udeb" file, see if a simple ".ude" version
		# is there instead.
		# ---------------------------------------------------------
		if( $ext eq "udeb" ) {
			$fname_maybe = $path . $file_sep . $basename 
				. ".ude";
			if( -e $fname_maybe ) {
				print "...fixed with $fname_maybe\n";
				system( "mv " . $fname_maybe . " " . $fname );
				$n_fixed++;
				}
			else
				{
				# ------------------------------------------
				# Not there with a simple .ude extension -- 
				# maybe the entire filename is too long, and
				# was truncated?
				# ------------------------------------------
				$basename_trunc = substr( $basename, 0, 61 );
				$fname_maybe = $path . $file_sep 
					. $basename_trunc
					. ".ude";
					#print "trying: $fname_maybe\n";
				if( -e $fname_maybe ) {
					print "...fixed with $fname_maybe\n";
					system( "mv " . $fname_maybe . " " . $fname );
					$n_fixed++;
					}
				else
					{
					print "Warning! Did not find any fix for that file\n";
					$n_not_fixed++;
					}
				}
			}

		elsif( $ext eq "deb" ) {
			# --------------------------------------
			# See if the deb file's name is too long
			# --------------------------------------
			$basename_trunc = substr( $basename, 0, 61 );
			$fname_maybe = $path . $file_sep 
				. $basename_trunc
				. ".deb";
			if( -e $fname_maybe ) {
				print "...fixed with $fname_maybe\n";
				system( "mv " . $fname_maybe . " " . $fname );
				$n_fixed++;
				}
			else
				{
				print "Warning! Did not find any fix for that file\n";
				$n_not_fixed++;
				}
			}
		else
			{
			$n_not_fixed++;
			}
		}
	}

close( MD5FILE_IN  );

print "\n\n";
print "Summary:\n";
print "\n";
print "Number of files processed: $n_proc\n";
print "Number of files fixed    : $n_fixed\n";
print "Number of files NOT fixed: $n_not_fixed\n";

print "\nAll done -- you are ready to use your USB stick now.\nPress the ENTER key to exit\n";
$z = <>;

