17 "github.com/Microsoft/go-winio"
18 "github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
22 c_ISUID = 04000 // Set uid
23 c_ISGID = 02000 // Set gid
24 c_ISVTX = 01000 // Save text (sticky bit)
25 c_ISDIR = 040000 // Directory
26 c_ISFIFO = 010000 // FIFO
27 c_ISREG = 0100000 // Regular file
28 c_ISLNK = 0120000 // Symbolic link
29 c_ISBLK = 060000 // Block special file
30 c_ISCHR = 020000 // Character special file
31 c_ISSOCK = 0140000 // Socket
35 hdrFileAttributes = "fileattr"
36 hdrSecurityDescriptor = "sd"
37 hdrRawSecurityDescriptor = "rawsd"
38 hdrMountPoint = "mountpoint"
41 func writeZeroes(w io.Writer, count int64) error {
42 buf := make([]byte, 8192)
44 for i := int64(0); i < count; i += int64(c) {
45 if int64(c) > count-i {
48 _, err := w.Write(buf[:c])
56 func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
59 bhdr, err := br.Next()
61 err = io.ErrUnexpectedEOF
66 if bhdr.Id != winio.BackupSparseBlock {
67 return fmt.Errorf("unexpected stream %d", bhdr.Id)
70 // archive/tar does not support writing sparse files
71 // so just write zeroes to catch up to the current offset.
72 err = writeZeroes(t, bhdr.Offset-curOffset)
76 n, err := io.Copy(t, br)
80 curOffset = bhdr.Offset + n
85 // BasicInfoHeader creates a tar header from basic file information.
86 func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
88 Name: filepath.ToSlash(name),
90 Typeflag: tar.TypeReg,
91 ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
92 ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
93 AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
94 CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
95 Winheaders: make(map[string]string),
97 hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
99 if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
102 hdr.Typeflag = tar.TypeDir
107 // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
109 // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
111 // The additional Win32 metadata is:
113 // MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
115 // MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
117 // MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
118 func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
119 name = filepath.ToSlash(name)
120 hdr := BasicInfoHeader(name, size, fileInfo)
121 br := winio.NewBackupStreamReader(r)
122 var dataHdr *winio.BackupHeader
124 bhdr, err := br.Next()
132 case winio.BackupData:
135 case winio.BackupSecurity:
136 sd, err := ioutil.ReadAll(br)
140 hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
142 case winio.BackupReparseData:
144 hdr.Typeflag = tar.TypeSymlink
145 reparseBuffer, err := ioutil.ReadAll(br)
146 rp, err := winio.DecodeReparsePoint(reparseBuffer)
151 hdr.Winheaders[hdrMountPoint] = "1"
153 hdr.Linkname = rp.Target
154 case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
155 // ignore these streams
157 return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
161 err := t.WriteHeader(hdr)
167 // A data stream was found. Copy the data.
168 if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
169 if size != dataHdr.Size {
170 return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
172 _, err = io.Copy(t, br)
177 err = copySparse(t, br)
184 // Look for streams after the data stream. The only ones we handle are alternate data streams.
185 // Other streams may have metadata that could be serialized, but the tar header has already
186 // been written. In practice, this means that we don't get EA or TXF metadata.
188 bhdr, err := br.Next()
196 case winio.BackupAlternateData:
198 if strings.HasSuffix(altName, ":$DATA") {
199 altName = altName[:len(altName)-len(":$DATA")]
201 if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
203 Name: name + altName,
205 Typeflag: tar.TypeReg,
207 ModTime: hdr.ModTime,
208 AccessTime: hdr.AccessTime,
209 ChangeTime: hdr.ChangeTime,
211 err = t.WriteHeader(hdr)
215 _, err = io.Copy(t, br)
221 // Unsupported for now, since the size of the alternate stream is not present
222 // in the backup stream until after the data has been read.
223 return errors.New("tar of sparse alternate data streams is unsupported")
225 case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
226 // ignore these streams
228 return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
234 // FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
235 // WriteTarFileFromBackupStream.
236 func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
238 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
241 fileInfo = &winio.FileBasicInfo{
242 LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
243 LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
244 ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
245 CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
247 if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
248 attr, err := strconv.ParseUint(attrStr, 10, 32)
250 return "", 0, nil, err
252 fileInfo.FileAttributes = uintptr(attr)
254 if hdr.Typeflag == tar.TypeDir {
255 fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
261 // WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
262 // tar file entries in order to collect all the alternate data streams for the file, it returns the next
263 // tar file that was not processed, or io.EOF is there are no more.
264 func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
265 bw := winio.NewBackupStreamWriter(w)
268 // Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
269 // by this library will have raw binary for the security descriptor.
270 if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
271 sd, err = winio.SddlToSecurityDescriptor(sddl)
276 if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
277 sd, err = base64.StdEncoding.DecodeString(sdraw)
283 bhdr := winio.BackupHeader{
284 Id: winio.BackupSecurity,
285 Size: int64(len(sd)),
287 err := bw.WriteHeader(&bhdr)
291 _, err = bw.Write(sd)
296 if hdr.Typeflag == tar.TypeSymlink {
297 _, isMountPoint := hdr.Winheaders[hdrMountPoint]
298 rp := winio.ReparsePoint{
299 Target: filepath.FromSlash(hdr.Linkname),
300 IsMountPoint: isMountPoint,
302 reparse := winio.EncodeReparsePoint(&rp)
303 bhdr := winio.BackupHeader{
304 Id: winio.BackupReparseData,
305 Size: int64(len(reparse)),
307 err := bw.WriteHeader(&bhdr)
311 _, err = bw.Write(reparse)
316 if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
317 bhdr := winio.BackupHeader{
318 Id: winio.BackupData,
321 err := bw.WriteHeader(&bhdr)
325 _, err = io.Copy(bw, t)
330 // Copy all the alternate data streams and return the next non-ADS header.
332 ahdr, err := t.Next()
336 if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
339 bhdr := winio.BackupHeader{
340 Id: winio.BackupAlternateData,
342 Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
344 err = bw.WriteHeader(&bhdr)
348 _, err = io.Copy(bw, t)