tar.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // +build windows
  2. package backuptar
  3. import (
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "syscall"
  13. "time"
  14. "github.com/Microsoft/go-winio"
  15. "github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
  16. )
  17. const (
  18. c_ISUID = 04000 // Set uid
  19. c_ISGID = 02000 // Set gid
  20. c_ISVTX = 01000 // Save text (sticky bit)
  21. c_ISDIR = 040000 // Directory
  22. c_ISFIFO = 010000 // FIFO
  23. c_ISREG = 0100000 // Regular file
  24. c_ISLNK = 0120000 // Symbolic link
  25. c_ISBLK = 060000 // Block special file
  26. c_ISCHR = 020000 // Character special file
  27. c_ISSOCK = 0140000 // Socket
  28. )
  29. const (
  30. hdrFileAttributes = "fileattr"
  31. hdrSecurityDescriptor = "sd"
  32. hdrRawSecurityDescriptor = "rawsd"
  33. hdrMountPoint = "mountpoint"
  34. )
  35. func writeZeroes(w io.Writer, count int64) error {
  36. buf := make([]byte, 8192)
  37. c := len(buf)
  38. for i := int64(0); i < count; i += int64(c) {
  39. if int64(c) > count-i {
  40. c = int(count - i)
  41. }
  42. _, err := w.Write(buf[:c])
  43. if err != nil {
  44. return err
  45. }
  46. }
  47. return nil
  48. }
  49. func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
  50. curOffset := int64(0)
  51. for {
  52. bhdr, err := br.Next()
  53. if err == io.EOF {
  54. err = io.ErrUnexpectedEOF
  55. }
  56. if err != nil {
  57. return err
  58. }
  59. if bhdr.Id != winio.BackupSparseBlock {
  60. return fmt.Errorf("unexpected stream %d", bhdr.Id)
  61. }
  62. // archive/tar does not support writing sparse files
  63. // so just write zeroes to catch up to the current offset.
  64. err = writeZeroes(t, bhdr.Offset-curOffset)
  65. if bhdr.Size == 0 {
  66. break
  67. }
  68. n, err := io.Copy(t, br)
  69. if err != nil {
  70. return err
  71. }
  72. curOffset = bhdr.Offset + n
  73. }
  74. return nil
  75. }
  76. // BasicInfoHeader creates a tar header from basic file information.
  77. func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
  78. hdr := &tar.Header{
  79. Name: filepath.ToSlash(name),
  80. Size: size,
  81. Typeflag: tar.TypeReg,
  82. ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
  83. ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
  84. AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
  85. CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
  86. Winheaders: make(map[string]string),
  87. }
  88. hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
  89. if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
  90. hdr.Mode |= c_ISDIR
  91. hdr.Size = 0
  92. hdr.Typeflag = tar.TypeDir
  93. }
  94. return hdr
  95. }
  96. // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
  97. //
  98. // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
  99. //
  100. // The additional Win32 metadata is:
  101. //
  102. // MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
  103. //
  104. // MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
  105. //
  106. // MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
  107. func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
  108. name = filepath.ToSlash(name)
  109. hdr := BasicInfoHeader(name, size, fileInfo)
  110. br := winio.NewBackupStreamReader(r)
  111. var dataHdr *winio.BackupHeader
  112. for dataHdr == nil {
  113. bhdr, err := br.Next()
  114. if err == io.EOF {
  115. break
  116. }
  117. if err != nil {
  118. return err
  119. }
  120. switch bhdr.Id {
  121. case winio.BackupData:
  122. hdr.Mode |= c_ISREG
  123. dataHdr = bhdr
  124. case winio.BackupSecurity:
  125. sd, err := ioutil.ReadAll(br)
  126. if err != nil {
  127. return err
  128. }
  129. hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
  130. case winio.BackupReparseData:
  131. hdr.Mode |= c_ISLNK
  132. hdr.Typeflag = tar.TypeSymlink
  133. reparseBuffer, err := ioutil.ReadAll(br)
  134. rp, err := winio.DecodeReparsePoint(reparseBuffer)
  135. if err != nil {
  136. return err
  137. }
  138. if rp.IsMountPoint {
  139. hdr.Winheaders[hdrMountPoint] = "1"
  140. }
  141. hdr.Linkname = rp.Target
  142. case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
  143. // ignore these streams
  144. default:
  145. return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
  146. }
  147. }
  148. err := t.WriteHeader(hdr)
  149. if err != nil {
  150. return err
  151. }
  152. if dataHdr != nil {
  153. // A data stream was found. Copy the data.
  154. if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
  155. if size != dataHdr.Size {
  156. return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
  157. }
  158. _, err = io.Copy(t, br)
  159. if err != nil {
  160. return err
  161. }
  162. } else {
  163. err = copySparse(t, br)
  164. if err != nil {
  165. return err
  166. }
  167. }
  168. }
  169. // Look for streams after the data stream. The only ones we handle are alternate data streams.
  170. // Other streams may have metadata that could be serialized, but the tar header has already
  171. // been written. In practice, this means that we don't get EA or TXF metadata.
  172. for {
  173. bhdr, err := br.Next()
  174. if err == io.EOF {
  175. break
  176. }
  177. if err != nil {
  178. return err
  179. }
  180. switch bhdr.Id {
  181. case winio.BackupAlternateData:
  182. altName := bhdr.Name
  183. if strings.HasSuffix(altName, ":$DATA") {
  184. altName = altName[:len(altName)-len(":$DATA")]
  185. }
  186. if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
  187. hdr = &tar.Header{
  188. Name: name + altName,
  189. Mode: hdr.Mode,
  190. Typeflag: tar.TypeReg,
  191. Size: bhdr.Size,
  192. ModTime: hdr.ModTime,
  193. AccessTime: hdr.AccessTime,
  194. ChangeTime: hdr.ChangeTime,
  195. }
  196. err = t.WriteHeader(hdr)
  197. if err != nil {
  198. return err
  199. }
  200. _, err = io.Copy(t, br)
  201. if err != nil {
  202. return err
  203. }
  204. } else {
  205. // Unsupported for now, since the size of the alternate stream is not present
  206. // in the backup stream until after the data has been read.
  207. return errors.New("tar of sparse alternate data streams is unsupported")
  208. }
  209. case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
  210. // ignore these streams
  211. default:
  212. return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
  213. }
  214. }
  215. return nil
  216. }
  217. // FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
  218. // WriteTarFileFromBackupStream.
  219. func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
  220. name = hdr.Name
  221. if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
  222. size = hdr.Size
  223. }
  224. fileInfo = &winio.FileBasicInfo{
  225. LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
  226. LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
  227. ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
  228. CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
  229. }
  230. if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
  231. attr, err := strconv.ParseUint(attrStr, 10, 32)
  232. if err != nil {
  233. return "", 0, nil, err
  234. }
  235. fileInfo.FileAttributes = uintptr(attr)
  236. } else {
  237. if hdr.Typeflag == tar.TypeDir {
  238. fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
  239. }
  240. }
  241. return
  242. }
  243. // WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
  244. // tar file entries in order to collect all the alternate data streams for the file, it returns the next
  245. // tar file that was not processed, or io.EOF is there are no more.
  246. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
  247. bw := winio.NewBackupStreamWriter(w)
  248. var sd []byte
  249. var err error
  250. // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
  251. // by this library will have raw binary for the security descriptor.
  252. if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
  253. sd, err = winio.SddlToSecurityDescriptor(sddl)
  254. if err != nil {
  255. return nil, err
  256. }
  257. }
  258. if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
  259. sd, err = base64.StdEncoding.DecodeString(sdraw)
  260. if err != nil {
  261. return nil, err
  262. }
  263. }
  264. if len(sd) != 0 {
  265. bhdr := winio.BackupHeader{
  266. Id: winio.BackupSecurity,
  267. Size: int64(len(sd)),
  268. }
  269. err := bw.WriteHeader(&bhdr)
  270. if err != nil {
  271. return nil, err
  272. }
  273. _, err = bw.Write(sd)
  274. if err != nil {
  275. return nil, err
  276. }
  277. }
  278. if hdr.Typeflag == tar.TypeSymlink {
  279. _, isMountPoint := hdr.Winheaders[hdrMountPoint]
  280. rp := winio.ReparsePoint{
  281. Target: filepath.FromSlash(hdr.Linkname),
  282. IsMountPoint: isMountPoint,
  283. }
  284. reparse := winio.EncodeReparsePoint(&rp)
  285. bhdr := winio.BackupHeader{
  286. Id: winio.BackupReparseData,
  287. Size: int64(len(reparse)),
  288. }
  289. err := bw.WriteHeader(&bhdr)
  290. if err != nil {
  291. return nil, err
  292. }
  293. _, err = bw.Write(reparse)
  294. if err != nil {
  295. return nil, err
  296. }
  297. }
  298. if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
  299. bhdr := winio.BackupHeader{
  300. Id: winio.BackupData,
  301. Size: hdr.Size,
  302. }
  303. err := bw.WriteHeader(&bhdr)
  304. if err != nil {
  305. return nil, err
  306. }
  307. _, err = io.Copy(bw, t)
  308. if err != nil {
  309. return nil, err
  310. }
  311. }
  312. // Copy all the alternate data streams and return the next non-ADS header.
  313. for {
  314. ahdr, err := t.Next()
  315. if err != nil {
  316. return nil, err
  317. }
  318. if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
  319. return ahdr, nil
  320. }
  321. bhdr := winio.BackupHeader{
  322. Id: winio.BackupAlternateData,
  323. Size: ahdr.Size,
  324. Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
  325. }
  326. err = bw.WriteHeader(&bhdr)
  327. if err != nil {
  328. return nil, err
  329. }
  330. _, err = io.Copy(bw, t)
  331. if err != nil {
  332. return nil, err
  333. }
  334. }
  335. }