legacy.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. package hcsshim
  2. import (
  3. "bufio"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "syscall"
  12. "github.com/Microsoft/go-winio"
  13. )
  14. var errorIterationCanceled = errors.New("")
  15. func openFileOrDir(path string, mode uint32, createDisposition uint32) (file *os.File, err error) {
  16. return winio.OpenForBackup(path, mode, syscall.FILE_SHARE_READ, createDisposition)
  17. }
  18. func makeLongAbsPath(path string) (string, error) {
  19. if strings.HasPrefix(path, `\\?\`) || strings.HasPrefix(path, `\\.\`) {
  20. return path, nil
  21. }
  22. if !filepath.IsAbs(path) {
  23. absPath, err := filepath.Abs(path)
  24. if err != nil {
  25. return "", err
  26. }
  27. path = absPath
  28. }
  29. if strings.HasPrefix(path, `\\`) {
  30. return `\\?\UNC\` + path[2:], nil
  31. }
  32. return `\\?\` + path, nil
  33. }
  34. type fileEntry struct {
  35. path string
  36. fi os.FileInfo
  37. err error
  38. }
  39. type legacyLayerReader struct {
  40. root string
  41. result chan *fileEntry
  42. proceed chan bool
  43. currentFile *os.File
  44. backupReader *winio.BackupFileReader
  45. isTP4Format bool
  46. }
  47. // newLegacyLayerReader returns a new LayerReader that can read the Windows
  48. // TP4 transport format from disk.
  49. func newLegacyLayerReader(root string) *legacyLayerReader {
  50. r := &legacyLayerReader{
  51. root: root,
  52. result: make(chan *fileEntry),
  53. proceed: make(chan bool),
  54. isTP4Format: IsTP4(),
  55. }
  56. go r.walk()
  57. return r
  58. }
  59. func readTombstones(path string) (map[string]([]string), error) {
  60. tf, err := os.Open(filepath.Join(path, "tombstones.txt"))
  61. if err != nil {
  62. return nil, err
  63. }
  64. defer tf.Close()
  65. s := bufio.NewScanner(tf)
  66. if !s.Scan() || s.Text() != "\xef\xbb\xbfVersion 1.0" {
  67. return nil, errors.New("Invalid tombstones file")
  68. }
  69. ts := make(map[string]([]string))
  70. for s.Scan() {
  71. t := filepath.Join("Files", s.Text()[1:]) // skip leading `\`
  72. dir := filepath.Dir(t)
  73. ts[dir] = append(ts[dir], t)
  74. }
  75. if err = s.Err(); err != nil {
  76. return nil, err
  77. }
  78. return ts, nil
  79. }
  80. func (r *legacyLayerReader) walkUntilCancelled() error {
  81. root, err := makeLongAbsPath(r.root)
  82. if err != nil {
  83. return err
  84. }
  85. r.root = root
  86. ts, err := readTombstones(r.root)
  87. if err != nil {
  88. return err
  89. }
  90. err = filepath.Walk(r.root, func(path string, info os.FileInfo, err error) error {
  91. if err != nil {
  92. return err
  93. }
  94. if path == r.root || path == filepath.Join(r.root, "tombstones.txt") || strings.HasSuffix(path, ".$wcidirs$") {
  95. return nil
  96. }
  97. r.result <- &fileEntry{path, info, nil}
  98. if !<-r.proceed {
  99. return errorIterationCanceled
  100. }
  101. // List all the tombstones.
  102. if info.IsDir() {
  103. relPath, err := filepath.Rel(r.root, path)
  104. if err != nil {
  105. return err
  106. }
  107. if dts, ok := ts[relPath]; ok {
  108. for _, t := range dts {
  109. r.result <- &fileEntry{filepath.Join(r.root, t), nil, nil}
  110. if !<-r.proceed {
  111. return errorIterationCanceled
  112. }
  113. }
  114. }
  115. }
  116. return nil
  117. })
  118. if err == errorIterationCanceled {
  119. return nil
  120. }
  121. if err == nil {
  122. return io.EOF
  123. }
  124. return err
  125. }
  126. func (r *legacyLayerReader) walk() {
  127. defer close(r.result)
  128. if !<-r.proceed {
  129. return
  130. }
  131. err := r.walkUntilCancelled()
  132. if err != nil {
  133. for {
  134. r.result <- &fileEntry{err: err}
  135. if !<-r.proceed {
  136. return
  137. }
  138. }
  139. }
  140. }
  141. func (r *legacyLayerReader) reset() {
  142. if r.backupReader != nil {
  143. r.backupReader.Close()
  144. r.backupReader = nil
  145. }
  146. if r.currentFile != nil {
  147. r.currentFile.Close()
  148. r.currentFile = nil
  149. }
  150. }
  151. func findBackupStreamSize(r io.Reader) (int64, error) {
  152. br := winio.NewBackupStreamReader(r)
  153. for {
  154. hdr, err := br.Next()
  155. if err != nil {
  156. if err == io.EOF {
  157. err = nil
  158. }
  159. return 0, err
  160. }
  161. if hdr.Id == winio.BackupData {
  162. return hdr.Size, nil
  163. }
  164. }
  165. }
  166. func (r *legacyLayerReader) Next() (path string, size int64, fileInfo *winio.FileBasicInfo, err error) {
  167. r.reset()
  168. r.proceed <- true
  169. fe := <-r.result
  170. if fe == nil {
  171. err = errors.New("LegacyLayerReader closed")
  172. return
  173. }
  174. if fe.err != nil {
  175. err = fe.err
  176. return
  177. }
  178. path, err = filepath.Rel(r.root, fe.path)
  179. if err != nil {
  180. return
  181. }
  182. if fe.fi == nil {
  183. // This is a tombstone. Return a nil fileInfo.
  184. return
  185. }
  186. if fe.fi.IsDir() && strings.HasPrefix(path, `Files\`) {
  187. fe.path += ".$wcidirs$"
  188. }
  189. f, err := openFileOrDir(fe.path, syscall.GENERIC_READ, syscall.OPEN_EXISTING)
  190. if err != nil {
  191. return
  192. }
  193. defer func() {
  194. if f != nil {
  195. f.Close()
  196. }
  197. }()
  198. fileInfo, err = winio.GetFileBasicInfo(f)
  199. if err != nil {
  200. return
  201. }
  202. if !strings.HasPrefix(path, `Files\`) {
  203. size = fe.fi.Size()
  204. r.backupReader = winio.NewBackupFileReader(f, false)
  205. if path == "Hives" || path == "Files" {
  206. // The Hives directory has a non-deterministic file time because of the
  207. // nature of the import process. Use the times from System_Delta.
  208. var g *os.File
  209. g, err = os.Open(filepath.Join(r.root, `Hives\System_Delta`))
  210. if err != nil {
  211. return
  212. }
  213. attr := fileInfo.FileAttributes
  214. fileInfo, err = winio.GetFileBasicInfo(g)
  215. g.Close()
  216. if err != nil {
  217. return
  218. }
  219. fileInfo.FileAttributes = attr
  220. }
  221. // The creation time and access time get reset for files outside of the Files path.
  222. fileInfo.CreationTime = fileInfo.LastWriteTime
  223. fileInfo.LastAccessTime = fileInfo.LastWriteTime
  224. } else {
  225. beginning := int64(0)
  226. if !r.isTP4Format {
  227. // In TP5, the file attributes were added before the backup stream
  228. var attr uint32
  229. err = binary.Read(f, binary.LittleEndian, &attr)
  230. if err != nil {
  231. return
  232. }
  233. fileInfo.FileAttributes = uintptr(attr)
  234. beginning = 4
  235. }
  236. // Find the accurate file size.
  237. if !fe.fi.IsDir() {
  238. size, err = findBackupStreamSize(f)
  239. if err != nil {
  240. err = &os.PathError{Op: "findBackupStreamSize", Path: fe.path, Err: err}
  241. return
  242. }
  243. }
  244. // Return back to the beginning of the backup stream.
  245. _, err = f.Seek(beginning, 0)
  246. if err != nil {
  247. return
  248. }
  249. }
  250. r.currentFile = f
  251. f = nil
  252. return
  253. }
  254. func (r *legacyLayerReader) Read(b []byte) (int, error) {
  255. if r.backupReader == nil {
  256. if r.currentFile == nil {
  257. return 0, io.EOF
  258. }
  259. return r.currentFile.Read(b)
  260. }
  261. return r.backupReader.Read(b)
  262. }
  263. func (r *legacyLayerReader) Close() error {
  264. r.proceed <- false
  265. <-r.result
  266. r.reset()
  267. return nil
  268. }
  269. type legacyLayerWriter struct {
  270. root string
  271. currentFile *os.File
  272. backupWriter *winio.BackupFileWriter
  273. tombstones []string
  274. isTP4Format bool
  275. pathFixed bool
  276. }
  277. // newLegacyLayerWriter returns a LayerWriter that can write the TP4 transport format
  278. // to disk.
  279. func newLegacyLayerWriter(root string) *legacyLayerWriter {
  280. return &legacyLayerWriter{
  281. root: root,
  282. isTP4Format: IsTP4(),
  283. }
  284. }
  285. func (w *legacyLayerWriter) init() error {
  286. if !w.pathFixed {
  287. path, err := makeLongAbsPath(w.root)
  288. if err != nil {
  289. return err
  290. }
  291. w.root = path
  292. w.pathFixed = true
  293. }
  294. return nil
  295. }
  296. func (w *legacyLayerWriter) reset() {
  297. if w.backupWriter != nil {
  298. w.backupWriter.Close()
  299. w.backupWriter = nil
  300. }
  301. if w.currentFile != nil {
  302. w.currentFile.Close()
  303. w.currentFile = nil
  304. }
  305. }
  306. func (w *legacyLayerWriter) Add(name string, fileInfo *winio.FileBasicInfo) error {
  307. w.reset()
  308. err := w.init()
  309. if err != nil {
  310. return err
  311. }
  312. path := filepath.Join(w.root, name)
  313. createDisposition := uint32(syscall.CREATE_NEW)
  314. if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
  315. err := os.Mkdir(path, 0)
  316. if err != nil {
  317. return err
  318. }
  319. path += ".$wcidirs$"
  320. }
  321. f, err := openFileOrDir(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, createDisposition)
  322. if err != nil {
  323. return err
  324. }
  325. defer func() {
  326. if f != nil {
  327. f.Close()
  328. os.Remove(path)
  329. }
  330. }()
  331. strippedFi := *fileInfo
  332. strippedFi.FileAttributes = 0
  333. err = winio.SetFileBasicInfo(f, &strippedFi)
  334. if err != nil {
  335. return err
  336. }
  337. if strings.HasPrefix(name, `Hives\`) {
  338. w.backupWriter = winio.NewBackupFileWriter(f, false)
  339. } else {
  340. if !w.isTP4Format {
  341. // In TP5, the file attributes were added to the header
  342. err = binary.Write(f, binary.LittleEndian, uint32(fileInfo.FileAttributes))
  343. if err != nil {
  344. return err
  345. }
  346. }
  347. }
  348. w.currentFile = f
  349. f = nil
  350. return nil
  351. }
  352. func (w *legacyLayerWriter) AddLink(name string, target string) error {
  353. return errors.New("hard links not supported with legacy writer")
  354. }
  355. func (w *legacyLayerWriter) Remove(name string) error {
  356. if !strings.HasPrefix(name, `Files\`) {
  357. return fmt.Errorf("invalid tombstone %s", name)
  358. }
  359. w.tombstones = append(w.tombstones, name[len(`Files\`):])
  360. return nil
  361. }
  362. func (w *legacyLayerWriter) Write(b []byte) (int, error) {
  363. if w.backupWriter == nil {
  364. if w.currentFile == nil {
  365. return 0, errors.New("closed")
  366. }
  367. return w.currentFile.Write(b)
  368. }
  369. return w.backupWriter.Write(b)
  370. }
  371. func (w *legacyLayerWriter) Close() error {
  372. w.reset()
  373. err := w.init()
  374. if err != nil {
  375. return err
  376. }
  377. tf, err := os.Create(filepath.Join(w.root, "tombstones.txt"))
  378. if err != nil {
  379. return err
  380. }
  381. defer tf.Close()
  382. _, err = tf.Write([]byte("\xef\xbb\xbfVersion 1.0\n"))
  383. if err != nil {
  384. return err
  385. }
  386. for _, t := range w.tombstones {
  387. _, err = tf.Write([]byte(filepath.Join(`\`, t) + "\n"))
  388. if err != nil {
  389. return err
  390. }
  391. }
  392. return nil
  393. }