Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 64fb6c1

Browse files
authoredOct 10, 2018
Merge pull request #1 from deitch/iso9660
iso9660
2 parents 42465da + ad5ad98 commit 64fb6c1

37 files changed

+4523
-8
lines changed
 

‎.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor/

‎.travis.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ services:
66
language: go
77

88
go:
9-
- 1.9.x
9+
- 1.10.3
1010
- master
1111

12+
before_install:
13+
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
14+
1215
script:
1316
- make image
1417
- make test
15-

‎Gopkg.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Gopkg.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
# Gopkg.toml example
3+
#
4+
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
5+
# for detailed Gopkg.toml documentation.
6+
#
7+
# required = ["github.com/user/thing/cmd/thing"]
8+
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
9+
#
10+
# [[constraint]]
11+
# name = "github.com/user/project"
12+
# version = "1.0.0"
13+
#
14+
# [[constraint]]
15+
# name = "github.com/user/project2"
16+
# branch = "dev"
17+
# source = "github.com/myfork/project2"
18+
#
19+
# [[override]]
20+
# name = "github.com/x/y"
21+
# version = "2.4.0"
22+
23+
24+
[[constraint]]
25+
name = "github.com/satori/go.uuid"
26+
version = "1.2.0"
27+
28+
[[constraint]]
29+
branch = "master"
30+
name = "golang.org/x/sys"

‎Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ image:
66
docker build -t $(IMAGE) testhelper/docker
77

88
dependencies:
9-
@go get -t ./...
9+
@dep ensure
1010

1111
unit_test: dependencies
1212
@go test ./...

‎README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ You do *not* need a partitioned disk to work with a filesystem; filesystems can
3737
### Working With a Disk
3838
Before you can do anything with a disk - partitions or filesystems - you need to access it.
3939

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

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

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

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

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

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

76+
### Read-Only Filesystems
77+
Some filesystem types are intended to be created once, after which they are read-only, for example `ISO9660`/`.iso` and `squashfs`.
78+
79+
`godiskfs` recognizes read-only filesystems and limits working with them to the following:
80+
81+
* 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.
82+
* 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.
83+
7684
### Examples
7785
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.
7886

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

140148
* embed boot code in `mbr` e.g. `altmbr.bin` (no need for `gpt` since an ESP with `/EFI/BOOT/BOOT<arch>.EFI` will boot)
141149
* `ext4` filesystem
142-
* `iso9660` / `Rock Ridge` filesystem
150+
* `Rock Ridge` and `Joliet` extensions to `iso9660`
151+
* `El Torito` booting extension to `iso9660`
143152
* `qcow` disk format
153+
* `squashfs` filesystem

‎disk/disk.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/deitch/diskfs/filesystem"
1414
"github.com/deitch/diskfs/filesystem/fat32"
15+
"github.com/deitch/diskfs/filesystem/iso9660"
1516
"github.com/deitch/diskfs/partition"
1617
)
1718

@@ -127,6 +128,8 @@ func (d *Disk) CreateFilesystem(partition int, fstype filesystem.Type) (filesyst
127128
switch fstype {
128129
case filesystem.TypeFat32:
129130
return fat32.Create(d.File, size, start, d.LogicalBlocksize)
131+
case filesystem.TypeISO9660:
132+
return iso9660.Create(d.File, size, start, d.LogicalBlocksize)
130133
default:
131134
return nil, errors.New("Unknown filesystem type requested")
132135
}
@@ -169,5 +172,9 @@ func (d *Disk) GetFilesystem(partition int) (filesystem.FileSystem, error) {
169172
if err == nil {
170173
return fat32FS, nil
171174
}
175+
iso9660FS, err := iso9660.Read(d.File, size, start, d.LogicalBlocksize)
176+
if err == nil {
177+
return iso9660FS, nil
178+
}
172179
return nil, fmt.Errorf("Unknown filesystem on partition %d", partition)
173180
}

‎diskfs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestOpen(t *testing.T) {
6161
err error
6262
}{
6363
{"", nil, fmt.Errorf("must pass device name")},
64-
{"/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")},
64+
{"/tmp/foo/bar/232323/23/2322/disk.img", nil, fmt.Errorf("")},
6565
{path, &disk.Disk{Type: disk.File, LogicalBlocksize: 512, PhysicalBlocksize: 512, Size: size}, nil},
6666
}
6767

‎filesystem/filesystem.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ type Type int
2020
const (
2121
// TypeFat32 is a FAT32 compatible filesystem
2222
TypeFat32 Type = iota
23+
// TypeISO9660 is an iso filesystem
24+
TypeISO9660
2325
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package iso9660
2+
3+
import (
4+
"os"
5+
"testing"
6+
"time"
7+
)
8+
9+
const (
10+
ISO9660File = "./testdata/file.iso"
11+
ISO9660Size = 11018240
12+
)
13+
14+
func GetTestFile(t *testing.T) (*File, string) {
15+
// we use the entry for FILENA01.;1 , which should have the content "filename_01" (without the quotes)
16+
// see ./testdata/README.md
17+
//
18+
// entry:
19+
// {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"},
20+
// FileSystem implements the FileSystem interface
21+
file, err := os.Open(ISO9660File)
22+
if err != nil {
23+
t.Errorf("Could not read ISO9660 test file %s: %v", ISO9660File, err)
24+
}
25+
fs := &FileSystem{
26+
workspace: "",
27+
size: ISO9660Size,
28+
start: 0,
29+
file: file,
30+
blocksize: 2048,
31+
}
32+
de := &directoryEntry{
33+
extAttrSize: 0,
34+
location: 0x1422,
35+
size: 0xb,
36+
creation: time.Now(),
37+
filesystem: fs,
38+
filename: "FILENA01.;1",
39+
}
40+
return &File{
41+
directoryEntry: de,
42+
isReadWrite: false,
43+
isAppend: false,
44+
offset: 0,
45+
}, "filename_1\n"
46+
}

‎filesystem/iso9660/directory.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package iso9660
2+
3+
// Directory represents a single directory in a FAT32 filesystem
4+
type Directory struct {
5+
directoryEntry
6+
entries []*directoryEntry
7+
}
8+
9+
// dirEntriesFromBytes loads the directory entries from the raw bytes
10+
func (d *Directory) entriesFromBytes(b []byte, f *FileSystem) error {
11+
entries, err := parseDirEntries(b, f)
12+
if err != nil {
13+
return err
14+
}
15+
d.entries = entries
16+
return nil
17+
}
18+
19+
// entriesToBytes convert our entries to raw bytes
20+
func (d *Directory) entriesToBytes() ([]byte, error) {
21+
b := make([]byte, 0)
22+
blocksize := int(d.filesystem.blocksize)
23+
for _, de := range d.entries {
24+
b2, err := de.toBytes()
25+
if err != nil {
26+
return nil, err
27+
}
28+
// a directory entry cannot cross a block boundary
29+
// so if adding this puts us past it, then pad it
30+
// but only if we are not already exactly at the boundary
31+
newlength := len(b) + len(b2)
32+
left := blocksize - len(b)%blocksize
33+
if left != 0 && newlength/blocksize > len(b)/blocksize {
34+
b = append(b, make([]byte, left)...)
35+
}
36+
b = append(b, b2...)
37+
}
38+
// in the end, must pad to exact blocks
39+
left := blocksize - len(b)%blocksize
40+
if left > 0 {
41+
b = append(b, make([]byte, left)...)
42+
}
43+
return b, nil
44+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package iso9660
2+
3+
import (
4+
"testing"
5+
)
6+
7+
// TestDirectoryEntriesFromBytes largely a duplicate of TestdirectoryEntryParseDirEntries
8+
// it just loads it into the Directory structure
9+
func TestDirectoryEntriesFromBytes(t *testing.T) {
10+
fs := &FileSystem{blocksize: 2048}
11+
validDe, _, _, b, err := getValidDirectoryEntries(fs)
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
16+
d := &Directory{}
17+
err = d.entriesFromBytes(b, fs)
18+
switch {
19+
case err != nil:
20+
t.Errorf("Unexpected non-nil error: %v", err)
21+
case d.entries == nil:
22+
t.Errorf("unexpected nil entries")
23+
case len(d.entries) != len(validDe):
24+
t.Errorf("mismatched entries length actual %d vs expected %d", len(d.entries), len(validDe))
25+
default:
26+
// run through them and see that they match
27+
for i, de := range d.entries {
28+
if !compareDirectoryEntriesIgnoreDates(de, validDe[i]) {
29+
t.Errorf("%d: directoryEntry mismatch, actual then valid:", i)
30+
t.Logf("%#v\n", de)
31+
t.Logf("%#v\n", validDe[i])
32+
}
33+
}
34+
}
35+
36+
}
37+
38+
func TestDirectoryEntriesToBytes(t *testing.T) {
39+
blocksize := 2048
40+
validDe, _, _, b, err := getValidDirectoryEntries(nil)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
d := &Directory{
45+
entries: validDe,
46+
directoryEntry: directoryEntry{
47+
filesystem: &FileSystem{
48+
blocksize: int64(blocksize),
49+
},
50+
},
51+
}
52+
output, err := d.entriesToBytes()
53+
// null the date bytes out
54+
if err != nil {
55+
t.Fatalf("unexpected non-nil error: %v", err)
56+
}
57+
// cannot directly compare the bytes as of yet, since the original contains all sorts of system area stuff
58+
output = clearDatesDirectoryBytes(output, blocksize)
59+
output = clearSuspDirectoryBytes(output, blocksize)
60+
b = clearDatesDirectoryBytes(b, blocksize)
61+
b = clearSuspDirectoryBytes(b, blocksize)
62+
switch {
63+
case output == nil:
64+
t.Errorf("unexpected nil bytes")
65+
case len(output) == 0:
66+
t.Errorf("unexpected 0 length byte slice")
67+
case len(output) != len(b):
68+
t.Errorf("mismatched byte slice length actual %d, expected %d", len(output), len(b))
69+
case len(output)%blocksize != 0:
70+
t.Errorf("output size was %d which is not a perfect multiple of %d", len(output), blocksize)
71+
}
72+
}
73+
74+
func clearDatesDirectoryBytes(b []byte, blocksize int) []byte {
75+
if b == nil {
76+
return b
77+
}
78+
nullBytes := make([]byte, 7, 7)
79+
for i := 0; i < len(b); {
80+
// get the length of the current record
81+
dirlen := int(b[i])
82+
if dirlen == 0 {
83+
i += blocksize - blocksize%i
84+
continue
85+
}
86+
copy(b[i+18:i+18+7], nullBytes)
87+
i += dirlen
88+
}
89+
return b
90+
}
91+
func clearSuspDirectoryBytes(b []byte, blocksize int) []byte {
92+
if b == nil {
93+
return b
94+
}
95+
for i := 0; i < len(b); {
96+
// get the length of the current record
97+
dirlen := int(b[i+0])
98+
namelen := int(b[i+32])
99+
if dirlen == 0 {
100+
i += blocksize - blocksize%i
101+
continue
102+
}
103+
if namelen%2 == 0 {
104+
namelen++
105+
}
106+
nullByteStart := 33 + namelen
107+
nullByteLen := dirlen - nullByteStart
108+
nullBytes := make([]byte, nullByteLen, nullByteLen)
109+
copy(b[i+nullByteStart:i+nullByteStart+nullByteLen], nullBytes)
110+
i += dirlen
111+
}
112+
return b
113+
}

0 commit comments

Comments
 (0)