Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / Microsoft / go-winio / backuptar / tar.go
1 // +build windows
2
3 package backuptar
4
5 import (
6         "encoding/base64"
7         "errors"
8         "fmt"
9         "io"
10         "io/ioutil"
11         "path/filepath"
12         "strconv"
13         "strings"
14         "syscall"
15         "time"
16
17         "github.com/Microsoft/go-winio"
18         "github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
19 )
20
21 const (
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
32 )
33
34 const (
35         hdrFileAttributes        = "fileattr"
36         hdrSecurityDescriptor    = "sd"
37         hdrRawSecurityDescriptor = "rawsd"
38         hdrMountPoint            = "mountpoint"
39 )
40
41 func writeZeroes(w io.Writer, count int64) error {
42         buf := make([]byte, 8192)
43         c := len(buf)
44         for i := int64(0); i < count; i += int64(c) {
45                 if int64(c) > count-i {
46                         c = int(count - i)
47                 }
48                 _, err := w.Write(buf[:c])
49                 if err != nil {
50                         return err
51                 }
52         }
53         return nil
54 }
55
56 func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
57         curOffset := int64(0)
58         for {
59                 bhdr, err := br.Next()
60                 if err == io.EOF {
61                         err = io.ErrUnexpectedEOF
62                 }
63                 if err != nil {
64                         return err
65                 }
66                 if bhdr.Id != winio.BackupSparseBlock {
67                         return fmt.Errorf("unexpected stream %d", bhdr.Id)
68                 }
69
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)
73                 if bhdr.Size == 0 {
74                         break
75                 }
76                 n, err := io.Copy(t, br)
77                 if err != nil {
78                         return err
79                 }
80                 curOffset = bhdr.Offset + n
81         }
82         return nil
83 }
84
85 // BasicInfoHeader creates a tar header from basic file information.
86 func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
87         hdr := &tar.Header{
88                 Name:         filepath.ToSlash(name),
89                 Size:         size,
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),
96         }
97         hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
98
99         if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
100                 hdr.Mode |= c_ISDIR
101                 hdr.Size = 0
102                 hdr.Typeflag = tar.TypeDir
103         }
104         return hdr
105 }
106
107 // WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
108 //
109 // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
110 //
111 // The additional Win32 metadata is:
112 //
113 // MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
114 //
115 // MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
116 //
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
123         for dataHdr == nil {
124                 bhdr, err := br.Next()
125                 if err == io.EOF {
126                         break
127                 }
128                 if err != nil {
129                         return err
130                 }
131                 switch bhdr.Id {
132                 case winio.BackupData:
133                         hdr.Mode |= c_ISREG
134                         dataHdr = bhdr
135                 case winio.BackupSecurity:
136                         sd, err := ioutil.ReadAll(br)
137                         if err != nil {
138                                 return err
139                         }
140                         hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
141
142                 case winio.BackupReparseData:
143                         hdr.Mode |= c_ISLNK
144                         hdr.Typeflag = tar.TypeSymlink
145                         reparseBuffer, err := ioutil.ReadAll(br)
146                         rp, err := winio.DecodeReparsePoint(reparseBuffer)
147                         if err != nil {
148                                 return err
149                         }
150                         if rp.IsMountPoint {
151                                 hdr.Winheaders[hdrMountPoint] = "1"
152                         }
153                         hdr.Linkname = rp.Target
154                 case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
155                         // ignore these streams
156                 default:
157                         return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
158                 }
159         }
160
161         err := t.WriteHeader(hdr)
162         if err != nil {
163                 return err
164         }
165
166         if dataHdr != nil {
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)
171                         }
172                         _, err = io.Copy(t, br)
173                         if err != nil {
174                                 return err
175                         }
176                 } else {
177                         err = copySparse(t, br)
178                         if err != nil {
179                                 return err
180                         }
181                 }
182         }
183
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.
187         for {
188                 bhdr, err := br.Next()
189                 if err == io.EOF {
190                         break
191                 }
192                 if err != nil {
193                         return err
194                 }
195                 switch bhdr.Id {
196                 case winio.BackupAlternateData:
197                         altName := bhdr.Name
198                         if strings.HasSuffix(altName, ":$DATA") {
199                                 altName = altName[:len(altName)-len(":$DATA")]
200                         }
201                         if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
202                                 hdr = &tar.Header{
203                                         Name:       name + altName,
204                                         Mode:       hdr.Mode,
205                                         Typeflag:   tar.TypeReg,
206                                         Size:       bhdr.Size,
207                                         ModTime:    hdr.ModTime,
208                                         AccessTime: hdr.AccessTime,
209                                         ChangeTime: hdr.ChangeTime,
210                                 }
211                                 err = t.WriteHeader(hdr)
212                                 if err != nil {
213                                         return err
214                                 }
215                                 _, err = io.Copy(t, br)
216                                 if err != nil {
217                                         return err
218                                 }
219
220                         } else {
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")
224                         }
225                 case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
226                         // ignore these streams
227                 default:
228                         return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
229                 }
230         }
231         return nil
232 }
233
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) {
237         name = hdr.Name
238         if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
239                 size = hdr.Size
240         }
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()),
246         }
247         if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
248                 attr, err := strconv.ParseUint(attrStr, 10, 32)
249                 if err != nil {
250                         return "", 0, nil, err
251                 }
252                 fileInfo.FileAttributes = uintptr(attr)
253         } else {
254                 if hdr.Typeflag == tar.TypeDir {
255                         fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
256                 }
257         }
258         return
259 }
260
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)
266         var sd []byte
267         var err error
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)
272                 if err != nil {
273                         return nil, err
274                 }
275         }
276         if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
277                 sd, err = base64.StdEncoding.DecodeString(sdraw)
278                 if err != nil {
279                         return nil, err
280                 }
281         }
282         if len(sd) != 0 {
283                 bhdr := winio.BackupHeader{
284                         Id:   winio.BackupSecurity,
285                         Size: int64(len(sd)),
286                 }
287                 err := bw.WriteHeader(&bhdr)
288                 if err != nil {
289                         return nil, err
290                 }
291                 _, err = bw.Write(sd)
292                 if err != nil {
293                         return nil, err
294                 }
295         }
296         if hdr.Typeflag == tar.TypeSymlink {
297                 _, isMountPoint := hdr.Winheaders[hdrMountPoint]
298                 rp := winio.ReparsePoint{
299                         Target:       filepath.FromSlash(hdr.Linkname),
300                         IsMountPoint: isMountPoint,
301                 }
302                 reparse := winio.EncodeReparsePoint(&rp)
303                 bhdr := winio.BackupHeader{
304                         Id:   winio.BackupReparseData,
305                         Size: int64(len(reparse)),
306                 }
307                 err := bw.WriteHeader(&bhdr)
308                 if err != nil {
309                         return nil, err
310                 }
311                 _, err = bw.Write(reparse)
312                 if err != nil {
313                         return nil, err
314                 }
315         }
316         if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
317                 bhdr := winio.BackupHeader{
318                         Id:   winio.BackupData,
319                         Size: hdr.Size,
320                 }
321                 err := bw.WriteHeader(&bhdr)
322                 if err != nil {
323                         return nil, err
324                 }
325                 _, err = io.Copy(bw, t)
326                 if err != nil {
327                         return nil, err
328                 }
329         }
330         // Copy all the alternate data streams and return the next non-ADS header.
331         for {
332                 ahdr, err := t.Next()
333                 if err != nil {
334                         return nil, err
335                 }
336                 if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
337                         return ahdr, nil
338                 }
339                 bhdr := winio.BackupHeader{
340                         Id:   winio.BackupAlternateData,
341                         Size: ahdr.Size,
342                         Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
343                 }
344                 err = bw.WriteHeader(&bhdr)
345                 if err != nil {
346                         return nil, err
347                 }
348                 _, err = io.Copy(bw, t)
349                 if err != nil {
350                         return nil, err
351                 }
352         }
353 }