Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iso9660 #1

Merged
merged 1 commit into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("")},
{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