diff --git a/tools/infrakit.hyperkit/cmd/instance.go b/tools/infrakit.hyperkit/cmd/instance.go index 8d2dcc263..8e0419035 100644 --- a/tools/infrakit.hyperkit/cmd/instance.go +++ b/tools/infrakit.hyperkit/cmd/instance.go @@ -14,6 +14,7 @@ import ( log "github.com/Sirupsen/logrus" ps "github.com/mitchellh/go-ps" + "github.com/rneugeba/iso9660wrap" "github.com/docker/infrakit/pkg/spi/instance" "github.com/docker/infrakit/pkg/template" @@ -91,7 +92,7 @@ func (v hyperkitPlugin) Provision(spec instance.Spec) (*instance.ID, error) { "Properties": properties, } - err = v.execHyperKit(params) + err = v.execHyperKit(spec, params) if err != nil { v.Destroy(id) return nil, err @@ -247,7 +248,7 @@ const hyperkitKernArgs = "kexec," + "{{.Properties.Moby}}-initrd.img," + "earlyprintk=serial console=ttyS0 panic=1 vsyscall=emulate page_poison=1 ntp=gateway" -func (v hyperkitPlugin) execHyperKit(params map[string]interface{}) error { +func (v hyperkitPlugin) execHyperKit(spec instance.Spec, params map[string]interface{}) error { instanceDir := params["VMLocation"].(string) @@ -280,6 +281,13 @@ func (v hyperkitPlugin) execHyperKit(params map[string]interface{}) error { return err } + if len(spec.Init) != 0 { + err = createConfigISO(instanceDir, spec.Init) + if err != nil { + return err + } + } + cmd := exec.Command(c[0], c[1:]...) cmd.Env = os.Environ() @@ -338,6 +346,29 @@ func createDisk(instanceDir string, diskSz int) error { return nil } +func createConfigISO(instanceDir, init string) error { + inName := path.Join(instanceDir, "config") + + if err := ioutil.WriteFile(inName, []byte(init), 0666); err != nil { + return err + } + + outfh, err := os.OpenFile(inName+".iso", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666) + if err != nil { + return err + } + infh, err := os.Open(inName) + if err != nil { + return err + } + err = iso9660wrap.WriteFile(outfh, infh) + if err != nil { + log.Fatalf("writing file failed with %s", err) + } + + return nil +} + func stream(r io.ReadCloser, dest chan<- string) { go func() { defer r.Close() diff --git a/tools/infrakit.hyperkit/hyperkit.json b/tools/infrakit.hyperkit/hyperkit.json index 0e4b97054..8521611b9 100644 --- a/tools/infrakit.hyperkit/hyperkit.json +++ b/tools/infrakit.hyperkit/hyperkit.json @@ -15,7 +15,16 @@ }, "Flavor": { "Plugin": "flavor-vanilla", - "Properties": { } + "Properties": { + "Init": [ + "test1", + "test2" + ], + "Tags": { + "tier": "sample", + "project": "infrakit" + } + } } } } diff --git a/tools/infrakit.hyperkit/vendor.conf b/tools/infrakit.hyperkit/vendor.conf index b427ead54..4f08e829e 100644 --- a/tools/infrakit.hyperkit/vendor.conf +++ b/tools/infrakit.hyperkit/vendor.conf @@ -1,4 +1,4 @@ -github.com/Masterminds/sprig 2009c2546db608c737012557c9d3e836468f0423 +github.com/Masterminds/sprig 427e90187e0902bc04b64167610f2da7ba26e0e1 github.com/Sirupsen/logrus 1deb2db2a6fff8a35532079061b903c3a25eed52 github.com/aokoli/goutils 9c37978a95bd5c709a15883b6242714ea6709e64 github.com/armon/go-radix 4239b77079c7b5d1243b7b4736304ce8ddb6f0f2 @@ -9,6 +9,7 @@ github.com/gorilla/rpc 22c016f3df3febe0c1f6727598b6389507e03a18 github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062 +github.com/rneugeba/iso9660wrap 9c7eaf5ac74b2416be8b7b8d1f35b9af44a6e4fa github.com/satori/go.uuid b061729afc07e77a8aa4fad0a2fd840958f1942a github.com/spf13/cobra 16c014f1a19d865b765b420e74508f80eb831ada github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7 diff --git a/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/LICENSE b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/LICENSE new file mode 100644 index 000000000..8a6914999 --- /dev/null +++ b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Marko Tiikkaja + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/README.md b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/README.md new file mode 100644 index 000000000..918b62aac --- /dev/null +++ b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/README.md @@ -0,0 +1,4 @@ +iso9660wrap +=========== +This turns the [iso9660wrap](https://github.com/johto/iso9660wrap) utility into a package. It provides a simple means to create an ISO9660 file containing a single file. + diff --git a/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/directories.go b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/directories.go new file mode 100644 index 000000000..124cd188d --- /dev/null +++ b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/directories.go @@ -0,0 +1,66 @@ +package iso9660wrap + +import ( + "time" +) + +func WriteDirectoryRecord(w *SectorWriter, identifier string, firstSectorNum uint32) uint32 { + if len(identifier) > 30 { + Panicf("directory identifier length %d is out of bounds", len(identifier)) + } + recordLength := 33 + len(identifier) + + w.WriteByte(byte(recordLength)) + w.WriteByte(0) // number of sectors in extended attribute record + w.WriteBothEndianDWord(firstSectorNum) + w.WriteBothEndianDWord(SectorSize) // directory length + writeDirectoryRecordtimestamp(w, time.Now()) + w.WriteByte(byte(3)) // bitfield; directory + w.WriteByte(byte(0)) // file unit size for an interleaved file + w.WriteByte(byte(0)) // interleave gap size for an interleaved file + w.WriteBothEndianWord(1) // volume sequence number + w.WriteByte(byte(len(identifier))) + w.WriteString(identifier) + // optional padding to even length + if recordLength%2 == 1 { + recordLength++ + w.WriteByte(0) + } + return uint32(recordLength) +} + +func WriteFileRecordHeader(w *SectorWriter, identifier string, firstSectorNum uint32, fileSize uint32) uint32 { + if len(identifier) > 30 { + Panicf("directory identifier length %d is out of bounds", len(identifier)) + } + recordLength := 33 + len(identifier) + + w.WriteByte(byte(recordLength)) + w.WriteByte(0) // number of sectors in extended attribute record + w.WriteBothEndianDWord(firstSectorNum) // first sector + w.WriteBothEndianDWord(fileSize) + writeDirectoryRecordtimestamp(w, time.Now()) + w.WriteByte(byte(0)) // bitfield; normal file + w.WriteByte(byte(0)) // file unit size for an interleaved file + w.WriteByte(byte(0)) // interleave gap size for an interleaved file + w.WriteBothEndianWord(1) // volume sequence number + w.WriteByte(byte(len(identifier))) + w.WriteString(identifier) + // optional padding to even length + if recordLength%2 == 1 { + recordLength++ + w.WriteByte(0) + } + return uint32(recordLength) +} + +func writeDirectoryRecordtimestamp(w *SectorWriter, t time.Time) { + t = t.UTC() + w.WriteByte(byte(t.Year() - 1900)) + w.WriteByte(byte(t.Month())) + w.WriteByte(byte(t.Day())) + w.WriteByte(byte(t.Hour())) + w.WriteByte(byte(t.Minute())) + w.WriteByte(byte(t.Second())) + w.WriteByte(0) // UTC offset +} diff --git a/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660_writer.go b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660_writer.go new file mode 100644 index 000000000..88594669f --- /dev/null +++ b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660_writer.go @@ -0,0 +1,148 @@ +package iso9660wrap + +import ( + "encoding/binary" + "io" + "math" + "strings" + "time" +) + +const SectorSize uint32 = 2048 + +type SectorWriter struct { + w io.Writer + p uint32 +} + +func (w *SectorWriter) Write(p []byte) uint32 { + if len(p) >= math.MaxUint32 { + Panicf("attempted write of length %d is out of sector bounds", len(p)) + } + l := uint32(len(p)) + if l > w.RemainingSpace() { + Panicf("attempted write of length %d at offset %d is out of sector bounds", w.p, len(p)) + } + w.p += l + _, err := w.w.Write(p) + if err != nil { + panic(err) + } + return l +} + +func (w *SectorWriter) WriteUnspecifiedDateTime() uint32 { + b := make([]byte, 17) + for i := 0; i < 16; i++ { + b[i] = '0' + } + b[16] = 0 + return w.Write(b) +} + +func (w *SectorWriter) WriteDateTime(t time.Time) uint32 { + f := t.UTC().Format("20060102150405") + f += "00" // 1/100 + f += "\x00" // UTC offset + if len(f) != 17 { + Panicf("date and time field %q is of unexpected length %d", f, len(f)) + } + return w.WriteString(f) +} + +func (w *SectorWriter) WriteString(str string) uint32 { + return w.Write([]byte(str)) +} + +func (w *SectorWriter) WritePaddedString(str string, length uint32) uint32 { + l := w.WriteString(str) + if l > 32 { + Panicf("padded string %q exceeds length %d", str, length) + } else if l < 32 { + w.WriteString(strings.Repeat(" ", int(32-l))) + } + return 32 +} + +func (w *SectorWriter) WriteByte(b byte) uint32 { + return w.Write([]byte{b}) +} + +func (w *SectorWriter) WriteWord(bo binary.ByteOrder, word uint16) uint32 { + b := make([]byte, 2) + bo.PutUint16(b, word) + return w.Write(b) +} + +func (w *SectorWriter) WriteBothEndianWord(word uint16) uint32 { + w.WriteWord(binary.LittleEndian, word) + w.WriteWord(binary.BigEndian, word) + return 4 +} + +func (w *SectorWriter) WriteDWord(bo binary.ByteOrder, dword uint32) uint32 { + b := make([]byte, 4) + bo.PutUint32(b, dword) + return w.Write(b) +} + +func (w *SectorWriter) WriteLittleEndianDWord(dword uint32) uint32 { + return w.WriteDWord(binary.LittleEndian, dword) +} + +func (w *SectorWriter) WriteBigEndianDWord(dword uint32) uint32 { + return w.WriteDWord(binary.BigEndian, dword) +} + +func (w *SectorWriter) WriteBothEndianDWord(dword uint32) uint32 { + w.WriteLittleEndianDWord(dword) + w.WriteBigEndianDWord(dword) + return 8 +} + +func (w *SectorWriter) WriteZeros(c int) uint32 { + return w.Write(make([]byte, c)) +} + +func (w *SectorWriter) PadWithZeros() uint32 { + return w.Write(make([]byte, w.RemainingSpace())) +} + +func (w *SectorWriter) RemainingSpace() uint32 { + return SectorSize - w.p +} + +func (w *SectorWriter) Reset() { + w.p = 0 +} + +type ISO9660Writer struct { + sw *SectorWriter + sectorNum uint32 +} + +func (w *ISO9660Writer) CurrentSector() uint32 { + return uint32(w.sectorNum) +} + +func (w *ISO9660Writer) NextSector() *SectorWriter { + if w.sw.RemainingSpace() == SectorSize { + Panicf("internal error: tried to leave sector %d empty", w.sectorNum) + } + w.sw.PadWithZeros() + w.sw.Reset() + w.sectorNum++ + return w.sw +} + +func (w *ISO9660Writer) Finish() { + if w.sw.RemainingSpace() != SectorSize { + w.sw.PadWithZeros() + } + w.sw = nil +} + +func NewISO9660Writer(w io.Writer) *ISO9660Writer { + // start at the end of the last reserved sector + return &ISO9660Writer{&SectorWriter{w, SectorSize}, 16 - 1} +} diff --git a/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660wrap.go b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660wrap.go new file mode 100644 index 000000000..7acf7f78f --- /dev/null +++ b/tools/infrakit.hyperkit/vendor/github.com/rneugeba/iso9660wrap/iso9660wrap.go @@ -0,0 +1,367 @@ +package iso9660wrap + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "math" + "os" + "strings" + "time" +) + +const reservedAreaData string = ` +Once upon a midnight dreary, while I pondered, weak and weary, +Over many a quaint and curious volume of forgotten lore— + While I nodded, nearly napping, suddenly there came a tapping, +As of some one gently rapping, rapping at my chamber door. +“’Tis some visitor,” I muttered, “tapping at my chamber door— + Only this and nothing more.” + + Ah, distinctly I remember it was in the bleak December; +And each separate dying ember wrought its ghost upon the floor. + Eagerly I wished the morrow;—vainly I had sought to borrow + From my books surcease of sorrow—sorrow for the lost Lenore— +For the rare and radiant maiden whom the angels name Lenore— + Nameless here for evermore. + + And the silken, sad, uncertain rustling of each purple curtain +Thrilled me—filled me with fantastic terrors never felt before; + So that now, to still the beating of my heart, I stood repeating + “’Tis some visitor entreating entrance at my chamber door— +Some late visitor entreating entrance at my chamber door;— + This it is and nothing more.” + + Presently my soul grew stronger; hesitating then no longer, +“Sir,” said I, “or Madam, truly your forgiveness I implore; + But the fact is I was napping, and so gently you came rapping, + And so faintly you came tapping, tapping at my chamber door, +That I scarce was sure I heard you”—here I opened wide the door;— + Darkness there and nothing more. + + Deep into that darkness peering, long I stood there wondering, fearing, +Doubting, dreaming dreams no mortal ever dared to dream before; + But the silence was unbroken, and the stillness gave no token, + And the only word there spoken was the whispered word, “Lenore?” +This I whispered, and an echo murmured back the word, “Lenore!”— + Merely this and nothing more. + + Back into the chamber turning, all my soul within me burning, +Soon again I heard a tapping somewhat louder than before. + “Surely,” said I, “surely that is something at my window lattice; + Let me see, then, what thereat is, and this mystery explore— +Let my heart be still a moment and this mystery explore;— + ’Tis the wind and nothing more!” + + Open here I flung the shutter, when, with many a flirt and flutter, +In there stepped a stately Raven of the saintly days of yore; + Not the least obeisance made he; not a minute stopped or stayed he; + But, with mien of lord or lady, perched above my chamber door— +Perched upon a bust of Pallas just above my chamber door— + Perched, and sat, and nothing more. + +Then this ebony bird beguiling my sad fancy into smiling, +By the grave and stern decorum of the countenance it wore, +“Though thy crest be shorn and shaven, thou,” I said, “art sure no craven, +Ghastly grim and ancient Raven wandering from the Nightly shore— +Tell me what thy lordly name is on the Night’s Plutonian shore!” + Quoth the Raven “Nevermore.” + + Much I marvelled this ungainly fowl to hear discourse so plainly, +Though its answer little meaning—little relevancy bore; + For we cannot help agreeing that no living human being + Ever yet was blessed with seeing bird above his chamber door— +Bird or beast upon the sculptured bust above his chamber door, + With such name as “Nevermore.” + + But the Raven, sitting lonely on the placid bust, spoke only +That one word, as if his soul in that one word he did outpour. + Nothing farther then he uttered—not a feather then he fluttered— + Till I scarcely more than muttered “Other friends have flown before— +On the morrow he will leave me, as my Hopes have flown before.” + Then the bird said “Nevermore.” + + Startled at the stillness broken by reply so aptly spoken, +“Doubtless,” said I, “what it utters is its only stock and store + Caught from some unhappy master whom unmerciful Disaster + Followed fast and followed faster till his songs one burden bore— +Till the dirges of his Hope that melancholy burden bore + Of ‘Never—nevermore’.” + + But the Raven still beguiling all my fancy into smiling, +Straight I wheeled a cushioned seat in front of bird, and bust and door; + Then, upon the velvet sinking, I betook myself to linking + Fancy unto fancy, thinking what this ominous bird of yore— +What this grim, ungainly, ghastly, gaunt, and ominous bird of yore + Meant in croaking “Nevermore.” + + This I sat engaged in guessing, but no syllable expressing +To the fowl whose fiery eyes now burned into my bosom’s core; + This and more I sat divining, with my head at ease reclining + On the cushion’s velvet lining that the lamp-light gloated o’er, +But whose velvet-violet lining with the lamp-light gloating o’er, + She shall press, ah, nevermore! + + Then, methought, the air grew denser, perfumed from an unseen censer +Swung by Seraphim whose foot-falls tinkled on the tufted floor. + “Wretch,” I cried, “thy God hath lent thee—by these angels he hath sent thee + Respite—respite and nepenthe from thy memories of Lenore; +Quaff, oh quaff this kind nepenthe and forget this lost Lenore!” + Quoth the Raven “Nevermore.” + + “Prophet!” said I, “thing of evil!—prophet still, if bird or devil!— +Whether Tempter sent, or whether tempest tossed thee here ashore, + Desolate yet all undaunted, on this desert land enchanted— + On this home by Horror haunted—tell me truly, I implore— +Is there—is there balm in Gilead?—tell me—tell me, I implore!” + Quoth the Raven “Nevermore.” + + “Prophet!” said I, “thing of evil!—prophet still, if bird or devil! +By that Heaven that bends above us—by that God we both adore— + Tell this soul with sorrow laden if, within the distant Aidenn, + It shall clasp a sainted maiden whom the angels name Lenore— +Clasp a rare and radiant maiden whom the angels name Lenore.” + Quoth the Raven “Nevermore.” + + “Be that word our sign of parting, bird or fiend!” I shrieked, upstarting— +“Get thee back into the tempest and the Night’s Plutonian shore! + Leave no black plume as a token of that lie thy soul hath spoken! + Leave my loneliness unbroken!—quit the bust above my door! +Take thy beak from out my heart, and take thy form from off my door!” + Quoth the Raven “Nevermore.” + + And the Raven, never flitting, still is sitting, still is sitting +On the pallid bust of Pallas just above my chamber door; + And his eyes have all the seeming of a demon’s that is dreaming, + And the lamp-light o’er him streaming throws his shadow on the floor; +And my soul from out that shadow that lies floating on the floor + Shall be lifted—nevermore! +` + +func Panicf(format string, v ...interface{}) { + panic(fmt.Errorf(format, v...)) +} + +const volumeDescriptorSetMagic = "\x43\x44\x30\x30\x31\x01" + +const primaryVolumeSectorNum uint32 = 16 +const numVolumeSectors uint32 = 2 // primary + terminator +const littleEndianPathTableSectorNum uint32 = primaryVolumeSectorNum + numVolumeSectors +const bigEndianPathTableSectorNum uint32 = littleEndianPathTableSectorNum + 1 +const numPathTableSectors = 2 // no secondaries +const rootDirectorySectorNum uint32 = primaryVolumeSectorNum + numVolumeSectors + numPathTableSectors + +// WriteFile writes the contents of infh to an iso at outfh with the name provided +func WriteFile(outfh, infh *os.File) error { + inputFileSize, inputFilename, err := getInputFileSizeAndName(infh) + if err != nil { + return err + } + if inputFileSize == 0 { + return fmt.Errorf("input file must be at least 1 byte in size") + } + inputFilename = strings.ToUpper(inputFilename) + if !filenameSatisfiesISOConstraints(inputFilename) { + return fmt.Errorf("Input file name %s does not satisfy the ISO9660 character set constraints", inputFilename) + } + + // reserved sectors + reservedAreaLength := int64(16 * SectorSize) + _, err = outfh.Write([]byte(reservedAreaData)) + if err != nil { + return fmt.Errorf("could not write to output file: %s", err) + } + err = outfh.Truncate(reservedAreaLength) + if err != nil { + return fmt.Errorf("could not truncate output file: %s", err) + } + _, err = outfh.Seek(reservedAreaLength, os.SEEK_SET) + if err != nil { + return fmt.Errorf("could not seek output file: %s", err) + } + + err = nil + func() { + defer func() { + var ok bool + e := recover() + if e != nil { + err, ok = e.(error) + if !ok { + panic(e) + } + } + }() + + bufw := bufio.NewWriter(outfh) + + w := NewISO9660Writer(bufw) + + writePrimaryVolumeDescriptor(w, inputFileSize, inputFilename) + writeVolumeDescriptorSetTerminator(w) + writePathTable(w, binary.LittleEndian) + writePathTable(w, binary.BigEndian) + writeData(w, infh, inputFileSize, inputFilename) + + w.Finish() + + err := bufw.Flush() + if err != nil { + panic(err) + } + }() + if err != nil { + return fmt.Errorf("could not write to output file: %s", err) + } + return nil +} + +func writePrimaryVolumeDescriptor(w *ISO9660Writer, inputFileSize uint32, inputFilename string) { + if len(inputFilename) > 32 { + inputFilename = inputFilename[:32] + } + now := time.Now() + + sw := w.NextSector() + if w.CurrentSector() != primaryVolumeSectorNum { + Panicf("internal error: unexpected primary volume sector %d", w.CurrentSector()) + } + + sw.WriteByte('\x01') + sw.WriteString(volumeDescriptorSetMagic) + sw.WriteByte('\x00') + + sw.WritePaddedString("", 32) + sw.WritePaddedString(inputFilename, 32) + + sw.WriteZeros(8) + sw.WriteBothEndianDWord(numTotalSectors(inputFileSize)) + sw.WriteZeros(32) + + sw.WriteBothEndianWord(1) // volume set size + sw.WriteBothEndianWord(1) // volume sequence number + sw.WriteBothEndianWord(uint16(SectorSize)) + sw.WriteBothEndianDWord(SectorSize) // path table length + + sw.WriteLittleEndianDWord(littleEndianPathTableSectorNum) + sw.WriteLittleEndianDWord(0) // no secondary path tables + sw.WriteBigEndianDWord(bigEndianPathTableSectorNum) + sw.WriteBigEndianDWord(0) // no secondary path tables + + WriteDirectoryRecord(sw, "\x00", rootDirectorySectorNum) // root directory + + sw.WritePaddedString("", 128) // volume set identifier + sw.WritePaddedString("", 128) // publisher identifier + sw.WritePaddedString("", 128) // data preparer identifier + sw.WritePaddedString("", 128) // application identifier + + sw.WritePaddedString("", 37) // copyright file identifier + sw.WritePaddedString("", 37) // abstract file identifier + sw.WritePaddedString("", 37) // bibliographical file identifier + + sw.WriteDateTime(now) // volume creation + sw.WriteDateTime(now) // most recent modification + sw.WriteUnspecifiedDateTime() // expires + sw.WriteUnspecifiedDateTime() // is effective (?) + + sw.WriteByte('\x01') // version + sw.WriteByte('\x00') // reserved + + sw.PadWithZeros() // 512 (reserved for app) + 653 (zeros) +} + +func writeVolumeDescriptorSetTerminator(w *ISO9660Writer) { + sw := w.NextSector() + if w.CurrentSector() != primaryVolumeSectorNum+1 { + Panicf("internal error: unexpected volume descriptor set terminator sector %d", w.CurrentSector()) + } + + sw.WriteByte('\xFF') + sw.WriteString(volumeDescriptorSetMagic) + + sw.PadWithZeros() +} + +func writePathTable(w *ISO9660Writer, bo binary.ByteOrder) { + sw := w.NextSector() + sw.WriteByte(1) // name length + sw.WriteByte(0) // number of sectors in extended attribute record + sw.WriteDWord(bo, rootDirectorySectorNum) + sw.WriteWord(bo, 1) // parent directory recno (root directory) + sw.WriteByte(0) // identifier (root directory) + sw.WriteByte(1) // padding + sw.PadWithZeros() +} + +func writeData(w *ISO9660Writer, infh io.Reader, inputFileSize uint32, inputFilename string) { + sw := w.NextSector() + if w.CurrentSector() != rootDirectorySectorNum { + Panicf("internal error: unexpected root directory sector %d", w.CurrentSector()) + } + + WriteDirectoryRecord(sw, "\x00", w.CurrentSector()) + WriteDirectoryRecord(sw, "\x01", rootDirectorySectorNum) + WriteFileRecordHeader(sw, inputFilename, w.CurrentSector()+1, inputFileSize) + + // Now stream the data. Note that the first buffer is never of SectorSize, + // since we've already filled a part of the sector. + b := make([]byte, SectorSize) + total := uint32(0) + for { + l, err := infh.Read(b) + if err != nil && err != io.EOF { + Panicf("could not read from input file: %s", err) + } + if l > 0 { + sw = w.NextSector() + sw.Write(b[:l]) + total += uint32(l) + } + if err == io.EOF { + break + } + } + if total != inputFileSize { + Panicf("input file size changed while the ISO file was being created (expected to read %d, read %d)", inputFileSize, total) + } else if w.CurrentSector() != numTotalSectors(inputFileSize)-1 { + Panicf("internal error: unexpected last sector number (expected %d, actual %d)", + numTotalSectors(inputFileSize)-1, w.CurrentSector()) + } +} + +func numTotalSectors(inputFileSize uint32) uint32 { + var numDataSectors uint32 + numDataSectors = (inputFileSize + (SectorSize - 1)) / SectorSize + return 1 + rootDirectorySectorNum + numDataSectors +} + +func getInputFileSizeAndName(fh *os.File) (uint32, string, error) { + fi, err := fh.Stat() + if err != nil { + return 0, "", err + } + if fi.Size() >= math.MaxUint32 { + return 0, "", fmt.Errorf("file size %d is too large", fi.Size()) + } + return uint32(fi.Size()), fi.Name(), nil +} + +func filenameSatisfiesISOConstraints(filename string) bool { + invalidCharacter := func(r rune) bool { + // According to ISO9660, only capital letters, digits, and underscores + // are permitted. Some sources say a dot is allowed as well. I'm too + // lazy to figure it out right now. + if r >= 'A' && r <= 'Z' { + return false + } else if r >= '0' && r <= '9' { + return false + } else if r == '_' { + return false + } else if r == '.' { + return false + } + return true + } + return strings.IndexFunc(filename, invalidCharacter) == -1 +}