Skip to content

Commit

Permalink
Initial cut of iso9660
Browse files Browse the repository at this point in the history
  • Loading branch information
deitch committed Oct 10, 2018
1 parent 42465da commit fb37a1b
Show file tree
Hide file tree
Showing 37 changed files with 4,523 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor/
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ services:
language: go

go:
- 1.9.x
- 1.10.3
- master

before_install:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

script:
- make image
- make test

21 changes: 21 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"


[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.2.0"

[[constraint]]
branch = "master"
name = "golang.org/x/sys"
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ image:
docker build -t $(IMAGE) testhelper/docker

dependencies:
@go get -t ./...
@dep ensure

unit_test: dependencies
@go test ./...
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ You do *not* need a partitioned disk to work with a filesystem; filesystems can
### Working With a Disk
Before you can do anything with a disk - partitions or filesystems - you need to access it.

* If you have an existing disk, you `Open()` it
* If you are creating a new one, in general just for disk image files, you `Create()` it
* If you have an existing disk or image file, you `Open()` it
* If you are creating a new one, usually just disk image files, you `Create()` it

Once you have a `Disk`, you can work with partitions or filesystems in it.

Expand All @@ -57,7 +57,7 @@ Once you have a valid disk, and optionally partition, you can access filesystems
* `CreateFilesystem()` - create a filesystem in an individual partition or the entire disk
* `GetFilesystem()` - access an existing filesystem in a partition or the entire disk

As of this writing, supported filesystems include `FAT32`.
As of this writing, supported filesystems include `FAT32` and `ISO9660` (a.k.a. `.iso`).

With a filesystem in hand, you can create, access and modify directories and files.

Expand All @@ -73,6 +73,14 @@ With a `File` in hand, you then can:
* `Read(b []byte)` from the file
* `Seek(offset int64, whence int)` to set the next read or write to an offset in the file

### Read-Only Filesystems
Some filesystem types are intended to be created once, after which they are read-only, for example `ISO9660`/`.iso` and `squashfs`.

`godiskfs` recognizes read-only filesystems and limits working with them to the following:

* You can `GetFilesystem()` a read-only filesystem and do all read activities, but cannot write to them. Any attempt to `Mkdir()` or `OpenFile()` in write/append/create modes or `Write()` to the file will result in an error.
* You can `CreateFilesystem()` a read-only filesystem and write anything to it that you want. It will do all of its work in a "scratch" area, or temporary "workspace" directory on your local filesystem. When you are ready to complete it, you call `Finalize()`, after which it becomes read-only. If you forget to `Finalize()` it, you get... nothing. The `Finalize()` function exists only on read-only filesystems.

### Examples
The following example will create a fully bootable EFI disk image. It assumes you have a bootable EFI file (any modern Linux kernel compiled with `CONFIG_EFI_STUB=y` will work) available.

Expand Down Expand Up @@ -139,5 +147,7 @@ Future plans are to add the following:

* embed boot code in `mbr` e.g. `altmbr.bin` (no need for `gpt` since an ESP with `/EFI/BOOT/BOOT<arch>.EFI` will boot)
* `ext4` filesystem
* `iso9660` / `Rock Ridge` filesystem
* `Rock Ridge` and `Joliet` extensions to `iso9660`
* `El Torito` booting extension to `iso9660`
* `qcow` disk format
* `squashfs` filesystem
7 changes: 7 additions & 0 deletions disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/deitch/diskfs/filesystem"
"github.com/deitch/diskfs/filesystem/fat32"
"github.com/deitch/diskfs/filesystem/iso9660"
"github.com/deitch/diskfs/partition"
)

Expand Down Expand Up @@ -127,6 +128,8 @@ func (d *Disk) CreateFilesystem(partition int, fstype filesystem.Type) (filesyst
switch fstype {
case filesystem.TypeFat32:
return fat32.Create(d.File, size, start, d.LogicalBlocksize)
case filesystem.TypeISO9660:
return iso9660.Create(d.File, size, start, d.LogicalBlocksize)
default:
return nil, errors.New("Unknown filesystem type requested")
}
Expand Down Expand Up @@ -169,5 +172,9 @@ func (d *Disk) GetFilesystem(partition int) (filesystem.FileSystem, error) {
if err == nil {
return fat32FS, nil
}
iso9660FS, err := iso9660.Read(d.File, size, start, d.LogicalBlocksize)
if err == nil {
return iso9660FS, nil
}
return nil, fmt.Errorf("Unknown filesystem on partition %d", partition)
}
2 changes: 1 addition & 1 deletion diskfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestOpen(t *testing.T) {
err error
}{
{"", nil, fmt.Errorf("must pass device name")},
{"/tmp/foo/bar/232323/23/2322/disk.img", nil, fmt.Errorf("provided device %s does not exist", "/tmp/foo/bar/232323/23/2322/disk.img")},
{"/tmp/foo/bar/232323/23/2322/disk.img", nil, fmt.Errorf("Could not open device %s", "/tmp/foo/bar/232323/23/2322/disk.img")},
{path, &disk.Disk{Type: disk.File, LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil},
}

Expand Down
2 changes: 2 additions & 0 deletions filesystem/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ type Type int
const (
// TypeFat32 is a FAT32 compatible filesystem
TypeFat32 Type = iota
// TypeISO9660 is an iso filesystem
TypeISO9660
)
46 changes: 46 additions & 0 deletions filesystem/iso9660/common_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package iso9660

import (
"os"
"testing"
"time"
)

const (
ISO9660File = "./testdata/file.iso"
ISO9660Size = 11018240
)

func GetTestFile(t *testing.T) (*File, string) {
// we use the entry for FILENA01.;1 , which should have the content "filename_01" (without the quotes)
// see ./testdata/README.md
//
// entry:
// {recordSize:0x7a, extAttrSize:0x0, location:0x1422, size:0xb, creation:time.Time{wall:0x0, ext:0, loc:(*time.Location)(nil)}, isHidden:false, isSubdirectory:false, isAssociated:false, hasExtendedAttrs:false, hasOwnerGroupPermissions:false, hasMoreEntries:false, volumeSequence:0x0, filename:"FILENA01.;1"},
// FileSystem implements the FileSystem interface
file, err := os.Open(ISO9660File)
if err != nil {
t.Errorf("Could not read ISO9660 test file %s: %v", ISO9660File, err)
}
fs := &FileSystem{
workspace: "",
size: ISO9660Size,
start: 0,
file: file,
blocksize: 2048,
}
de := &directoryEntry{
extAttrSize: 0,
location: 0x1422,
size: 0xb,
creation: time.Now(),
filesystem: fs,
filename: "FILENA01.;1",
}
return &File{
directoryEntry: de,
isReadWrite: false,
isAppend: false,
offset: 0,
}, "filename_1\n"
}
44 changes: 44 additions & 0 deletions filesystem/iso9660/directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package iso9660

// Directory represents a single directory in a FAT32 filesystem
type Directory struct {
directoryEntry
entries []*directoryEntry
}

// dirEntriesFromBytes loads the directory entries from the raw bytes
func (d *Directory) entriesFromBytes(b []byte, f *FileSystem) error {
entries, err := parseDirEntries(b, f)
if err != nil {
return err
}
d.entries = entries
return nil
}

// entriesToBytes convert our entries to raw bytes
func (d *Directory) entriesToBytes() ([]byte, error) {
b := make([]byte, 0)
blocksize := int(d.filesystem.blocksize)
for _, de := range d.entries {
b2, err := de.toBytes()
if err != nil {
return nil, err
}
// a directory entry cannot cross a block boundary
// so if adding this puts us past it, then pad it
// but only if we are not already exactly at the boundary
newlength := len(b) + len(b2)
left := blocksize - len(b)%blocksize
if left != 0 && newlength/blocksize > len(b)/blocksize {
b = append(b, make([]byte, left)...)
}
b = append(b, b2...)
}
// in the end, must pad to exact blocks
left := blocksize - len(b)%blocksize
if left > 0 {
b = append(b, make([]byte, left)...)
}
return b, nil
}
113 changes: 113 additions & 0 deletions filesystem/iso9660/directory_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package iso9660

import (
"testing"
)

// TestDirectoryEntriesFromBytes largely a duplicate of TestdirectoryEntryParseDirEntries
// it just loads it into the Directory structure
func TestDirectoryEntriesFromBytes(t *testing.T) {
fs := &FileSystem{blocksize: 2048}
validDe, _, _, b, err := getValidDirectoryEntries(fs)
if err != nil {
t.Fatal(err)
}

d := &Directory{}
err = d.entriesFromBytes(b, fs)
switch {
case err != nil:
t.Errorf("Unexpected non-nil error: %v", err)
case d.entries == nil:
t.Errorf("unexpected nil entries")
case len(d.entries) != len(validDe):
t.Errorf("mismatched entries length actual %d vs expected %d", len(d.entries), len(validDe))
default:
// run through them and see that they match
for i, de := range d.entries {
if !compareDirectoryEntriesIgnoreDates(de, validDe[i]) {
t.Errorf("%d: directoryEntry mismatch, actual then valid:", i)
t.Logf("%#v\n", de)
t.Logf("%#v\n", validDe[i])
}
}
}

}

func TestDirectoryEntriesToBytes(t *testing.T) {
blocksize := 2048
validDe, _, _, b, err := getValidDirectoryEntries(nil)
if err != nil {
t.Fatal(err)
}
d := &Directory{
entries: validDe,
directoryEntry: directoryEntry{
filesystem: &FileSystem{
blocksize: int64(blocksize),
},
},
}
output, err := d.entriesToBytes()
// null the date bytes out
if err != nil {
t.Fatalf("unexpected non-nil error: %v", err)
}
// cannot directly compare the bytes as of yet, since the original contains all sorts of system area stuff
output = clearDatesDirectoryBytes(output, blocksize)
output = clearSuspDirectoryBytes(output, blocksize)
b = clearDatesDirectoryBytes(b, blocksize)
b = clearSuspDirectoryBytes(b, blocksize)
switch {
case output == nil:
t.Errorf("unexpected nil bytes")
case len(output) == 0:
t.Errorf("unexpected 0 length byte slice")
case len(output) != len(b):
t.Errorf("mismatched byte slice length actual %d, expected %d", len(output), len(b))
case len(output)%blocksize != 0:
t.Errorf("output size was %d which is not a perfect multiple of %d", len(output), blocksize)
}
}

func clearDatesDirectoryBytes(b []byte, blocksize int) []byte {
if b == nil {
return b
}
nullBytes := make([]byte, 7, 7)
for i := 0; i < len(b); {
// get the length of the current record
dirlen := int(b[i])
if dirlen == 0 {
i += blocksize - blocksize%i
continue
}
copy(b[i+18:i+18+7], nullBytes)
i += dirlen
}
return b
}
func clearSuspDirectoryBytes(b []byte, blocksize int) []byte {
if b == nil {
return b
}
for i := 0; i < len(b); {
// get the length of the current record
dirlen := int(b[i+0])
namelen := int(b[i+32])
if dirlen == 0 {
i += blocksize - blocksize%i
continue
}
if namelen%2 == 0 {
namelen++
}
nullByteStart := 33 + namelen
nullByteLen := dirlen - nullByteStart
nullBytes := make([]byte, nullByteLen, nullByteLen)
copy(b[i+nullByteStart:i+nullByteStart+nullByteLen], nullBytes)
i += dirlen
}
return b
}
Loading

0 comments on commit fb37a1b

Please sign in to comment.