From 8eb0cc1ed1af402450e5b7140000b5e4a0b67bc1 Mon Sep 17 00:00:00 2001 From: Avi Deitcher Date: Wed, 22 May 2024 15:58:32 +0300 Subject: [PATCH] iso9660 finalizeFileInfo contain enough info for rockridge extensions Signed-off-by: Avi Deitcher --- .../directoryentrysystemuseextension.go | 2 +- filesystem/iso9660/finalize.go | 217 +++++++++++++----- filesystem/iso9660/iso9660.go | 56 ++--- filesystem/iso9660/rockridge.go | 37 ++- filesystem/iso9660/rockridge_internal_test.go | 100 ++++---- 5 files changed, 277 insertions(+), 135 deletions(-) diff --git a/filesystem/iso9660/directoryentrysystemuseextension.go b/filesystem/iso9660/directoryentrysystemuseextension.go index 1d10ec54..8bef9f6c 100644 --- a/filesystem/iso9660/directoryentrysystemuseextension.go +++ b/filesystem/iso9660/directoryentrysystemuseextension.go @@ -37,7 +37,7 @@ type suspExtension interface { Descriptor() string Source() string Version() uint8 - GetFileExtensions(string, bool, bool) ([]directoryEntrySystemUseExtension, error) + GetFileExtensions(*finalizeFileInfo, bool, bool) ([]directoryEntrySystemUseExtension, error) GetFinalizeExtensions(*finalizeFileInfo) ([]directoryEntrySystemUseExtension, error) Relocatable() bool Relocate(map[string]*finalizeFileInfo) ([]*finalizeFileInfo, map[string]*finalizeFileInfo, error) diff --git a/filesystem/iso9660/finalize.go b/filesystem/iso9660/finalize.go index f4cff559..2ef3c285 100644 --- a/filesystem/iso9660/finalize.go +++ b/filesystem/iso9660/finalize.go @@ -3,6 +3,7 @@ package iso9660 import ( "fmt" "io" + "io/fs" "os" "path" "path/filepath" @@ -12,11 +13,13 @@ import ( "time" "github.com/diskfs/go-diskfs/util" + "github.com/djherbis/times" ) const ( dataStartSector = 16 defaultVolumeIdentifier = "ISOIMAGE" + elToritoBootTableOffset = 8 ) // FinalizeOptions options to pass to finalize @@ -41,6 +44,14 @@ type FinalizeOptions struct { // IsDir() bool // abbreviation for Mode().IsDir() // Sys() interface{} // underlying data source (can return nil) // +// Also supports: +// +// AccessTime() time.Time +// ChangeTime() time.Time +// Nlink() uint32 // number of hardlinks, if supported +// Uid() uint32 // uid, if supported +// Gid() uint32 // gid, if supported +// //nolint:structcheck // keep unused members so that we can know their references type finalizeFileInfo struct { path string @@ -56,6 +67,8 @@ type finalizeFileInfo struct { size int64 mode os.FileMode modTime time.Time + accessTime time.Time + changeTime time.Time isDir bool isRoot bool bytes [][]byte @@ -64,7 +77,54 @@ type finalizeFileInfo struct { trueParent *finalizeFileInfo trueChild *finalizeFileInfo elToritoEntry *ElToritoEntry - content []byte + linkTarget string + uid uint32 + gid uint32 + nlink uint32 + // content in memory content of file. If this is anything other than nil, including a zero-length slice, + // then this content is used, rather than anything on disk. + content []byte +} + +func finalizeFileInfoFromFile(p, fullPath string, fi fs.FileInfo) (*finalizeFileInfo, error) { + isRoot := p == "." + name := fi.Name() + shortname, _ := calculateShortnameExtension(name) + + if isRoot { + name = string([]byte{0x00}) + shortname = name + } + t, err := times.Lstat(fullPath) + if err != nil { + return nil, fmt.Errorf("could not get times information for %s: %w", fullPath, err) + } + mode := fi.Mode() + var target string + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + target, err = os.Readlink(fullPath) + if err != nil { + return nil, fmt.Errorf("unable to read link for %s: %w", fullPath, err) + } + } + nlink, uid, gid := statt(fi) + + return &finalizeFileInfo{ + path: p, + name: name, + isDir: fi.IsDir(), + isRoot: isRoot, + modTime: fi.ModTime(), + accessTime: t.AccessTime(), + changeTime: t.ChangeTime(), + mode: mode, + size: fi.Size(), + shortname: shortname, + linkTarget: target, + uid: uid, + gid: gid, + nlink: nlink, + }, nil } func (fi *finalizeFileInfo) Name() string { @@ -100,8 +160,26 @@ func (fi *finalizeFileInfo) updateDepth(depth int) { } } } +func (fi *finalizeFileInfo) AccessTime() time.Time { + return fi.accessTime +} +func (fi *finalizeFileInfo) ChangeTime() time.Time { + return fi.changeTime +} +func (fi *finalizeFileInfo) LinkTarget() string { + return fi.linkTarget +} +func (fi *finalizeFileInfo) Nlink() uint32 { + return fi.nlink +} +func (fi *finalizeFileInfo) UID() uint32 { + return fi.uid +} +func (fi *finalizeFileInfo) GID() uint32 { + return fi.gid +} -func (fi *finalizeFileInfo) toDirectoryEntry(fs *FileSystem, isSelf, isParent bool) (*directoryEntry, error) { +func (fi *finalizeFileInfo) toDirectoryEntry(fsm *FileSystem, isSelf, isParent bool) (*directoryEntry, error) { de := &directoryEntry{ extAttrSize: 0, location: fi.location, @@ -116,18 +194,22 @@ func (fi *finalizeFileInfo) toDirectoryEntry(fs *FileSystem, isSelf, isParent bo isSelf: isSelf, isParent: isParent, volumeSequence: 1, - filesystem: fs, + filesystem: fsm, // we keep the full filename until after processing filename: fi.Name(), } // if it is root, and we have susp enabled, add the necessary entries - if fs.suspEnabled { + if fsm.suspEnabled { if fi.isRoot && isSelf { de.extensions = append(de.extensions, directoryEntrySystemUseExtensionSharingProtocolIndicator{skipBytes: 0}) } // add appropriate PX, TF, SL, NM extensions - for _, e := range fs.suspExtensions { - ext, err := e.GetFileExtensions(path.Join(fs.workspace, fi.path), isSelf, isParent) + for _, e := range fsm.suspExtensions { + var ( + ext []directoryEntrySystemUseExtension + err error + ) + ext, err = e.GetFileExtensions(fi, isSelf, isParent) if err != nil { return nil, fmt.Errorf("error getting extensions for %s at path %s: %v", e.ID(), fi.path, err) } @@ -140,14 +222,14 @@ func (fi *finalizeFileInfo) toDirectoryEntry(fs *FileSystem, isSelf, isParent bo } if fi.isRoot && isSelf { - for _, e := range fs.suspExtensions { + for _, e := range fsm.suspExtensions { de.extensions = append(de.extensions, directoryEntrySystemUseExtensionReference{id: e.ID(), descriptor: e.Descriptor(), source: e.Source(), extensionVersion: e.Version()}) } } } return de, nil } -func (fi *finalizeFileInfo) toDirectory(fs *FileSystem) (*Directory, error) { +func (fi *finalizeFileInfo) toDirectory(fsm *FileSystem) (*Directory, error) { // also need to add self and parent to it var ( self, parent, dirEntry *directoryEntry @@ -156,7 +238,7 @@ func (fi *finalizeFileInfo) toDirectory(fs *FileSystem) (*Directory, error) { if !fi.IsDir() { return nil, fmt.Errorf("cannot convert a file entry to a directtory") } - self, err = fi.toDirectoryEntry(fs, true, false) + self, err = fi.toDirectoryEntry(fsm, true, false) if err != nil { return nil, fmt.Errorf("could not convert self entry %s to dirEntry: %v", fi.path, err) } @@ -167,14 +249,14 @@ func (fi *finalizeFileInfo) toDirectory(fs *FileSystem) (*Directory, error) { if fi.isRoot { parentEntry = fi } - parent, err = parentEntry.toDirectoryEntry(fs, false, true) + parent, err = parentEntry.toDirectoryEntry(fsm, false, true) if err != nil { return nil, fmt.Errorf("could not convert parent entry %s to dirEntry: %v", fi.parent.path, err) } entries := []*directoryEntry{self, parent} for _, child := range fi.children { - dirEntry, err = child.toDirectoryEntry(fs, false, false) + dirEntry, err = child.toDirectoryEntry(fsm, false, false) if err != nil { return nil, fmt.Errorf("could not convert child entry %s to dirEntry: %v", child.path, err) } @@ -188,10 +270,10 @@ func (fi *finalizeFileInfo) toDirectory(fs *FileSystem) (*Directory, error) { } // calculate the size of a directory entry single record -func (fi *finalizeFileInfo) calculateRecordSize(fs *FileSystem, isSelf, isParent bool) (dirEntrySize, continuationBlocksSize int, err error) { +func (fi *finalizeFileInfo) calculateRecordSize(fsm *FileSystem, isSelf, isParent bool) (dirEntrySize, continuationBlocksSize int, err error) { // we do not actually need the the continuation blocks to calculate size, just length, so use an empty slice extTmpBlocks := make([]uint32, 100) - dirEntry, err := fi.toDirectoryEntry(fs, isSelf, isParent) + dirEntry, err := fi.toDirectoryEntry(fsm, isSelf, isParent) if err != nil { return 0, 0, fmt.Errorf("could not convert to dirEntry: %v", err) } @@ -205,21 +287,21 @@ func (fi *finalizeFileInfo) calculateRecordSize(fs *FileSystem, isSelf, isParent } // calculate the size of a directory, similar to a file size -func (fi *finalizeFileInfo) calculateDirectorySize(fs *FileSystem) (dirEntrySize, continuationBlocksSize int, err error) { +func (fi *finalizeFileInfo) calculateDirectorySize(fsm *FileSystem) (dirEntrySize, continuationBlocksSize int, err error) { var ( recSize, recCE int ) if !fi.IsDir() { return 0, 0, fmt.Errorf("cannot convert a file entry to a directtory") } - recSize, recCE, err = fi.calculateRecordSize(fs, true, false) + recSize, recCE, err = fi.calculateRecordSize(fsm, true, false) if err != nil { return 0, 0, fmt.Errorf("could not calculate self entry size %s: %v", fi.path, err) } dirEntrySize += recSize continuationBlocksSize += recCE - recSize, recCE, err = fi.calculateRecordSize(fs, false, true) + recSize, recCE, err = fi.calculateRecordSize(fsm, false, true) if err != nil { return 0, 0, fmt.Errorf("could not calculate parent entry size %s: %v", fi.path, err) } @@ -228,13 +310,13 @@ func (fi *finalizeFileInfo) calculateDirectorySize(fs *FileSystem) (dirEntrySize for _, e := range fi.children { // get size of data and CE blocks - recSize, recCE, err = e.calculateRecordSize(fs, false, false) + recSize, recCE, err = e.calculateRecordSize(fsm, false, false) if err != nil { return 0, 0, fmt.Errorf("could not calculate child %s entry size %s: %v", e.path, fi.path, err) } // do not go over a block boundary; pad if necessary newSize := dirEntrySize + recSize - blocksize := int(fs.blocksize) + blocksize := int(fsm.blocksize) left := blocksize - dirEntrySize%blocksize if left != 0 && newSize/blocksize > dirEntrySize/blocksize { dirEntrySize += left @@ -341,15 +423,15 @@ func (fi *finalizeFileInfo) addChild(entry *finalizeFileInfo) { // Finalize finalize a read-only filesystem by writing it out to a read-only format // //nolint:gocyclo // this finalize function is complex and needs to be. We might be better off refactoring it to multiple functions, but it does not buy all that much. -func (fs *FileSystem) Finalize(options FinalizeOptions) error { - if fs.workspace == "" { +func (fsm *FileSystem) Finalize(options FinalizeOptions) error { + if fsm.workspace == "" { return fmt.Errorf("cannot finalize an already finalized filesystem") } // did we ask for susp? if options.RockRidge { - fs.suspEnabled = true - fs.suspExtensions = append(fs.suspExtensions, getRockRidgeExtension(rockRidge112)) + fsm.suspEnabled = true + fsm.suspExtensions = append(fsm.suspExtensions, getRockRidgeExtension(rockRidge112)) } /* @@ -380,11 +462,11 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { 10- write volume descriptor set terminator */ - f := fs.file - blocksize := int(fs.blocksize) + f := fsm.file + blocksize := int(fsm.blocksize) // 1- blank out sectors 0-15 - b := make([]byte, dataStartSector*fs.blocksize) + b := make([]byte, dataStartSector*fsm.blocksize) n, err := f.WriteAt(b, 0) if err != nil { return fmt.Errorf("could not write blank system area: %v", err) @@ -394,7 +476,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { } // 3- build out file tree - fileList, dirList, err := walkTree(fs.Workspace()) + fileList, dirList, err := walkTree(fsm.Workspace()) if err != nil { return fmt.Errorf("error walking tree: %v", err) } @@ -406,9 +488,9 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { // if we need to relocate directories, must do them here, before finalizing order and sizes // do not bother if enabled DeepDirectories, i.e. non-ISO9660 compliant if !options.DeepDirectories { - if fs.suspEnabled { + if fsm.suspEnabled { var handler suspExtension - for _, e := range fs.suspExtensions { + for _, e := range fsm.suspExtensions { if e.Relocatable() { handler = e break @@ -431,7 +513,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { // convert sizes to required blocks for files for _, e := range fileList { - e.blocks = calculateBlocks(e.size, fs.blocksize) + e.blocks = calculateBlocks(e.size, fsm.blocksize) } // we now have list of all of the files and directories and their properties, as well as children of every directory @@ -467,14 +549,18 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { shortname, extension := calculateShortnameExtension(path.Base(catname)) // break down the catalog basename from the parent dir catSize := int64(len(bootcat)) + now := time.Now() catEntry = &finalizeFileInfo{ - content: bootcat, - size: catSize, - path: catname, - name: path.Base(catname), - shortname: shortname, - extension: extension, - blocks: calculateBlocks(catSize, fs.blocksize), + content: bootcat, + size: catSize, + path: catname, + name: path.Base(catname), + shortname: shortname, + extension: extension, + blocks: calculateBlocks(catSize, fsm.blocksize), + modTime: now, + accessTime: now, + changeTime: now, } // make it the first file files = append([]*finalizeFileInfo{catEntry}, files...) @@ -512,7 +598,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { var size, ceBlocks int for _, dir := range dirs { dir.location = location - size, ceBlocks, err = dir.calculateDirectorySize(fs) + size, ceBlocks, err = dir.calculateDirectorySize(fsm) if err != nil { return fmt.Errorf("unable to calculate size of directory for %s: %v", dir.path, err) } @@ -566,7 +652,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { for _, e := range dirs { writeAt := int64(e.location) * int64(blocksize) var d *Directory - d, err = e.toDirectory(fs) + d, err = e.toDirectory(fsm) if err != nil { return fmt.Errorf("unable to convert entry to directory: %v", err) } @@ -601,13 +687,14 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { }() for _, e := range files { var ( - from *os.File - copied int + from *os.File + copied int + bootTableMinSize int ) writeAt := int64(e.location) * int64(blocksize) if e.content == nil { // for file, just copy the data across - from, err = os.Open(path.Join(fs.workspace, e.path)) + from, err = os.Open(path.Join(fsm.workspace, e.path)) if err != nil { return fmt.Errorf("failed to open file for reading %s: %v", e.path, err) } @@ -617,21 +704,23 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { var count int // first 8 bytes - count, err = copyFileData(from, f, 0, writeAt, 8) + count, err = copyFileData(from, f, 0, writeAt, elToritoBootTableOffset) if err != nil { return fmt.Errorf("failed to copy first bytes 0-8 of boot file to disk %s: %v", e.path, err) } copied += count // insert El Torito Boot Information Table - bootTable, err := e.elToritoEntry.generateBootTable(dataStartSector, path.Join(fs.workspace, e.path)) + bootTable, err := e.elToritoEntry.generateBootTable(dataStartSector, path.Join(fsm.workspace, e.path)) if err != nil { return fmt.Errorf("failed to generate boot table for %s: %v", e.path, err) } - count, err = f.WriteAt(bootTable, writeAt+8) + count, err = f.WriteAt(bootTable, writeAt+elToritoBootTableOffset) if err != nil { return fmt.Errorf("failed to write 56 byte boot table to disk %s: %v", e.path, err) } copied += count + // file with boot table file must be a minimum of boot table size and the offset + bootTableMinSize = count // remainder of file count, err = copyFileData(from, f, 64, writeAt+64, 0) if err != nil { @@ -644,8 +733,12 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { return fmt.Errorf("failed to copy file to disk %s: %v", e.path, err) } } - if copied != int(e.Size()) { - return fmt.Errorf("error copying file %s to disk, copied %d bytes, expected %d", e.path, copied, e.Size()) + targetSize := e.Size() + if targetSize < int64(bootTableMinSize) { + targetSize = int64(bootTableMinSize) + } + if copied != int(targetSize) { + return fmt.Errorf("error copying file %s to disk, copied %d bytes, expected %d", e.path, copied, targetSize) } } else { copied = len(e.content) @@ -665,7 +758,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { location = dataStartSector // create and write the primary volume descriptor, supplementary and boot, and volume descriptor set terminator now := time.Now() - rootDE, err := root.toDirectoryEntry(fs, true, false) + rootDE, err := root.toDirectoryEntry(fsm, true, false) if err != nil { return fmt.Errorf("could not convert root entry for primary volume descriptor to dirEntry: %v", err) } @@ -676,7 +769,7 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { volumeSize: totalSize, setSize: 1, sequenceNumber: 1, - blocksize: uint16(fs.blocksize), + blocksize: uint16(fsm.blocksize), pathTableSize: uint32(pathTableSize), pathTableLLocation: pathTableLLocation, pathTableLOptionalLocation: 0, @@ -710,10 +803,10 @@ func (fs *FileSystem) Finalize(options FinalizeOptions) error { b = terminator.toBytes() _, _ = f.WriteAt(b, int64(location)*int64(blocksize)) - _ = os.RemoveAll(fs.workspace) + _ = os.RemoveAll(fsm.workspace) // finish by setting as finalized - fs.workspace = "" + fsm.workspace = "" return nil } @@ -772,16 +865,16 @@ func sortFinalizeFileInfoPathTable(left, right *finalizeFileInfo) bool { // create a path table from a slice of *finalizeFileInfo that are directories func createPathTable(fi []*finalizeFileInfo) *pathTable { // copy so we do not modify the original - fs := make([]*finalizeFileInfo, len(fi)) - copy(fs, fi) + fis := make([]*finalizeFileInfo, len(fi)) + copy(fis, fi) // sort via the rules - sort.Slice(fs, func(i, j int) bool { - return sortFinalizeFileInfoPathTable(fs[i], fs[j]) + sort.Slice(fis, func(i, j int) bool { + return sortFinalizeFileInfoPathTable(fis[i], fis[j]) }) indexMap := make(map[*finalizeFileInfo]int) // now that it is sorted, create the ordered path table entries entries := make([]*pathTableEntry, 0) - for i, e := range fs { + for i, e := range fis { name := e.Name() nameSize := len(name) size := 8 + uint16(nameSize) @@ -824,15 +917,15 @@ func walkTree(workspace string) ([]*finalizeFileInfo, map[string]*finalizeFileIn if err != nil { return fmt.Errorf("error walking path %s: %v", fp, err) } - isRoot := fp == "." name := fi.Name() - shortname, extension := calculateShortnameExtension(name) + _, extension := calculateShortnameExtension(name) - if isRoot { - name = string([]byte{0x00}) - shortname = name + actualPath := path.Join(workspace, fp) + + entry, err = finalizeFileInfoFromFile(fp, actualPath, fi) + if err != nil { + return err } - entry = &finalizeFileInfo{path: fp, name: name, isDir: fi.IsDir(), isRoot: isRoot, modTime: fi.ModTime(), mode: fi.Mode(), size: fi.Size(), shortname: shortname} // we will have to save it as its parent parentDir := filepath.Dir(fp) @@ -841,7 +934,7 @@ func walkTree(workspace string) ([]*finalizeFileInfo, map[string]*finalizeFileIn if fi.IsDir() { entry.children = make([]*finalizeFileInfo, 0, 20) dirList[fp] = entry - if !isRoot { + if !entry.isRoot { parentDirInfo.children = append(parentDirInfo.children, entry) dirList[parentDir] = parentDirInfo } diff --git a/filesystem/iso9660/iso9660.go b/filesystem/iso9660/iso9660.go index c6c1321c..64b28ed2 100644 --- a/filesystem/iso9660/iso9660.go +++ b/filesystem/iso9660/iso9660.go @@ -34,15 +34,15 @@ type FileSystem struct { } // Equal compare if two filesystems are equal -func (fs *FileSystem) Equal(a *FileSystem) bool { - localMatch := fs.file == a.file && fs.size == a.size - vdMatch := fs.volumes.equal(&a.volumes) +func (fsm *FileSystem) Equal(a *FileSystem) bool { + localMatch := fsm.file == a.file && fsm.size == a.size + vdMatch := fsm.volumes.equal(&a.volumes) return localMatch && vdMatch } // Workspace get the workspace path -func (fs *FileSystem) Workspace() string { - return fs.workspace +func (fsm *FileSystem) Workspace() string { + return fsm.workspace } // Create creates an ISO9660 filesystem in a given directory @@ -283,7 +283,7 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { } // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 -func (fs *FileSystem) Type() filesystem.Type { +func (fsm *FileSystem) Type() filesystem.Type { return filesystem.TypeISO9660 } @@ -293,11 +293,11 @@ func (fs *FileSystem) Type() filesystem.Type { // * It will not return an error if the path already exists // // if readonly and not in workspace, will return an error -func (fs *FileSystem) Mkdir(p string) error { - if fs.workspace == "" { +func (fsm *FileSystem) Mkdir(p string) error { + if fsm.workspace == "" { return fmt.Errorf("cannot write to read-only filesystem") } - err := os.MkdirAll(path.Join(fs.workspace, p), 0o755) + err := os.MkdirAll(path.Join(fsm.workspace, p), 0o755) if err != nil { return fmt.Errorf("could not create directory %s: %v", p, err) } @@ -310,12 +310,12 @@ func (fs *FileSystem) Mkdir(p string) error { // Returns a slice of os.FileInfo with all of the entries in the directory. // // Will return an error if the directory does not exist or is a regular file and not a directory -func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { +func (fsm *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { var fi []os.FileInfo // non-workspace: read from iso9660 // workspace: read from regular filesystem - if fs.workspace != "" { - fullPath := path.Join(fs.workspace, p) + if fsm.workspace != "" { + fullPath := path.Join(fsm.workspace, p) // read the entries dirEntries, err := os.ReadDir(fullPath) if err != nil { @@ -329,7 +329,7 @@ func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { fi = append(fi, info) } } else { - dirEntries, err := fs.readDirectory(p) + dirEntries, err := fsm.readDirectory(p) if err != nil { return nil, fmt.Errorf("error reading directory %s: %v", p, err) } @@ -351,7 +351,7 @@ func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) { // accepts normal os.OpenFile flags // // returns an error if the file does not exist -func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { +func (fsm *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { var f filesystem.File var err error @@ -366,14 +366,14 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { // cannot open to write or append or create if we do not have a workspace writeMode := flag&os.O_WRONLY != 0 || flag&os.O_RDWR != 0 || flag&os.O_APPEND != 0 || flag&os.O_CREATE != 0 || flag&os.O_TRUNC != 0 || flag&os.O_EXCL != 0 - if fs.workspace == "" { + if fsm.workspace == "" { if writeMode { return nil, fmt.Errorf("cannot write to read-only filesystem") } // get the directory entries var entries []*directoryEntry - entries, err = fs.readDirectory(dir) + entries, err = fsm.readDirectory(dir) if err != nil { return nil, fmt.Errorf("could not read directory entries for %s", dir) } @@ -405,7 +405,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { offset: 0, } } else { - f, err = os.OpenFile(path.Join(fs.workspace, p), flag, 0o644) + f, err = os.OpenFile(path.Join(fsm.workspace, p), flag, 0o644) if err != nil { return nil, fmt.Errorf("target file %s does not exist: %v", p, err) } @@ -415,7 +415,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { } // readDirectory - read directory entry on iso only (not workspace) -func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { +func (fsm *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { var ( location, size uint32 err error @@ -424,7 +424,7 @@ func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { // try from path table, then walk the directory tree, unless we were told explicitly not to usePathtable := true - for _, e := range fs.suspExtensions { + for _, e := range fsm.suspExtensions { usePathtable = e.UsePathtable() if !usePathtable { break @@ -432,14 +432,14 @@ func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { } if usePathtable { - location = fs.pathTable.getLocation(p) + location = fsm.pathTable.getLocation(p) } // if we found it, read the first directory entry to get the size if location != 0 { // we need 4 bytes to read the size of the directory; it is at offset 10 from beginning dirb := make([]byte, 4) - n, err = fs.file.ReadAt(dirb, int64(location)*fs.blocksize+10) + n, err = fsm.file.ReadAt(dirb, int64(location)*fsm.blocksize+10) if err != nil { return nil, fmt.Errorf("could not read directory %s: %v", p, err) } @@ -451,7 +451,7 @@ func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { } else { // if we could not find the location in the path table, try reading directly from the disk // it is slow, but this is how Unix does it, since many iso creators *do* create illegitimate disks - location, size, err = fs.rootDir.getLocation(p) + location, size, err = fsm.rootDir.getLocation(p) if err != nil { return nil, fmt.Errorf("unable to read directory tree for %s: %v", p, err) } @@ -464,7 +464,7 @@ func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { // we have a location, let's read the directories from it b := make([]byte, size) - n, err = fs.file.ReadAt(b, int64(location)*fs.blocksize) + n, err = fsm.file.ReadAt(b, int64(location)*fsm.blocksize) if err != nil { return nil, fmt.Errorf("could not read directory entries for %s: %v", p, err) } @@ -472,7 +472,7 @@ func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { return nil, fmt.Errorf("reading directory %s returned %d bytes read instead of expected %d", p, n, size) } // parse the entries - entries, err := parseDirEntries(b, fs) + entries, err := parseDirEntries(b, fsm) if err != nil { return nil, fmt.Errorf("could not parse directory entries for %s: %v", p, err) } @@ -488,13 +488,13 @@ func validateBlocksize(blocksize int64) error { } } -func (fs *FileSystem) Label() string { - if fs.volumes.primary == nil { +func (fsm *FileSystem) Label() string { + if fsm.volumes.primary == nil { return "" } - return fs.volumes.primary.volumeIdentifier + return fsm.volumes.primary.volumeIdentifier } -func (fs *FileSystem) SetLabel(string) error { +func (fsm *FileSystem) SetLabel(string) error { return fmt.Errorf("ISO9660 filesystem is read-only") } diff --git a/filesystem/iso9660/rockridge.go b/filesystem/iso9660/rockridge.go index b2360727..b3cb38d3 100644 --- a/filesystem/iso9660/rockridge.go +++ b/filesystem/iso9660/rockridge.go @@ -96,7 +96,7 @@ func (r *rockRidgeExtension) GetFilename(de *directoryEntry) (string, error) { } return name, nil } -func (r *rockRidgeExtension) GetFileExtensions(fp string, isSelf, isParent bool) ([]directoryEntrySystemUseExtension, error) { +func (r *rockRidgeExtension) GetFileExtensionsOld(fp string, isSelf, isParent bool) ([]directoryEntrySystemUseExtension, error) { // we always do PX, TF, NM, SL order ret := []directoryEntrySystemUseExtension{} // do not follow symlinks @@ -148,6 +148,41 @@ func (r *rockRidgeExtension) GetFileExtensions(fp string, isSelf, isParent bool) return ret, nil } +func (r *rockRidgeExtension) GetFileExtensions(ffi *finalizeFileInfo, isSelf, isParent bool) ([]directoryEntrySystemUseExtension, error) { + // we always do PX, TF, NM, SL order + ret := []directoryEntrySystemUseExtension{} + + // PX + mtime := ffi.ModTime() + + ret = append(ret, rockRidgePosixAttributes{ + mode: ffi.Mode(), + linkCount: ffi.Nlink(), + uid: ffi.UID(), + gid: ffi.GID(), + length: r.pxLength, + }) + // TF + tf := rockRidgeTimestamps{longForm: false, stamps: []rockRidgeTimestamp{ + {timestampType: rockRidgeTimestampModify, time: mtime}, + {timestampType: rockRidgeTimestampAccess, time: ffi.AccessTime()}, + {timestampType: rockRidgeTimestampAttribute, time: ffi.ChangeTime()}, + }} + + ret = append(ret, tf) + // NM + if !isSelf && !isParent { + ret = append(ret, rockRidgeName{name: ffi.name}) + } + // SL + if ffi.Mode()&os.ModeSymlink == os.ModeSymlink { + // need the target if it is a symlink + ret = append(ret, rockRidgeSymlink{continued: false, name: ffi.LinkTarget()}) + } + + return ret, nil +} + func (r *rockRidgeExtension) GetFinalizeExtensions(fi *finalizeFileInfo) ([]directoryEntrySystemUseExtension, error) { // we look for CL, PL, RE entries ret := []directoryEntrySystemUseExtension{} diff --git a/filesystem/iso9660/rockridge_internal_test.go b/filesystem/iso9660/rockridge_internal_test.go index c0475b1d..4934f85d 100644 --- a/filesystem/iso9660/rockridge_internal_test.go +++ b/filesystem/iso9660/rockridge_internal_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/user" + "path" "path/filepath" "sort" "strconv" @@ -148,12 +149,13 @@ func TestGetExtensions(t *testing.T) { gid := uint32(gidI) now := time.Now() - // symlinks have fixed perms based on OS, we will get it and then set it - if err = os.Symlink("testa", "testb"); err != nil { + // symlinks have fixed perms based on OS, so we just create a random symlink somewhere to get the OS-specific perms + linkfile := path.Join(dir, "testb") + if err = os.Symlink("testa", linkfile); err != nil { t.Fatalf("unable to create test symlink %s: %v", "testb", err) } defer os.Remove("testb") - fi, err := os.Lstat("testb") + fi, err := os.Lstat(linkfile) if err != nil { t.Fatalf("unable to ready file info for test symlink: %v", err) } @@ -176,13 +178,14 @@ func TestGetExtensions(t *testing.T) { }, }, rockRidgeName{name: "regular01"}, - }, func(path string) { - if err := os.WriteFile(path, []byte("some data"), 0o600); err != nil { - t.Fatalf("unable to create regular file %s: %v", path, err) + }, func(p string) { + content := []byte("some data") + if err := os.WriteFile(p, content, 0o600); err != nil { + t.Fatalf("unable to create regular file %s: %v", p, err) } // because of umask, must set explicitly - if err := os.Chmod(path, 0o764); err != nil { - t.Fatalf("unable to chmod %s: %v", path, err) + if err := os.Chmod(p, 0o764); err != nil { + t.Fatalf("unable to chmod %s: %v", p, err) } }, }, @@ -196,13 +199,13 @@ func TestGetExtensions(t *testing.T) { }, }, rockRidgeName{name: "directory02"}, - }, func(path string) { - if err := os.Mkdir(path, 0o754); err != nil { - t.Fatalf("unable to create directory %s: %v", path, err) + }, func(p string) { + if err := os.Mkdir(p, 0o754); err != nil { + t.Fatalf("unable to create directory %s: %v", p, err) } // because of umask, must set explicitly - if err := os.Chmod(path, 0o754); err != nil { - t.Fatalf("unable to chmod %s: %v", path, err) + if err := os.Chmod(p, 0o754); err != nil { + t.Fatalf("unable to chmod %s: %v", p, err) } }, }, @@ -217,9 +220,10 @@ func TestGetExtensions(t *testing.T) { }, rockRidgeName{name: "symlink03"}, rockRidgeSymlink{continued: false, name: "/a/b/c/d/efgh"}, - }, func(path string) { - if err := os.Symlink("/a/b/c/d/efgh", path); err != nil { - t.Fatalf("unable to create symlink %s: %v", path, err) + }, func(p string) { + target := "/a/b/c/d/efgh" + if err := os.Symlink(target, p); err != nil { + t.Fatalf("unable to create symlink %s: %v", p, err) } }, }, @@ -232,9 +236,9 @@ func TestGetExtensions(t *testing.T) { {timestampType: rockRidgeTimestampAttribute, time: now}, }, }, - }, func(path string) { - if err := os.Mkdir(path, 0o754); err != nil { - t.Fatalf("unable to create parent directory %s: %v", path, err) + }, func(p string) { + if err := os.Mkdir(p, 0o754); err != nil { + t.Fatalf("unable to create parent directory %s: %v", p, err) } }, }, @@ -247,40 +251,50 @@ func TestGetExtensions(t *testing.T) { {timestampType: rockRidgeTimestampAttribute, time: now}, }, }, - }, func(path string) { - if err := os.Mkdir(path, 0o754); err != nil { - t.Fatalf("unable to create self directory %s: %v", path, err) + }, func(p string) { + if err := os.Mkdir(p, 0o754); err != nil { + t.Fatalf("unable to create self directory %s: %v", p, err) } }, }, } for _, tt := range tests { - // random filename - fp := filepath.Join(dir, tt.name) - // create the file - tt.createFile(fp) + t.Run(tt.name, func(t *testing.T) { + // random filename + fp := filepath.Join(dir, tt.name) + // create the file + tt.createFile(fp) + fi, err := os.Lstat(fp) + if err != nil { + t.Fatalf("unable to os.Stat(%s): %v", fp, err) + } + ffi, err := finalizeFileInfoFromFile(fp, fp, fi) + if err != nil { + t.Fatalf("unable to create finalizeFileInfo from file %s: %v", fp, err) + } - // get the extensions - ext, err := rr.GetFileExtensions(fp, tt.self, tt.parent) - if err != nil { - t.Fatalf("%s: Unexpected error getting extensions for %s: %v", tt.name, fp, err) - } - if len(ext) != len(tt.extensions) { - t.Fatalf("%s: rock ridge extensions gave %d extensions instead of expected %d", tt.name, len(ext), len(tt.extensions)) - } - // loop through each attribute - for i, e := range ext { - if stamp, ok := e.(rockRidgeTimestamps); ok { - if !stamp.Close(tt.extensions[i]) { + // get the extensions + ext, err := rr.GetFileExtensions(ffi, tt.self, tt.parent) + if err != nil { + t.Fatalf("%s: Unexpected error getting extensions for %s: %v", tt.name, fp, err) + } + if len(ext) != len(tt.extensions) { + t.Fatalf("%s: rock ridge extensions gave %d extensions instead of expected %d", tt.name, len(ext), len(tt.extensions)) + } + // loop through each attribute + for i, e := range ext { + if stamp, ok := e.(rockRidgeTimestamps); ok { + if !stamp.Close(tt.extensions[i]) { + t.Errorf("%s: Mismatched extension number %d for %s, actual then expected", tt.name, i, fp) + t.Logf("%#v\n", e) + t.Logf("%#v\n", tt.extensions[i]) + } + } else if !e.Equal(tt.extensions[i]) { t.Errorf("%s: Mismatched extension number %d for %s, actual then expected", tt.name, i, fp) t.Logf("%#v\n", e) t.Logf("%#v\n", tt.extensions[i]) } - } else if !e.Equal(tt.extensions[i]) { - t.Errorf("%s: Mismatched extension number %d for %s, actual then expected", tt.name, i, fp) - t.Logf("%#v\n", e) - t.Logf("%#v\n", tt.extensions[i]) } - } + }) } }