func WriteTarFileFromBackupStream()

in backuptar/tar.go [127:319]


func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
	name = filepath.ToSlash(name)
	hdr := BasicInfoHeader(name, size, fileInfo)

	// If r can be seeked, then this function is two-pass: pass 1 collects the
	// tar header data, and pass 2 copies the data stream. If r cannot be
	// seeked, then some header data (in particular EAs) will be silently lost.
	var (
		restartPos int64
		err        error
	)
	sr, readTwice := r.(io.Seeker)
	if readTwice {
		if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
			readTwice = false
		}
	}

	br := winio.NewBackupStreamReader(r)
	var dataHdr *winio.BackupHeader
	for dataHdr == nil {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupData:
			hdr.Mode |= c_ISREG
			if !readTwice {
				dataHdr = bhdr
			}
		case winio.BackupSecurity:
			sd, err := ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)

		case winio.BackupReparseData:
			hdr.Mode |= c_ISLNK
			hdr.Typeflag = tar.TypeSymlink
			reparseBuffer, err := ioutil.ReadAll(br)
			rp, err := winio.DecodeReparsePoint(reparseBuffer)
			if err != nil {
				return err
			}
			if rp.IsMountPoint {
				hdr.PAXRecords[hdrMountPoint] = "1"
			}
			hdr.Linkname = rp.Target

		case winio.BackupEaData:
			eab, err := ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			eas, err := winio.DecodeExtendedAttributes(eab)
			if err != nil {
				return err
			}
			for _, ea := range eas {
				// Use base64 encoding for the binary value. Note that there
				// is no way to encode the EA's flags, since their use doesn't
				// make any sense for persisted EAs.
				hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
			}

		case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
		}
	}

	err = t.WriteHeader(hdr)
	if err != nil {
		return err
	}

	if readTwice {
		// Get back to the data stream.
		if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
			return err
		}
		for dataHdr == nil {
			bhdr, err := br.Next()
			if err == io.EOF {
				break
			}
			if err != nil {
				return err
			}
			if bhdr.Id == winio.BackupData {
				dataHdr = bhdr
			}
		}
	}

	// The logic for copying file contents is fairly complicated due to the need for handling sparse files,
	// and the weird ways they are represented by BackupRead. A normal file will always either have a data stream
	// with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also
	// be represented using a series of sparse block streams following the data stream. Additionally, the way sparse
	// files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described
	// in the list at the bottom of this block comment.
	//
	// Sparse files can be represented in four different ways, based on the specifics of the file.
	// - Size = 0:
	//     Previously: BackupRead yields no data stream and no sparse block streams.
	//     Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams.
	// - Size > 0, no allocated ranges:
	//     BackupRead yields a data stream with size = 0. Following is a single sparse block stream with
	//     size = 0 and offset = <file size>.
	// - Size > 0, one allocated range:
	//     BackupRead yields a data stream with size = <file size> containing the file contents. There are no
	//     sparse block streams. This is the case if you take a normal file with contents and simply set the
	//     sparse flag on it.
	// - Size > 0, multiple allocated ranges:
	//     BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated
	//     range of the file containing the range contents. Finally there is a sparse block stream with
	//     size = 0 and offset = <file size>.

	if dataHdr != nil {
		// A data stream was found. Copy the data.
		// We assume that we will either have a data stream size > 0 XOR have sparse block streams.
		if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 {
			if size != dataHdr.Size {
				return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
			}
			if _, err = io.Copy(t, br); err != nil {
				return fmt.Errorf("%s: copying contents from data stream: %s", name, err)
			}
		} else if size > 0 {
			// As of a recent OS change, BackupRead now returns a data stream for empty sparse files.
			// These files have no sparse block streams, so skip the copySparse call if file size = 0.
			if err = copySparse(t, br); err != nil {
				return fmt.Errorf("%s: copying contents from sparse block stream: %s", name, err)
			}
		}
	}

	// Look for streams after the data stream. The only ones we handle are alternate data streams.
	// Other streams may have metadata that could be serialized, but the tar header has already
	// been written. In practice, this means that we don't get EA or TXF metadata.
	for {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupAlternateData:
			altName := bhdr.Name
			if strings.HasSuffix(altName, ":$DATA") {
				altName = altName[:len(altName)-len(":$DATA")]
			}
			if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
				hdr = &tar.Header{
					Format:     hdr.Format,
					Name:       name + altName,
					Mode:       hdr.Mode,
					Typeflag:   tar.TypeReg,
					Size:       bhdr.Size,
					ModTime:    hdr.ModTime,
					AccessTime: hdr.AccessTime,
					ChangeTime: hdr.ChangeTime,
				}
				err = t.WriteHeader(hdr)
				if err != nil {
					return err
				}
				_, err = io.Copy(t, br)
				if err != nil {
					return err
				}

			} else {
				// Unsupported for now, since the size of the alternate stream is not present
				// in the backup stream until after the data has been read.
				return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name)
			}
		case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
		}
	}
	return nil
}