image_writer.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. package iso9660
  2. import (
  3. "bytes"
  4. "container/list"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "math"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "sync/atomic"
  15. "time"
  16. )
  17. const (
  18. primaryVolumeDirectoryIdentifierMaxLength = 31 // ECMA-119 7.6.3
  19. primaryVolumeFileIdentifierMaxLength = 30 // ECMA-119 7.5
  20. )
  21. var (
  22. // ErrFileTooLarge is returned when trying to process a file of size greater
  23. // than 4GB, which due to the 32-bit address limitation is not possible
  24. // except with ISO 9660-Level 3
  25. ErrFileTooLarge = errors.New("file is exceeding the maximum file size of 4GB")
  26. )
  27. // ImageWriter is responsible for staging an image's contents
  28. // and writing them to an image.
  29. type ImageWriter struct {
  30. stagingDir string
  31. }
  32. // NewWriter creates a new ImageWrite and initializes its temporary staging dir.
  33. // Cleanup should be called after the ImageWriter is no longer needed.
  34. func NewWriter() (*ImageWriter, error) {
  35. tmp, err := os.MkdirTemp("", "")
  36. if err != nil {
  37. return nil, err
  38. }
  39. return &ImageWriter{stagingDir: tmp}, nil
  40. }
  41. // Cleanup deletes the underlying temporary staging directory of an ImageWriter.
  42. // It can be called multiple times without issues.
  43. func (iw *ImageWriter) Cleanup() error {
  44. if iw.stagingDir == "" {
  45. return nil
  46. }
  47. if err := os.RemoveAll(iw.stagingDir); err != nil {
  48. return err
  49. }
  50. iw.stagingDir = ""
  51. return nil
  52. }
  53. // AddFile adds a file to the ImageWriter's staging area.
  54. // All path components are mangled to match basic ISO9660 filename requirements.
  55. func (iw *ImageWriter) AddFile(data io.Reader, filePath string) error {
  56. directoryPath, fileName := manglePath(filePath)
  57. if err := os.MkdirAll(path.Join(iw.stagingDir, directoryPath), 0755); err != nil {
  58. return err
  59. }
  60. f, err := os.OpenFile(path.Join(iw.stagingDir, directoryPath, fileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
  61. if err != nil {
  62. return err
  63. }
  64. defer f.Close()
  65. _, err = io.Copy(f, data)
  66. return err
  67. }
  68. func failIfSymlink(path string) error {
  69. info, err := os.Lstat(path)
  70. if err != nil {
  71. return err
  72. }
  73. if info.Mode()&os.ModeSymlink != 0 {
  74. return fmt.Errorf("%q is a symlink - these are not yet supported", path)
  75. }
  76. return nil
  77. }
  78. // AddLocalFile adds a file identified by its path to the ImageWriter's staging area.
  79. func (iw *ImageWriter) AddLocalFile(origin, target string) error {
  80. if err := failIfSymlink(origin); err != nil {
  81. return err
  82. }
  83. directoryPath, fileName := manglePath(target)
  84. if err := os.MkdirAll(path.Join(iw.stagingDir, directoryPath), 0755); err != nil {
  85. return err
  86. }
  87. // try to hardlink file to staging area before copying.
  88. stagedFile := path.Join(iw.stagingDir, directoryPath, fileName)
  89. if err := os.Remove(stagedFile); err != nil && !os.IsNotExist(err) {
  90. return err
  91. }
  92. if err := os.Link(origin, stagedFile); err == nil {
  93. return nil
  94. }
  95. f, err := os.Open(origin)
  96. if err != nil {
  97. return err
  98. }
  99. defer f.Close()
  100. return iw.AddFile(f, target)
  101. }
  102. func ensureIsDirectory(path string) error {
  103. f, err := os.Open(path)
  104. if err != nil {
  105. return err
  106. }
  107. defer f.Close()
  108. fileinfo, err := f.Stat()
  109. if err != nil {
  110. return err
  111. }
  112. if !fileinfo.IsDir() {
  113. return fmt.Errorf("%q is not a directory", path)
  114. }
  115. return nil
  116. }
  117. // AddLocalDirectory adds a directory recursively to the ImageWriter's staging area.
  118. func (iw *ImageWriter) AddLocalDirectory(origin, target string) error {
  119. if err := ensureIsDirectory(origin); err != nil {
  120. return err
  121. }
  122. walkfn := func(path string, info os.FileInfo, err error) error {
  123. if info.IsDir() {
  124. return nil
  125. }
  126. relPath := path[len(origin):] // We need the path to be relative to the origin.
  127. return iw.AddLocalFile(path, filepath.Join(target, relPath))
  128. }
  129. return filepath.Walk(origin, walkfn)
  130. }
  131. func manglePath(input string) (string, string) {
  132. input = posixifyPath(input)
  133. nonEmptySegments := splitPath(input)
  134. dirSegments := nonEmptySegments[:len(nonEmptySegments)-1]
  135. name := nonEmptySegments[len(nonEmptySegments)-1]
  136. for i := 0; i < len(dirSegments); i++ {
  137. dirSegments[i] = mangleDirectoryName(dirSegments[i])
  138. }
  139. name = mangleFileName(name)
  140. return path.Join(dirSegments...), name
  141. }
  142. // Converts given path to Posix (replacing \ with /)
  143. //
  144. // @param {string} givenPath Path to convert
  145. //
  146. // @returns {string} Converted filepath
  147. func posixifyPath(path string) string {
  148. if runtime.GOOS == "windows" {
  149. return strings.ReplaceAll(path, "\\", "/")
  150. }
  151. return path
  152. }
  153. func splitPath(input string) []string {
  154. rawSegments := strings.Split(input, "/")
  155. var nonEmptySegments []string
  156. for _, s := range rawSegments {
  157. if len(s) > 0 {
  158. nonEmptySegments = append(nonEmptySegments, s)
  159. }
  160. }
  161. return nonEmptySegments
  162. }
  163. // See ECMA-119 7.5
  164. func mangleFileName(input string) string {
  165. // https://github.com/torvalds/linux/blob/v5.6/fs/isofs/dir.c#L29
  166. input = strings.ToLower(input)
  167. split := strings.Split(input, ".")
  168. version := "1"
  169. var filename, extension string
  170. if len(split) == 1 {
  171. filename = split[0]
  172. } else {
  173. filename = strings.Join(split[:len(split)-1], "_")
  174. extension = split[len(split)-1]
  175. }
  176. // enough characters for the `.ignition` extension
  177. extension = mangleD1String(extension, 8)
  178. maxRemainingFilenameLength := primaryVolumeFileIdentifierMaxLength - (1 + len(version))
  179. if len(extension) > 0 {
  180. maxRemainingFilenameLength -= (1 + len(extension))
  181. }
  182. filename = mangleD1String(filename, maxRemainingFilenameLength)
  183. if len(extension) > 0 {
  184. return filename + "." + extension + ";" + version
  185. }
  186. return filename + ";" + version
  187. }
  188. // See ECMA-119 7.6
  189. func mangleDirectoryName(input string) string {
  190. return mangleD1String(input, primaryVolumeDirectoryIdentifierMaxLength)
  191. }
  192. func mangleD1String(input string, maxCharacters int) string {
  193. // https://github.com/torvalds/linux/blob/v5.6/fs/isofs/dir.c#L29
  194. input = strings.ToLower(input)
  195. var mangledString string
  196. for i := 0; i < len(input) && i < maxCharacters; i++ {
  197. r := rune(input[i])
  198. if strings.ContainsRune(d1Characters, r) {
  199. mangledString += string(r)
  200. } else {
  201. mangledString += "_"
  202. }
  203. }
  204. return mangledString
  205. }
  206. // calculateDirChildrenSectors calculates the total mashalled size of all DirectoryEntries
  207. // within a directory. The size of each entry depends of the length of the filename.
  208. func calculateDirChildrenSectors(path string) (uint32, error) {
  209. contents, err := os.ReadDir(path)
  210. if err != nil {
  211. return 0, err
  212. }
  213. var sectors uint32
  214. var currentSectorOccupied uint32 = 68 // the 0x00 and 0x01 entries
  215. for _, c := range contents {
  216. identifierLen := len(c.Name())
  217. idPaddingLen := (identifierLen + 1) % 2
  218. entryLength := uint32(33 + identifierLen + idPaddingLen)
  219. if currentSectorOccupied+entryLength > sectorSize {
  220. sectors++
  221. currentSectorOccupied = entryLength
  222. } else {
  223. currentSectorOccupied += entryLength
  224. }
  225. }
  226. if currentSectorOccupied > 0 {
  227. sectors++
  228. }
  229. return sectors, nil
  230. }
  231. func fileLengthToSectors(l uint32) uint32 {
  232. if (l % sectorSize) == 0 {
  233. return l / sectorSize
  234. }
  235. return (l / sectorSize) + 1
  236. }
  237. type writeContext struct {
  238. stagingDir string
  239. timestamp RecordingTimestamp
  240. freeSectorPointer uint32
  241. }
  242. func (wc *writeContext) allocateSectors(n uint32) uint32 {
  243. return atomic.AddUint32(&wc.freeSectorPointer, n) - n
  244. }
  245. func (wc *writeContext) createDEForRoot() (*DirectoryEntry, error) {
  246. extentLengthInSectors, err := calculateDirChildrenSectors(wc.stagingDir)
  247. if err != nil {
  248. return nil, err
  249. }
  250. extentLocation := wc.allocateSectors(extentLengthInSectors)
  251. de := &DirectoryEntry{
  252. ExtendedAtributeRecordLength: 0,
  253. ExtentLocation: int32(extentLocation),
  254. ExtentLength: uint32(extentLengthInSectors * sectorSize),
  255. RecordingDateTime: wc.timestamp,
  256. FileFlags: dirFlagDir,
  257. FileUnitSize: 0, // 0 for non-interleaved write
  258. InterleaveGap: 0, // not interleaved
  259. VolumeSequenceNumber: 1, // we only have one volume
  260. Identifier: string([]byte{0}),
  261. SystemUse: []byte{},
  262. }
  263. return de, nil
  264. }
  265. type itemToWrite struct {
  266. isDirectory bool
  267. dirPath string
  268. ownEntry *DirectoryEntry
  269. parentEntery *DirectoryEntry
  270. childrenEntries []*DirectoryEntry
  271. targetSector uint32
  272. }
  273. // scanDirectory reads the directory's contents and adds them to the queue, as well as stores all their DirectoryEntries in the item,
  274. // because we'll need them to write this item's descriptor.
  275. func (wc *writeContext) scanDirectory(item *itemToWrite, dirPath string, ownEntry *DirectoryEntry, parentEntery *DirectoryEntry, targetSector uint32) (*list.List, error) {
  276. contents, err := os.ReadDir(dirPath)
  277. if err != nil {
  278. return nil, err
  279. }
  280. itemsToWrite := list.New()
  281. for _, c := range contents {
  282. var (
  283. fileFlags byte
  284. extentLengthInSectors uint32
  285. extentLength uint32
  286. )
  287. if c.IsDir() {
  288. extentLengthInSectors, err = calculateDirChildrenSectors(path.Join(dirPath, c.Name()))
  289. if err != nil {
  290. return nil, err
  291. }
  292. fileFlags = dirFlagDir
  293. extentLength = extentLengthInSectors * sectorSize
  294. } else {
  295. fileinfo, err := c.Info()
  296. if err != nil {
  297. return nil, err
  298. }
  299. if fileinfo.Size() > int64(math.MaxUint32) {
  300. return nil, ErrFileTooLarge
  301. }
  302. extentLength = uint32(fileinfo.Size())
  303. extentLengthInSectors = fileLengthToSectors(extentLength)
  304. fileFlags = 0
  305. }
  306. extentLocation := wc.allocateSectors(extentLengthInSectors)
  307. de := &DirectoryEntry{
  308. ExtendedAtributeRecordLength: 0,
  309. ExtentLocation: int32(extentLocation),
  310. ExtentLength: uint32(extentLength),
  311. RecordingDateTime: wc.timestamp,
  312. FileFlags: fileFlags,
  313. FileUnitSize: 0, // 0 for non-interleaved write
  314. InterleaveGap: 0, // not interleaved
  315. VolumeSequenceNumber: 1, // we only have one volume
  316. Identifier: c.Name(),
  317. SystemUse: []byte{},
  318. }
  319. // Add this child's descriptor to the currently scanned directory's list of children,
  320. // so that later we can use it for writing the current item.
  321. if item.childrenEntries == nil {
  322. item.childrenEntries = []*DirectoryEntry{de}
  323. } else {
  324. item.childrenEntries = append(item.childrenEntries, de)
  325. }
  326. // queue this child for processing
  327. itemsToWrite.PushBack(itemToWrite{
  328. isDirectory: c.IsDir(),
  329. dirPath: path.Join(dirPath, c.Name()),
  330. ownEntry: de,
  331. parentEntery: ownEntry,
  332. targetSector: uint32(de.ExtentLocation),
  333. })
  334. }
  335. return itemsToWrite, nil
  336. }
  337. // processDirectory writes a given directory item to the destination sectors
  338. func processDirectory(w io.Writer, children []*DirectoryEntry, ownEntry *DirectoryEntry, parentEntry *DirectoryEntry) error {
  339. var currentOffset uint32
  340. currentDE := ownEntry.Clone()
  341. currentDE.Identifier = string([]byte{0})
  342. parentDE := parentEntry.Clone()
  343. parentDE.Identifier = string([]byte{1})
  344. currentDEData, err := currentDE.MarshalBinary()
  345. if err != nil {
  346. return err
  347. }
  348. parentDEData, err := parentDE.MarshalBinary()
  349. if err != nil {
  350. return err
  351. }
  352. n, err := w.Write(currentDEData)
  353. if err != nil {
  354. return err
  355. }
  356. currentOffset += uint32(n)
  357. n, err = w.Write(parentDEData)
  358. if err != nil {
  359. return err
  360. }
  361. currentOffset += uint32(n)
  362. for _, childDescriptor := range children {
  363. data, err := childDescriptor.MarshalBinary()
  364. if err != nil {
  365. return err
  366. }
  367. remainingSectorSpace := sectorSize - (currentOffset % sectorSize)
  368. if remainingSectorSpace < uint32(len(data)) {
  369. // ECMA-119 6.8.1.1 If the body of the next descriptor won't fit into the sector,
  370. // we fill the rest of space with zeros and skip to the next sector.
  371. zeros := bytes.Repeat([]byte{0}, int(remainingSectorSpace))
  372. _, err = w.Write(zeros)
  373. if err != nil {
  374. return err
  375. }
  376. // skip to the next sector
  377. currentOffset = 0
  378. }
  379. n, err = w.Write(data)
  380. if err != nil {
  381. return err
  382. }
  383. currentOffset += uint32(n)
  384. }
  385. // fill with zeros to the end of the sector
  386. remainingSectorSpace := sectorSize - (currentOffset % sectorSize)
  387. if remainingSectorSpace != 0 {
  388. zeros := bytes.Repeat([]byte{0}, int(remainingSectorSpace))
  389. _, err = w.Write(zeros)
  390. if err != nil {
  391. return err
  392. }
  393. }
  394. return nil
  395. }
  396. func processFile(w io.Writer, dirPath string) error {
  397. f, err := os.Open(dirPath)
  398. if err != nil {
  399. return err
  400. }
  401. defer f.Close()
  402. fileinfo, err := f.Stat()
  403. if err != nil {
  404. return err
  405. }
  406. if fileinfo.Size() > int64(math.MaxUint32) {
  407. return ErrFileTooLarge
  408. }
  409. buffer := make([]byte, sectorSize)
  410. for bytesLeft := uint32(fileinfo.Size()); bytesLeft > 0; {
  411. var toRead uint32
  412. if bytesLeft < sectorSize {
  413. toRead = bytesLeft
  414. } else {
  415. toRead = sectorSize
  416. }
  417. if _, err = io.ReadAtLeast(f, buffer, int(toRead)); err != nil {
  418. return err
  419. }
  420. if _, err = w.Write(buffer); err != nil {
  421. return err
  422. }
  423. bytesLeft -= toRead
  424. }
  425. // We already write a whole sector-sized buffer, so there's need to fill with zeroes.
  426. return nil
  427. }
  428. // traverseStagingDir creates a new queue of items to write by traversing the staging directory
  429. func (wc *writeContext) traverseStagingDir(rootItem itemToWrite) (*list.List, error) {
  430. itemsToWrite := list.New()
  431. itemsToWrite.PushBack(rootItem)
  432. for item := itemsToWrite.Front(); item != nil; item = item.Next() {
  433. it := item.Value.(itemToWrite)
  434. if it.isDirectory {
  435. newItems, err := wc.scanDirectory(&it, it.dirPath, it.ownEntry, it.parentEntery, it.targetSector)
  436. if err != nil {
  437. relativePath := it.dirPath[len(wc.stagingDir):]
  438. return nil, fmt.Errorf("processing %s: %s", relativePath, err)
  439. }
  440. itemsToWrite.PushBackList(newItems)
  441. }
  442. item.Value = it
  443. }
  444. return itemsToWrite, nil
  445. }
  446. func writeAll(w io.Writer, itemsToWrite *list.List) error {
  447. for item := itemsToWrite.Front(); item != nil; item = item.Next() {
  448. it := item.Value.(itemToWrite)
  449. var err error
  450. if it.isDirectory {
  451. err = processDirectory(w, it.childrenEntries, it.ownEntry, it.parentEntery)
  452. } else {
  453. err = processFile(w, it.dirPath)
  454. }
  455. if err != nil {
  456. return err
  457. }
  458. }
  459. return nil
  460. }
  461. // WriteTo writes the image to the given WriterAt
  462. func (iw *ImageWriter) WriteTo(w io.Writer, volumeIdentifier string) error {
  463. now := time.Now()
  464. wc := writeContext{
  465. stagingDir: iw.stagingDir,
  466. timestamp: RecordingTimestamp{},
  467. freeSectorPointer: 18, // system area (16) + 2 volume descriptors
  468. }
  469. rootDE, err := wc.createDEForRoot()
  470. if err != nil {
  471. return fmt.Errorf("creating root directory descriptor: %s", err)
  472. }
  473. rootItem := itemToWrite{
  474. isDirectory: true,
  475. dirPath: wc.stagingDir,
  476. ownEntry: rootDE,
  477. parentEntery: rootDE,
  478. targetSector: uint32(rootDE.ExtentLocation),
  479. }
  480. itemsToWrite, err := wc.traverseStagingDir(rootItem)
  481. if err != nil {
  482. return fmt.Errorf("tranversing staging directory: %s", err)
  483. }
  484. pvd := volumeDescriptor{
  485. Header: volumeDescriptorHeader{
  486. Type: volumeTypePrimary,
  487. Identifier: standardIdentifierBytes,
  488. Version: 1,
  489. },
  490. Primary: &PrimaryVolumeDescriptorBody{
  491. SystemIdentifier: runtime.GOOS,
  492. VolumeIdentifier: volumeIdentifier,
  493. VolumeSpaceSize: int32(wc.freeSectorPointer),
  494. VolumeSetSize: 1,
  495. VolumeSequenceNumber: 1,
  496. LogicalBlockSize: int16(sectorSize),
  497. PathTableSize: 0,
  498. TypeLPathTableLoc: 0,
  499. OptTypeLPathTableLoc: 0,
  500. TypeMPathTableLoc: 0,
  501. OptTypeMPathTableLoc: 0,
  502. RootDirectoryEntry: rootDE,
  503. VolumeSetIdentifier: "",
  504. PublisherIdentifier: "",
  505. DataPreparerIdentifier: "",
  506. ApplicationIdentifier: "github.com/kdomanski/iso9660",
  507. CopyrightFileIdentifier: "",
  508. AbstractFileIdentifier: "",
  509. BibliographicFileIdentifier: "",
  510. VolumeCreationDateAndTime: VolumeDescriptorTimestampFromTime(now),
  511. VolumeModificationDateAndTime: VolumeDescriptorTimestampFromTime(now),
  512. VolumeExpirationDateAndTime: VolumeDescriptorTimestamp{},
  513. VolumeEffectiveDateAndTime: VolumeDescriptorTimestampFromTime(now),
  514. FileStructureVersion: 1,
  515. ApplicationUsed: [512]byte{},
  516. },
  517. }
  518. terminator := volumeDescriptor{
  519. Header: volumeDescriptorHeader{
  520. Type: volumeTypeTerminator,
  521. Identifier: standardIdentifierBytes,
  522. Version: 1,
  523. },
  524. }
  525. // write 16 sectors of zeroes
  526. zeroSector := bytes.Repeat([]byte{0}, int(sectorSize))
  527. for i := uint32(0); i < 16; i++ {
  528. if _, err = w.Write(zeroSector); err != nil {
  529. return err
  530. }
  531. }
  532. buffer, err := pvd.MarshalBinary()
  533. if err != nil {
  534. return err
  535. }
  536. if _, err = w.Write(buffer); err != nil {
  537. return err
  538. }
  539. if buffer, err = terminator.MarshalBinary(); err != nil {
  540. return err
  541. }
  542. if _, err = w.Write(buffer); err != nil {
  543. return err
  544. }
  545. if err = writeAll(w, itemsToWrite); err != nil {
  546. return fmt.Errorf("writing files: %s", err)
  547. }
  548. return nil
  549. }