#!/usr/bin/env perl #--------------------------------------------------------------------- # module setup #--------------------------------------------------------------------- # Filename: cdtoiso, ocdvdtoiso, cdmd5, dvdmd5, cdsha1, dvdsha1 # # I.e. This script can be run under different names for different pur- # poses. # Type: Linux Perl script # Purpose: Data CD and Video DVD utilities # Copyright: (c) 2008-2025 OldCoder aka Robert Kiraly # License: Creative Commons CC BY-NC-SA 4.0 International # Revision: 250304 #--------------------------------------------------------------------- # module setup #--------------------------------------------------------------------- require 5.10.1 ; use strict ; use Carp ; use warnings ; # Trap warnings $SIG {__WARN__} = sub { die @_; }; #--------------------------------------------------------------------- # basic constants #--------------------------------------------------------------------- use constant ZERO => 0; # Zero use constant ONE => 1; # One use constant TWO => 2; # Two use constant FALSE => 0; # Boolean FALSE use constant TRUE => 1; # Boolean TRUE #--------------------------------------------------------------------- # program parameters #--------------------------------------------------------------------- my $BLOCK_SIZE = 2048; # Block size [in bytes] # Internal-error message prefix my $IE = 'Internal error'; my $REVISION = '250304'; # Revision string #--------------------------------------------------------------------- # global variable[s] #--------------------------------------------------------------------- my $ISO_File; # Output -file [path]name my $ISO_Temp; # Temporary -file [path]name my $ProgName; # Single-word program name #--------------------------------------------------------------------- # support routines #--------------------------------------------------------------------- # HandleSIGINT is a signal handler. It's triggered by Control-C and/or # SIGTERM interrupts. sub HandleSIGINT { print STDERR "Error: Received SIGINT or SIGTERM - Aborting\n"; unlink $ISO_File if defined $ISO_File; unlink $ISO_Temp if defined $ISO_Temp; exit ONE; } #--------------------------------------------------------------------- # Future change: Document this routine. sub CheckUsage_device_2iso { my ($device, $ofname) = @_; if (!defined ($device) || ($device !~ m@^/dev/@) || (!-b $device) || !defined ($ofname) || ($ofname !~ m@\w\.iso\z@i) || (-l $ofname) || ((-e $ofname) && (!-f $ofname))) { print STDERR << "END"; # "END" must be double-quoted here $ProgName rev. $REVISION - Copies a data CD or DVD to an ISO file Usage: $ProgName /dev/foo bar.iso Replace "/dev/foo" with the device name for a CD or DVD device. The loaded disk may be an ISO data disk, a LiveCD or LiveDVD, or a single- layer or dual-layer video DVD. Audio CDs and raw-data disks aren't supported. To get a list of CD and DVD device names, run "cdlist". Replace "bar.iso" with the desired output-file name [or pathname]. This string should end with ".iso". Note: Make sure that the associ- ated filesystem has plenty of free disk space. The output file can't be a symbolic link. Additionally, if it already exists, it must be a regular file. END exit ONE; } } #--------------------------------------------------------------------- # Future change: Document this routine. sub CheckUsage_device_md5 { my ($device) = @_; if (!defined ($device) || ($device !~ m@^/dev/@) || (!-b $device)) { print STDERR << "END"; # "END" must be double-quoted here $ProgName $REVISION - Computes MD5 value for CD or DVD ISO data Usage: $ProgName /dev/foo Replace "/dev/foo" with the device name for a CD or DVD device. To get a list of CD and DVD device names, run "cdlist". This program should print an MD5 value for the ISO data [if any] stor- ed on the specified device. If you use "cdtoiso" or "ocdvdtoiso" to extract the ISO data, the reported MD5 value should match whatever "md5sum -b" prints for the extracted data. If you use alternate "cat" or "dd" commands to extract ISO data, re- ported MD5 values may not match "md5sum -b" output. Reason: The alter- nate commands may extract trailing "filler" bytes that aren't part of the ISO filesystem. END exit ONE; } } #--------------------------------------------------------------------- # Future change: Document this routine. sub CheckUsage_device_sha1 { my ($device) = @_; if (!defined ($device) || ($device !~ m@^/dev/@) || (!-b $device)) { print STDERR << "END"; # "END" must be double-quoted here $ProgName $REVISION - Computes SHA1 value for CD or DVD ISO data Usage: $ProgName /dev/foo Replace "/dev/foo" with the device name for a CD or DVD device. To get a list of CD and DVD device names, run "cdlist". This program should print a SHA1 value for the ISO data [if any] stor- ed on the specified device. If you use "cdtoiso" or "ocdvdtoiso" to extract the ISO data, the reported SHA1 value should match whatever "sha1sum -b" prints for the extracted data. If you use alternate "cat" or "dd" commands to extract ISO data, re- ported SHA1 values may not match "sha1sum -b" output. Reason: The al- ternate commands may extract trailing "filler" bytes that aren't part of the ISO filesystem. END exit ONE; } } #--------------------------------------------------------------------- # main routine #--------------------------------------------------------------------- sub Main { my $n; # Scratch (integer) my $str; # Scratch (string ) my $device; # CD or DVD device node my $dd_output; # "dd" output my $TargetDir; # Target directory my $ofname; # Data output -file name [or undef] my $tmpfile; # Data temporary -file name [or undef] my $sumtmp; # Checksum-output temporary-file name # [or undef] #--------------------------------------------------------------------- # Initial setup. select STDERR; $| = ONE; # Set flush-on-write mode for STDERR select STDOUT; $| = ONE; # Set flush-on-write mode for STDOUT # Install a signal handler $SIG {INT} = $SIG {TERM} = 'HandleSIGINT'; $str = $0; $ProgName = 'cdtoiso' if $str =~ m@\bcdtoiso\z@; $ProgName = 'ocdvdtoiso' if $str =~ m@\bocdvdtoiso\z@; $ProgName = 'cdmd5' if $str =~ m@\bcdmd5\z@; $ProgName = 'dvdmd5' if $str =~ m@\bdvdmd5\z@; $ProgName = 'cdsha1' if $str =~ m@\bcdsha1\z@; $ProgName = 'dvdsha1' if $str =~ m@\bdvdsha1\z@; if (!defined ($ProgName)) { print STDERR << 'END'; Error: This program must be executed using one of the following sym- bolic links: cdtoiso, ocdvdtoiso, cdmd5, dvdmd5, cdsha1, dvdsha1 END exit ONE; } $IE =~ s@^@$ProgName: @; #--------------------------------------------------------------------- # Process the command line. $device = shift (@ARGV); if ($ProgName =~ m@iso\z@) { $ofname = shift (@ARGV); &CheckUsage_device_2iso ($device, $ofname); } elsif ($ProgName =~ m@md5\z@) { &CheckUsage_device_md5 ($device); } elsif ($ProgName =~ m@sha1\z@) { &CheckUsage_device_sha1 ($device); } else { die "$IE #0001\n"; } $ISO_File = $ofname; # Copy ISO-file name [or undef] to a # global variable #--------------------------------------------------------------------- # Video-related setup. # This code runs "lsdvd" against the specified drive. This accomplish- # es two things: # # a. "lsdvd" tells us whether or the loaded disk is a video DVD. # This code sets the flag $IsVideoDVD accordingly. # # b. If the specified drive contains a video DVD, "lsdvd" forces # a low-level "reset". The "reset" is required. If this opera- # tion is skipped, block-level accesses to video DVDs may # fail. $str = `lsdvd $device 2>&1`; $str = "" unless defined ($str); $str =~ s@\s+\z@@s; my $IsVideoDVD = ($str =~ m@(|\n)Title: \d@) ? TRUE : FALSE; #--------------------------------------------------------------------- # Determine size of input. my $ISO_Size; $ISO_Size = `isosize $device 2>&1`; $ISO_Size = "" unless defined ($ISO_Size); $ISO_Size =~ s@\s+\z@@s; die "$IE #0002\n" if $ISO_Size !~ m@^\d+\z@; my $NumBlocks; $NumBlocks = int ($ISO_Size / $BLOCK_SIZE); $n = $BLOCK_SIZE * $NumBlocks; die "$IE #0003\n" if $ISO_Size != $n; #--------------------------------------------------------------------- # Identify the target directory. if (defined ($ofname)) { $str = $ofname; $str =~ s@(^|/)[^/]+\z@$1@; $str =~ s@(.)/\z@$1@; $str = '.' if !length $str; $TargetDir = $str; if ((!-d $TargetDir) || (!-r $TargetDir) || (!-w $TargetDir) || (!-x $TargetDir)) { print STDERR << "END"; $ProgName: Error: The following target directory is missing or inaccessible: $TargetDir END exit ONE; } } #--------------------------------------------------------------------- # Check disk space on target filesystem. if (defined ($ofname)) { $str = `df --block-size=$BLOCK_SIZE $TargetDir 2>&1`; $str = "" unless defined $str; die "$IE #0004\n" unless $str =~ s@^Filesystem[^\012]+\n@@; $str =~ y/\011\040/ /s; die "$IE #0005\n" unless $str =~ m@^\S+ \d+ \d+ (\d+) @; $n = $1; # Note: The subtraction used here adds a requirement for some extra # disk space [to be safe]. if ($NumBlocks >= ($n - 10)) { print STDERR << "END"; $ProgName: Error: Disk space is too low in $TargetDir END exit ONE; } } #--------------------------------------------------------------------- # Set up files. if (defined ($ofname)) { $ISO_Temp = "$ofname-$>-$$.tmp"; print "FYI: Temporary file is $ISO_Temp\n"; for my $file ($ISO_Temp, $ofname) { unlink $file; if ((-l $file) || (-e $file) || !open (OFD, ">$file") || !close (OFD) || !unlink ($file)) { print STDERR << "END"; $ProgName: Error: Insufficient privileges or other problem Either you've got insufficient privileges in the following directory or there's a similar problem: $TargetDir END exit ONE; } } } #--------------------------------------------------------------------- # Run an appropriate "dd" command. if (defined ($ofname)) { $str = << "END"; dd if=$device bs=$BLOCK_SIZE count=$NumBlocks of=$ISO_Temp END if ($IsVideoDVD) # Transferring video-ISO data? { # Yes - Make a required change $str =~ s@^dd @dd conv=noerror,sync @; } } elsif ($ProgName =~ m@md5\z@) { $sumtmp = "/tmp/md5sum-$>-$$.tmp"; $str = << "END"; dd if=$device bs=$BLOCK_SIZE count=$NumBlocks | (md5sum -b > $sumtmp) END } elsif ($ProgName =~ m@sha1\z@) { $sumtmp = "/tmp/sha1sum-$>-$$.tmp"; $str = << "END"; dd if=$device bs=$BLOCK_SIZE count=$NumBlocks | (sha1sum -b > $sumtmp) END } else { die "$IE #0006\n"; } $str =~ s@\s*\n\s*@ @gs; $str =~ s@\s+\z@@s; print "FYI: Running $str\n" if defined ($ofname); $dd_output = `($str) 2>&1`; $dd_output = '???' unless defined $dd_output; $dd_output =~ s@\s+\z@@s; #--------------------------------------------------------------------- # Check "dd" output. $str = $dd_output; $str =~ s@\n\d+[^\012]+copied[^\012]*\n?@\n@; if (($str !~ s@$NumBlocks\+0 records in\n@@) || ($str !~ s@$NumBlocks\+0 records out\n@@) || ($str =~ m@\S@)) { print STDERR << "END"; $ProgName: Error: Operation failed. "dd", "md5sum", and/or "sha1sum" produced the following output: $dd_output END unlink $ISO_Temp if defined ($ISO_Temp); exit ONE; } #--------------------------------------------------------------------- # Finish data-copy operation [if applicable]. if (defined ($ofname)) { if (!rename ($ISO_Temp, $ofname)) { print STDERR << "END"; $ProgName: Error: Operation failed. "dd" created a temporary file, but the final "rename" operation failed. The reason given was $! END unlink $ISO_Temp; unlink $ofname; exit ONE; } # Print a status message print "\nCreated $ofname\n"; } #--------------------------------------------------------------------- # Finish "md5sum" or "sha1sum" operation [if applicable]. if ($ProgName =~ m@(md5|sha1)\z@) { die "$IE #0007\n" unless defined $sumtmp; die "$IE #0008\n" unless -T $sumtmp; die "$IE #0009\n" unless open (IFD, "<$sumtmp"); undef $/; $str = ; $str = "" unless defined $str; close IFD; unlink $sumtmp; $str =~ s@\s+\z@@s; if (($ProgName =~ m@md5\z@) && ($str =~ m@^[0-9a-f]{32} \*-\z@)) { # O.K. } elsif (($ProgName =~ m@sha1\z@) && ($str =~ m@^[0-9a-f]{40} \*-\z@)) { # O.K. } else { # Internal error die "$IE #0010\n"; } print "$str\n"; } undef; } #--------------------------------------------------------------------- # main program #--------------------------------------------------------------------- &Main(); # Call the main routine exit ZERO; # Normal exit