Skip to content

Commit 5aad41e

Browse files
MagicalTuxclaude
andcommitted
Modernize codebase and add Rock Ridge extension support
- Add go.mod with Go 1.21 requirement - Replace deprecated io/ioutil with io and os equivalents - Fix WriteTo signature to implement io.WriterTo properly - Remove unused volumeDescriptorBodySize constant - Fix filename parsing for files without dot (upstream bugfix) - Add Label() method to get volume label - Add AddLocalDirectory() for recursive directory addition - Add Rock Ridge extension support for reader: - SUSP (System Use Sharing Protocol) parsing - Rock Ridge PX (POSIX attributes) and NM (alternate names) - Long filenames, symlink detection, POSIX permissions - Add comprehensive tests for SUSP and Rock Ridge - Rename fixtures/ to testdata/ (goproxy exclusion) - Update README with new features and examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 23d0ef3 commit 5aad41e

File tree

1,020 files changed

+991
-54
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,020 files changed

+991
-54
lines changed

README.md

Lines changed: 164 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
## iso9660
2+
23
[![GoDoc](https://godoc.org/github.com/KarpelesLab/iso9660?status.svg)](https://godoc.org/github.com/KarpelesLab/iso9660)
34

4-
A package for reading and creating ISO9660, forked from https://github.com/kdomanski/iso9660.
5+
A Go package for reading and creating ISO9660 images, forked from https://github.com/kdomanski/iso9660.
6+
7+
Requires Go 1.21 or newer.
8+
9+
## Features
510

6-
Requires Go 1.13 or newer.
11+
### Reading
12+
- Basic ISO9660 support
13+
- Rock Ridge extensions (long filenames, POSIX permissions, symlinks)
14+
- SUSP (System Use Sharing Protocol)
715

8-
Joliet and Rock Ridge extensions are not supported.
16+
### Writing
17+
- Basic ISO9660 support
18+
- El Torito boot support
19+
- Hard links (same file added multiple times is written once)
20+
- Streaming writes (no temp files needed)
21+
22+
Joliet extensions are not supported.
923

1024
## Examples
1125

@@ -16,6 +30,7 @@ package main
1630

1731
import (
1832
"log"
33+
"os"
1934

2035
"github.com/KarpelesLab/iso9660/isoutil"
2136
)
@@ -33,6 +48,53 @@ func main() {
3348
}
3449
```
3550

51+
### Reading an ISO with Rock Ridge
52+
53+
```go
54+
package main
55+
56+
import (
57+
"fmt"
58+
"log"
59+
"os"
60+
61+
"github.com/KarpelesLab/iso9660"
62+
)
63+
64+
func main() {
65+
f, err := os.Open("/home/user/myImage.iso")
66+
if err != nil {
67+
log.Fatalf("failed to open file: %s", err)
68+
}
69+
defer f.Close()
70+
71+
img, err := iso9660.OpenImage(f)
72+
if err != nil {
73+
log.Fatalf("failed to open image: %s", err)
74+
}
75+
76+
// Get the volume label
77+
label, _ := img.Label()
78+
fmt.Printf("Volume: %s\n", label)
79+
80+
root, err := img.RootDir()
81+
if err != nil {
82+
log.Fatalf("failed to get root dir: %s", err)
83+
}
84+
85+
children, err := root.GetChildren()
86+
if err != nil {
87+
log.Fatalf("failed to get children: %s", err)
88+
}
89+
90+
for _, child := range children {
91+
// With Rock Ridge, Name() returns the full filename
92+
// Mode() returns POSIX permissions
93+
fmt.Printf("%s %s (%d bytes)\n", child.Mode(), child.Name(), child.Size())
94+
}
95+
}
96+
```
97+
3698
### Creating an ISO
3799

38100
```go
@@ -51,20 +113,27 @@ func main() {
51113
log.Fatalf("failed to create writer: %s", err)
52114
}
53115

54-
// set volume name
116+
// Set volume name
55117
writer.Primary.VolumeIdentifier = "testvol"
56118

119+
// Add a single file
57120
err = writer.AddLocalFile("/home/user/myFile.txt", "folder/MYFILE.TXT")
58121
if err != nil {
59122
log.Fatalf("failed to add file: %s", err)
60123
}
61124

62-
outputFile, err := os.OpenFile("/home/user/output.iso", os.O_WRONLY | os.O_TRUNC | os.O_CREATE, 0644)
125+
// Or add an entire directory recursively
126+
err = writer.AddLocalDirectory("/home/user/myFolder", "folder")
127+
if err != nil {
128+
log.Fatalf("failed to add directory: %s", err)
129+
}
130+
131+
outputFile, err := os.OpenFile("/home/user/output.iso", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
63132
if err != nil {
64133
log.Fatalf("failed to create file: %s", err)
65134
}
66135

67-
err = writer.WriteTo(outputFile)
136+
_, err = writer.WriteTo(outputFile)
68137
if err != nil {
69138
log.Fatalf("failed to write ISO image: %s", err)
70139
}
@@ -76,39 +145,113 @@ func main() {
76145
}
77146
```
78147

79-
### Streaming an ISO via HTTP
80-
81-
It is possible to stream a dynamically generated file on request via HTTP in order to include files or customize configuration files:
148+
### Creating a Bootable ISO
82149

83150
```go
151+
package main
152+
84153
import (
85-
"http"
86154
"log"
155+
"os"
87156

88157
"github.com/KarpelesLab/iso9660"
89158
)
90159

91-
func ServeHTTP(rw http.RequestWriter, req *http.Request) {
160+
func main() {
92161
writer, err := iso9660.NewWriter()
93162
if err != nil {
94163
log.Fatalf("failed to create writer: %s", err)
95164
}
96165

97-
// set volume name
98-
writer.Primary.VolumeIdentifier = "LIVE IMAGE"
166+
writer.Primary.VolumeIdentifier = "BOOTABLE"
167+
168+
// Add El Torito boot entry
169+
isolinux, err := iso9660.NewItemFile("/usr/share/syslinux/isolinux.bin")
170+
if err != nil {
171+
log.Fatalf("failed to open isolinux.bin: %s", err)
172+
}
173+
174+
err = writer.AddBootEntry(&iso9660.BootCatalogEntry{BootInfoTable: true}, isolinux, "isolinux/isolinux.bin")
175+
if err != nil {
176+
log.Fatalf("failed to add boot entry: %s", err)
177+
}
178+
179+
// Add other boot files
180+
writer.AddLocalFile("/usr/share/syslinux/ldlinux.c32", "isolinux/ldlinux.c32")
99181

100-
if syslinux, err := iso9660.NewItemFile("/pkg/main/sys-boot.syslinux.core/share/syslinux/isolinux.bin"); err == nil {
101-
writer.AddBootEntry(&iso9660.BootCatalogEntry{BootInfoTable: true}, isolinux, "isolinux/isolinux.bin")
102-
writer.AddLocalFile("/pkg/main/sys-boot.syslinux.core/share/syslinux/linux.c32", "isolinux/linux.c32")
103-
writer.AddLocalFile("/pkg/main/sys-boot.syslinux.core/share/syslinux/ldlinux.c32", "isolinux/ldlinux.c32")
182+
outputFile, err := os.Create("/home/user/bootable.iso")
183+
if err != nil {
184+
log.Fatalf("failed to create file: %s", err)
104185
}
105186

106-
writer.AddLocalFile("kernel.img", "isolinux/kernel.img")
107-
writer.AddLocalFile("initrd.img", "isolinux/initrd.img")
108-
writer.AddLocalFile("root.squashfs", "root.img")
109-
writer.AddFile(getSyslinuxConfig(), "isolinux/isolinux.cfg")
187+
_, err = writer.WriteTo(outputFile)
188+
if err != nil {
189+
log.Fatalf("failed to write ISO image: %s", err)
190+
}
191+
192+
outputFile.Close()
193+
}
194+
```
195+
196+
### Streaming an ISO via HTTP
197+
198+
It is possible to stream a dynamically generated ISO via HTTP:
199+
200+
```go
201+
package main
202+
203+
import (
204+
"net/http"
205+
"log"
206+
207+
"github.com/KarpelesLab/iso9660"
208+
)
209+
210+
func handler(rw http.ResponseWriter, req *http.Request) {
211+
writer, err := iso9660.NewWriter()
212+
if err != nil {
213+
http.Error(rw, err.Error(), 500)
214+
return
215+
}
216+
217+
writer.Primary.VolumeIdentifier = "LIVE IMAGE"
218+
219+
// Add files dynamically
220+
writer.AddLocalFile("kernel.img", "boot/kernel.img")
221+
writer.AddLocalFile("initrd.img", "boot/initrd.img")
110222

111223
rw.Header().Set("Content-Type", "application/x-iso9660-image")
224+
rw.Header().Set("Content-Disposition", "attachment; filename=image.iso")
225+
112226
writer.WriteTo(rw)
113227
}
228+
229+
func main() {
230+
http.HandleFunc("/image.iso", handler)
231+
log.Fatal(http.ListenAndServe(":8080", nil))
232+
}
114233
```
234+
235+
## API
236+
237+
### Reader
238+
239+
- `OpenImage(ra io.ReaderAt) (*Image, error)` - Open an ISO image for reading
240+
- `(*Image) RootDir() (*File, error)` - Get the root directory
241+
- `(*Image) Label() (string, error)` - Get the volume label
242+
- `(*File) GetChildren() ([]*File, error)` - Get directory children (excludes `.` and `..`)
243+
- `(*File) GetAllChildren() ([]*File, error)` - Get all directory children (includes `.` and `..`)
244+
- `(*File) Reader() io.Reader` - Get a reader for file contents
245+
- `(*File) Name() string` - Get filename (Rock Ridge long name if available)
246+
- `(*File) Mode() os.FileMode` - Get file mode (Rock Ridge permissions if available)
247+
- `(*File) IsDir() bool` - Check if entry is a directory
248+
- `(*File) Size() int64` - Get file size
249+
250+
### Writer
251+
252+
- `NewWriter() (*ImageWriter, error)` - Create a new ISO writer
253+
- `(*ImageWriter) AddFile(data io.Reader, filePath string) error` - Add a file from a reader
254+
- `(*ImageWriter) AddLocalFile(localPath, filePath string) error` - Add a file from the filesystem
255+
- `(*ImageWriter) AddLocalDirectory(origin, target string) error` - Add a directory recursively
256+
- `(*ImageWriter) AddBootEntry(boot *BootCatalogEntry, data Item, filePath string) error` - Add El Torito boot entry
257+
- `(*ImageWriter) WriteTo(w io.Writer) (int64, error)` - Write the ISO image

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/KarpelesLab/iso9660
2+
3+
go 1.21
4+
5+
require github.com/stretchr/testify v1.8.4
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)