1 // +build linux windows
17 "github.com/docker/docker/pkg/archive"
18 "github.com/docker/docker/pkg/tarsum"
19 "github.com/pkg/errors"
20 "golang.org/x/net/context"
21 "golang.org/x/sync/errgroup"
24 type WriteToFunc func(context.Context, string, io.WriteCloser) error
26 type DiskWriterOpt struct {
27 AsyncDataCb WriteToFunc
28 SyncDataCb WriteToFunc
29 NotifyCb func(ChangeKind, string, os.FileInfo, error) error
32 type DiskWriter struct {
42 func NewDiskWriter(ctx context.Context, dest string, opt DiskWriterOpt) (*DiskWriter, error) {
43 if opt.SyncDataCb == nil && opt.AsyncDataCb == nil {
44 return nil, errors.New("no data callback specified")
46 if opt.SyncDataCb != nil && opt.AsyncDataCb != nil {
47 return nil, errors.New("can't specify both sync and async data callbacks")
50 ctx, cancel := context.WithCancel(ctx)
51 eg, ctx := errgroup.WithContext(ctx)
62 func (dw *DiskWriter) Wait(ctx context.Context) error {
66 func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
83 p = filepath.FromSlash(p)
85 destPath := filepath.Join(dw.dest, p)
87 if kind == ChangeKindDelete {
88 // todo: no need to validate if diff is trusted but is it always?
89 if err := os.RemoveAll(destPath); err != nil {
90 return errors.Wrapf(err, "failed to remove: %s", destPath)
92 if dw.opt.NotifyCb != nil {
93 if err := dw.opt.NotifyCb(kind, p, nil, nil); err != nil {
100 stat, ok := fi.Sys().(*Stat)
102 return errors.Errorf("%s invalid change without stat information", p)
106 oldFi, err := os.Lstat(destPath)
108 if os.IsNotExist(err) {
109 if kind != ChangeKindAdd {
110 return errors.Wrapf(err, "invalid addition: %s", destPath)
114 return errors.Wrapf(err, "failed to stat %s", destPath)
118 if oldFi != nil && fi.IsDir() && oldFi.IsDir() {
119 if err := rewriteMetadata(destPath, stat); err != nil {
120 return errors.Wrapf(err, "error setting dir metadata for %s", destPath)
127 newPath = filepath.Join(filepath.Dir(destPath), ".tmp."+nextSuffix())
130 isRegularFile := false
134 if err := os.Mkdir(newPath, fi.Mode()); err != nil {
135 return errors.Wrapf(err, "failed to create dir %s", newPath)
137 case fi.Mode()&os.ModeDevice != 0 || fi.Mode()&os.ModeNamedPipe != 0:
138 if err := handleTarTypeBlockCharFifo(newPath, stat); err != nil {
139 return errors.Wrapf(err, "failed to create device %s", newPath)
141 case fi.Mode()&os.ModeSymlink != 0:
142 if err := os.Symlink(stat.Linkname, newPath); err != nil {
143 return errors.Wrapf(err, "failed to symlink %s", newPath)
145 case stat.Linkname != "":
146 if err := os.Link(filepath.Join(dw.dest, stat.Linkname), newPath); err != nil {
147 return errors.Wrapf(err, "failed to link %s to %s", newPath, stat.Linkname)
151 file, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY, fi.Mode()) //todo: windows
153 return errors.Wrapf(err, "failed to create %s", newPath)
155 if dw.opt.SyncDataCb != nil {
156 if err := dw.processChange(ChangeKindAdd, p, fi, file); err != nil {
162 if err := file.Close(); err != nil {
163 return errors.Wrapf(err, "failed to close %s", newPath)
167 if err := rewriteMetadata(newPath, stat); err != nil {
168 return errors.Wrapf(err, "error setting metadata for %s", newPath)
172 if err := os.Rename(newPath, destPath); err != nil {
173 return errors.Wrapf(err, "failed to rename %s to %s", newPath, destPath)
178 if dw.opt.AsyncDataCb != nil {
179 dw.requestAsyncFileData(p, destPath, fi)
182 return dw.processChange(kind, p, fi, nil)
188 func (dw *DiskWriter) requestAsyncFileData(p, dest string, fi os.FileInfo) {
189 // todo: limit worker threads
190 dw.eg.Go(func() error {
191 if err := dw.processChange(ChangeKindAdd, p, fi, &lazyFileWriter{
196 return chtimes(dest, fi.ModTime().UnixNano()) // TODO: parent dirs
200 func (dw *DiskWriter) processChange(kind ChangeKind, p string, fi os.FileInfo, w io.WriteCloser) error {
203 if dw.opt.NotifyCb != nil {
205 if hw, err = newHashWriter(p, fi, w); err != nil {
211 fn := dw.opt.SyncDataCb
212 if fn == nil && dw.opt.AsyncDataCb != nil {
213 fn = dw.opt.AsyncDataCb
215 if err := fn(dw.ctx, p, w); err != nil {
224 return dw.opt.NotifyCb(kind, p, hw, nil)
229 type hashedWriter struct {
237 func newHashWriter(p string, fi os.FileInfo, w io.WriteCloser) (*hashedWriter, error) {
238 h, err := NewTarsumHash(p, fi)
244 Writer: io.MultiWriter(w, h),
251 func (hw *hashedWriter) Close() error {
252 hw.sum = string(hex.EncodeToString(hw.h.Sum(nil)))
259 func (hw *hashedWriter) Hash() string {
263 type lazyFileWriter struct {
269 func (lfw *lazyFileWriter) Write(dt []byte) (int, error) {
271 file, err := os.OpenFile(lfw.dest, os.O_WRONLY, 0) //todo: windows
273 return 0, errors.Wrapf(err, "failed to open %s", lfw.dest)
277 return lfw.f.Write(dt)
280 func (lfw *lazyFileWriter) Close() error {
287 func mkdev(major int64, minor int64) uint32 {
288 return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
291 // Random number state.
292 // We generate random temporary file names so that there's a good
293 // chance the file doesn't exist yet - keeps the number of tries in
294 // TempFile to a minimum.
296 var randmu sync.Mutex
298 func reseed() uint32 {
299 return uint32(time.Now().UnixNano() + int64(os.Getpid()))
302 func nextSuffix() string {
308 r = r*1664525 + 1013904223 // constants from Numerical Recipes
311 return strconv.Itoa(int(1e9 + r%1e9))[1:]
314 func NewTarsumHash(p string, fi os.FileInfo) (hash.Hash, error) {
315 stat, ok := fi.Sys().(*Stat)
321 p += string(os.PathSeparator)
323 h, err := archive.FileInfoHeader(p, fi, link)
329 h.Uid = int(stat.Uid)
330 h.Gid = int(stat.Gid)
331 h.Linkname = stat.Linkname
332 if stat.Xattrs != nil {
333 h.Xattrs = make(map[string]string)
334 for k, v := range stat.Xattrs {
335 h.Xattrs[k] = string(v)
339 tsh := &tarsumHash{h: h, Hash: sha256.New()}
344 // Reset resets the Hash to its initial state.
345 func (tsh *tarsumHash) Reset() {
347 tarsum.WriteV1Header(tsh.h, tsh.Hash)
350 type tarsumHash struct {