#!/usr/bin/env ruby # $Id: iso9660.rb,v 1.13 2008/05/02 13:05:40 karl Exp $ # # Copyright (C) 2006, 2008, 2008 Rocky Bernstein # # 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 3 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, see . # Author:: Rocky Bernstein (mailto:rocky@gnu.org) # # = iso9660 # Module for ISO 9660 handling # == Version # :include:VERSION # # ==SYNOPSIS # # This encapsulates IS9660 filesystem handling. This library however # needs to be used in conjunction with classes Device # ISO9660::IFS and ISO9660::FS. # # require "iso9660" # name = ISO9660::name_translate('COPYING.;1') # bool = ISO9660::is_achar('A') # # == DESCRIPTION # # This is an Ruby interface to the GNU CD Input and # Control library's ISO 9660 library, libiso9660. # # Encapsulation is done in two parts. The lower-level Ruby interface is # called Rubyiso9660 and is generated by SWIG. # # The more object-oriented package ISO9660 and uses # Rubyiso9660. # # Although Rubyiso9660 is perfectly usable on its own, it is expected # that these module and classes are what most people will use. As # Rubyiso9660 more closely models the C interface, it is conceivable (if # unlikely) that die-hard libiso9660 C users who are very familiar with # that interface could prefer that. require "cdio" require "rubyiso9660" # General device or driver exceptions class DeviceException < Exception end class ISO9660 # = ISO 9660 Filesystem image reading # == SYNOPSIS # # This encapsulates ISO 9660 filesystem Image handling. The class is # often used in conjunction with ISO9660. # # require "cdio" # require "iso9660" # # iso = ISO9660::IFS::new('copying.iso') # id = iso.get_application_id() # file_stats = iso.readdir($path) # for stat in file_stats # filename = stat["filename"] # lsn = stat["lsn"] # size = stat["size"] # sec_size = stat["secsize"] # is_dir = stat["type"] == 2 ? 'd' : '-' # puts "%s [LSN %6d] %8d %s%s" % [is_dir, lsn, size, path, # name_translate(filename)] # end # # == DESCRIPTION # # This is an Ruby interface to the GNU CD Input and Control library # (libcdio) which is written in C. This class handles ISO 9660 # aspects of an ISO 9600 image. An ISO 9660 image is distinct from a # CD or a CD iamge in that the latter contains other CD-like # information (e.g. tracks, information or assocated with the # CD). See also ISO9660::FS for working with a CD or CD image. class IFS # Create a new ISO 9660 object. If source is given, open() # is called using that and the optional iso_mask parameter; # iso_mask is used only if source is specified. If source is # given but opening fails, nil is returned. If source is not # given, an object is always returned. def initialize(source=nil, iso_mask=Rubyiso9660::EXTENSION_NONE) @iso9660 = nil if source open(source, iso_mask) end end # Returns: bool # # Close previously opened ISO 9660 image and free resources # associated with ISO9660. Call this when done using using # an ISO 9660 image. def close() if @iso9660 Rubyiso9660::close(@iso9660) else puts "***No object to close" end @iso9660 = nil end # Returns: [stat_href] # # Find the filesystem entry that contains LSN and return # file stat information about it. nil is returned on # error. def find_lsn(lsn) if Rubycdio::VERSION_NUM <= 76 puts "*** Routine available only in libcdio versions >= 0.76" return nil end return Rubyiso9660::ifs_find_lsn(@iso9660, lsn) end # Returns: String (id) # # Get the application ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def application_id() return Rubyiso9660::ifs_get_application_id(@iso9660) end # Returns: String (id) # # Get the preparer ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def preparer_id() return Rubyiso9660::ifs_get_preparer_id(@iso9660) end # Returns: String (id) # # Get the publisher ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def publisher_id() return Rubyiso9660::ifs_get_publisher_id(@iso9660) end # Returns: Fixnum (lsn) # # Get the Root LSN stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def root_lsn() return Rubyiso9660::ifs_get_root_lsn(@iso9660) end # Returns: String (id) # # Get the Volume ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. # def system_id() return Rubyiso9660::ifs_get_system_id(@iso9660) end # Returns; String (id) # # Get the Volume ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def volume_id() return Rubyiso9660::ifs_get_volume_id(@iso9660) end # Returns: String (id) # # Get the Volume ID stored in the Primary Volume Descriptor. # nil is returned if there is some problem. def volumeset_id() return Rubyiso9660::ifs_get_volumeset_id(@iso9660) end # Returns: bool # # Return true if we have an ISO9660 image open. # def open?() return @iso9660 != nil end # Open an ISO 9660 image for reading. Subsequent operations # will read from this ISO 9660 image. # # This should be called before using any other routine # except possibly new. It is implicitly called when a new is # done specifying a source. # # If device object was previously opened it is closed first. # # See also open_fuzzy. def open(source, iso_mask=Rubyiso9660::EXTENSION_NONE) if @iso9660 != nil then close() end @iso9660 = Rubyiso9660::open_ext(source, iso_mask) return @iso9660 != nil end # Open an ISO 9660 image for reading. Subsequent operations # will read from this ISO 9660 image. Some tolerence allowed # for positioning the ISO9660 image. We scan for # Rubyiso9660::STANDARD_ID and use that to set the eventual # offset to adjust by (as long as that is <= fuzz). # # This should be called before using any other routine # except possibly new (which must be called first. It is # implicitly called when a new is done specifying a source. # # See also open. def open_fuzzy(source, iso_mask=Rubyiso9660::EXTENSION_NONE, fuzz=20) if @iso9660 != nil then close() end if fuzz.class != Fixnum puts "*** Expecting fuzz to be an integer; got 'fuzz'" return false end @iso9660 = Rubyiso9660::open_fuzzy_ext(source, iso_mask, fuzz) return @iso9660 end # Read the Super block of an ISO 9660 image but determine # framesize and datastart and a possible additional # offset. Generally here we are not reading an ISO 9660 image # but a CD-Image which contains an ISO 9660 filesystem. def read_fuzzy_superblock(iso_mask=Rubyiso9660::EXTENSION_NONE, fuzz=20) if fuzz.class != Fixnum puts "*** Expecting fuzz to be an integer; got 'fuzz'" return false end return Rubyiso9660::ifs_fuzzy_read_superblock(@iso9660, iso_mask, fuzz) end # Read path (a directory) and return a list of iso9660 stat # references # # Each item of @iso_stat is a hash which contains # # * lsn - the Logical sector number (an integer) # * size - the total size of the file in bytes # * secsize - the number of sectors allocated # * filename - the file name of the statbuf entry def readdir(dirname) #--- # FIXME: If you look at iso9660.h you'll see more fields, # such as for Rock-Ridge specific fields or XA specific # fields. Eventually these will be added. Volunteers? #+++ return Rubyiso9660::ifs_readdir(@iso9660, dirname) end # Returns: pvd # # Read the Super block of an ISO 9660 image. This is the # Primary Volume Descriptor (PVD) and perhaps a Supplemental # Volume Descriptor if (Joliet) extensions are # acceptable. def read_pvd() return Rubyiso9660::ifs_read_pvd(@iso9660) end # Returns: bool # # Read the Super block of an ISO 9660 image. This is the # Primary Volume Descriptor (PVD) and perhaps a Supplemental # Volume Descriptor if (Joliet) extensions are # acceptable. def read_superblock(iso_mask=Rubyiso9660::EXTENSION_NONE) return Rubyiso9660::ifs_read_superblock(@iso9660, iso_mask) end # Returns; [size, str] # # Seek to a position and then read n blocks. A block is # Rubycdio::ISO_BLOCKSIZE (2048) bytes. The Size in BYTES (not blocks) # is returned. def seek_read(start, size=1) size *= Rubyiso9660::ISO_BLOCKSIZE return Rubyiso9660::seek_read(@iso9660, start, size) end # Returns: {stat} # # Return file status for path name psz_path. nil is returned on # error. If translate is true, version numbers in the ISO 9660 # name are dropped, i.e. ;1 is removed and if level 1 ISO-9660 # names are lowercased. # # Each item of the return is a hash reference which contains: # # * lsn - the Logical sector number (an integer) # * size - the total size of the file in bytes # * sec_size - the number of sectors allocated # * filename - the file name of the statbuf entry def stat(path, translate=false) if translate values = Rubyiso9660::ifs_stat_translate(@iso9660, path) else values = Rubyiso9660::ifs_stat(@iso9660, path) end return values end end # IFS # = ISO 9660 Filesystem reading # == SYNOPSIS # # This encapsulates ISO-9660 Filesystem aspects of CD Tracks. # As such this is a This library # however needs to be used in conjunction with ISO9660. # # require "iso9660" # cd = ISO9660::FS::new('/dev/cdrom') # statbuf = cd.stat("filename") # # blocks = (statbuf['size'].to_f / Rubycdio::ISO_BLOCKSIZE).ceil() # for i in 0.. block - 1 # lsn = statbuf['lsn'] + i # size, buf = cd.read_data_blocks(lsn) # puts buf # end # # == DESCRIPTION # # This is an Object-Oriented interface to the GNU CD Input and Control # library (libcdio) which is written in C. This class handles ISO # 9660 aspects of a tracks from a CD in a CD-ROM or as a track of a CD # image. A CD image is distinct from an ISO 9660 image in that a CD # image contains other CD-line information (e.g. tracks, information or # assocated with the CD). See also ISO9660::IFS for working with an # ISO 9660 image. class FS < Device # find_lsn(lsn)->[stat_href] # # Find the filesystem entry that contains LSN and return # file stat information about it. nil is returned on # error. def find_lsn(lsn) return Rubyiso9660::fs_find_lsn(@cd, lsn) end # Read path (a directory) and return a list of iso9660 stat # references # # Each item of a hash which contains # # * lsn - the Logical sector number (an integer) # * size - the total size of the file in bytes # * sec_size - the number of sectors allocated # * filename - the file name of the statbuf entry # * is_dir - 2 if a directory; 0 if a not; # # FIXME: If you look at iso9660.h you'll see more fields, such as for # Rock-Ridge specific fields or XA specific fields. Eventually these # will be added. Volunteers? def readdir(dirname) return Rubyiso9660::fs_readdir(@cd, dirname) end # Returns: pvd # # Read the Super block of an ISO 9660 image. This is the # Primary Volume Descriptor (PVD) and perhaps a Supplemental # Volume Descriptor if (Joliet) extensions are # acceptable. def read_pvd() return Rubyiso9660::fs_read_pvd(@cd) end # read_pvd # read_superblock(iso_mask=Rubyiso9660::EXTENSION_NONE)->bool # # Read the Super block of an ISO 9660 image. This is the # Primary Volume Descriptor (PVD) and perhaps a Supplemental # Volume Descriptor if (Joliet) extensions are # acceptable. def read_superblock(iso_mask=Rubyiso9660::EXTENSION_NONE) return Rubyiso9660::fs_read_superblock(@cd, iso_mask) end # read_superblock # Returns: {stat} # # Return file status for path name psz_path. nil is returned on # error. If translate is true, version numbers in the ISO 9660 # name are dropped, i.e. ;1 is removed and if level 1 ISO-9660 # names are lowercased. # # Each item of the return is a hash reference which contains: # # * lsn - the Logical sector number (an integer) # * size - the total size of the file in bytes # * secsize - the number of sectors allocated # * filename - the file name of the statbuf entry # * is_dir - true if a directory; false if a not. def stat(path, translate=false) if translate value = Rubyiso9660::fs_stat_translate(@cd, path) else value = Rubyiso9660::fs_stat(@cd, path) end return value end # stat end # class FS end # class ISO9660 def ISO9660.check_types() return { :nocheck => Rubyiso9660::NOCHECK, :"7bit" => Rubyiso9660::SEVEN_BIT, :achars => Rubyiso9660::ACHARS, :dchars => Rubyiso9660::DCHARS } end # Returns: bool # # Check that path is a valid ISO-9660 directory name. # # A valid directory name should not start out with a slash (/), # dot (.) or null byte, should be less than 37 characters long, # have no more than 8 characters in a directory component # which is separated by a /, and consist of only DCHARs. # # true is returned if path is valid. def ISO9660.dirname_valid?(path) return Rubyiso9660::dirname_valid?(path) end # Returns: bool # # Return 1 if achar is an ISO-9660 ACHAR. achar should either be a string of # length one or the ord() of a string of length 1. # # These are the DCHAR's plus some ASCII symbols including the space # symbol. def ISO9660.achar?(achar) if achar.class == Fixnum # Is an integer. Is it too large? if achar > 255 then return false end elsif achar.class == String and achar.length() == 1 achar = achar[0] else return false end return Rubyiso9660::achar?(achar) end # Returns: bool # # Return 1 if dchar is a ISO-9660 DCHAR - a character that can appear in an an # ISO-9600 level 1 directory name. These are the ASCII capital # letters A-Z, the digits 0-9 and an underscore. # # dchar should either be a string of length one or the ord() of a string # of length 1. def ISO9660.dchar?(dchar) if dchar.class == Fixnum # Is an integer. Is it too large? if dchar > 255 then return false end # Not integer. Should be a string of length one then. # We'll turn it into an integer. elsif dchar = dchar[0] else return false end return Rubyiso9660::dchar?(dchar) end # Returns: bool # # Check that path is a valid ISO-9660 pathname. # # A valid pathname contains a valid directory name, if one appears and # the filename portion should be no more than 8 characters for the # file prefix and 3 characters in the extension (or portion after a # dot). There should be exactly one dot somewhere in the filename # portion and the filename should be composed of only DCHARs. # # true is returned if path is valid. def ISO9660.pathname_valid?(path) return Rubyiso9660::pathname_valid?(path) end # Take path and a version number and turn that into a ISO-9660 # pathname. (That's just the pathname followed by ';' and the version # number. For example, mydir/file.ext -> MYDIR/FILE.EXT;1 for version 1. # The resulting ISO-9660 pathname is returned. def ISO9660.pathname_isofy(path, version=1) return Rubyiso9660::pathname_isofy(path, version) end # Returns: String # # Convert an ISO-9660 file name of the kind that is that stored in a ISO # 9660 directory entry into what's usually listed as the file name in a # listing. Lowercase name if no Joliet Extension interpretation. Remove # trailing ;1's or .;1's and turn the other ;'s into version numbers. # # If joliet_level is not given it is 0 which means use no Joliet # Extensions. Otherwise use the specified the Joliet level. # # The translated string is returned and it will be larger than the input # filename. def ISO9660.name_translate(filename, joliet_level=0) return Rubyiso9660::name_translate_ext(filename, joliet_level) end #--- # FIXME: should be # def stat_array_to_dict(*args): # Probably have a SWIG error. #+++ # Returns: String # # Pad string 'name' with spaces to size len and return this. If 'len' is # less than the length of 'src', the return value will be truncated to # the first len characters of 'name'. # # 'name' can also be scanned to see if it contains only ACHARs, DCHARs, # or 7-bit ASCII chars, and this is specified via the 'check' parameter. # If the I parameter is given it must be one of the 'nocheck', # '7bit', 'achars' or 'dchars'. Case is not significant. def ISO9660.strncpy_pad(name, len, check) if not ISO9660.check_types().member?(check) puts "*** A CHECK parameter must be one of %s\n" % ISO9660.check_types.keys().join(',') return nil end return Rubyiso9660::strncpy_pad(name, len, ISO9660.check_types()[check]) end