From 9e984f429c2d357ad8af59d57387233acc9bb877 Mon Sep 17 00:00:00 2001 From: Mark Mickan Date: Sat, 3 Jun 2017 20:45:00 +0930 Subject: [PATCH 1/2] Include symlinks in snapshots when migrating disks Fixes #2685 --- client/allocdir/alloc_dir.go | 19 +++++++++++-------- client/client.go | 7 +++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/client/allocdir/alloc_dir.go b/client/allocdir/alloc_dir.go index 749f5da17932..1850cef1c6e8 100644 --- a/client/allocdir/alloc_dir.go +++ b/client/allocdir/alloc_dir.go @@ -118,26 +118,29 @@ func (d *AllocDir) Snapshot(w io.Writer) error { defer tw.Close() walkFn := func(path string, fileInfo os.FileInfo, err error) error { - // Ignore if the file is a symlink - if fileInfo.Mode() == os.ModeSymlink { - return nil - } - // Include the path of the file name relative to the alloc dir // so that we can put the files in the right directories relPath, err := filepath.Rel(d.AllocDir, path) if err != nil { return err } - hdr, err := tar.FileInfoHeader(fileInfo, "") + link := "" + if fileInfo.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(path) + if err != nil { + return fmt.Errorf("error reading symlink: %v", err) + } + link = target + } + hdr, err := tar.FileInfoHeader(fileInfo, link) if err != nil { return fmt.Errorf("error creating file header: %v", err) } hdr.Name = relPath tw.WriteHeader(hdr) - // If it's a directory we just write the header into the tar - if fileInfo.IsDir() { + // If it's a directory or symlink we just write the header into the tar + if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) { return nil } diff --git a/client/client.go b/client/client.go index ed366ae0eb63..7bc7f9a8625f 100644 --- a/client/client.go +++ b/client/client.go @@ -1851,6 +1851,13 @@ func (c *Client) unarchiveAllocDir(resp io.ReadCloser, allocID string, pathToAll os.MkdirAll(filepath.Join(pathToAllocDir, hdr.Name), os.FileMode(hdr.Mode)) continue } + // If the header is for a symlink we create the symlink + if hdr.Typeflag == tar.TypeSymlink { + if os.Symlink(hdr.Linkname, filepath.Join(pathToAllocDir, hdr.Name)); err != nil { + c.logger.Printf("[ERR] client: error creating symlink: %v", err) + } + continue + } // If the header is a file, we write to a file if hdr.Typeflag == tar.TypeReg { f, err := os.Create(filepath.Join(pathToAllocDir, hdr.Name)) From e418b3e468fdf96216fa2fde65746d343fb852ac Mon Sep 17 00:00:00 2001 From: Mark Mickan Date: Sun, 4 Jun 2017 15:56:22 +0930 Subject: [PATCH 2/2] Add tests for migrating symlinks in alloc and local directories --- client/allocdir/alloc_dir_test.go | 20 +++++++++++++++++- client/client.go | 2 +- client/client_test.go | 34 +++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/client/allocdir/alloc_dir_test.go b/client/allocdir/alloc_dir_test.go index efd4805014d5..7ed7c13b774c 100644 --- a/client/allocdir/alloc_dir_test.go +++ b/client/allocdir/alloc_dir_test.go @@ -166,11 +166,23 @@ func TestAllocDir_Snapshot(t *testing.T) { t.Fatalf("Couldn't write file to shared directory: %v", err) } + // Write a symlink to the shared dir + link := "qux" + if err := os.Symlink("foo", filepath.Join(d.SharedDir, "data", link)); err != nil { + t.Fatalf("Couldn't write symlink to shared directory: %v", err) + } + // Write a file to the task local exp = []byte{'b', 'a', 'r'} file1 := "lol" if err := ioutil.WriteFile(filepath.Join(td1.LocalDir, file1), exp, 0666); err != nil { - t.Fatalf("couldn't write to task local directory: %v", err) + t.Fatalf("couldn't write file to task local directory: %v", err) + } + + // Write a symlink to the task local + link1 := "baz" + if err := os.Symlink("bar", filepath.Join(td1.LocalDir, link1)); err != nil { + t.Fatalf("couldn't write symlink to task local dirctory :%v", err) } var b bytes.Buffer @@ -180,6 +192,7 @@ func TestAllocDir_Snapshot(t *testing.T) { tr := tar.NewReader(&b) var files []string + var links []string for { hdr, err := tr.Next() if err != nil && err != io.EOF { @@ -190,12 +203,17 @@ func TestAllocDir_Snapshot(t *testing.T) { } if hdr.Typeflag == tar.TypeReg { files = append(files, hdr.FileInfo().Name()) + } else if hdr.Typeflag == tar.TypeSymlink { + links = append(links, hdr.FileInfo().Name()) } } if len(files) != 2 { t.Fatalf("bad files: %#v", files) } + if len(links) != 2 { + t.Fatalf("bad links: %#v", links) + } } func TestAllocDir_Move(t *testing.T) { diff --git a/client/client.go b/client/client.go index 7bc7f9a8625f..0316ce30f119 100644 --- a/client/client.go +++ b/client/client.go @@ -1853,7 +1853,7 @@ func (c *Client) unarchiveAllocDir(resp io.ReadCloser, allocID string, pathToAll } // If the header is for a symlink we create the symlink if hdr.Typeflag == tar.TypeSymlink { - if os.Symlink(hdr.Linkname, filepath.Join(pathToAllocDir, hdr.Name)); err != nil { + if err = os.Symlink(hdr.Linkname, filepath.Join(pathToAllocDir, hdr.Name)); err != nil { c.logger.Printf("[ERR] client: error creating symlink: %v", err) } continue diff --git a/client/client_test.go b/client/client_test.go index abf857da4fc7..0d17ad363be0 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -939,27 +939,37 @@ func TestClient_UnarchiveAllocDir(t *testing.T) { t.Fatalf("err: %v", err) } f.Close() + if err := os.Symlink("bar", filepath.Join(dir, "foo", "baz")); err != nil { + t.Fatalf("err: %v", err) + } + linkInfo, err := os.Lstat(filepath.Join(dir, "foo", "baz")) + if err != nil { + t.Fatalf("err: %v", err) + } buf := new(bytes.Buffer) tw := tar.NewWriter(buf) walkFn := func(path string, fileInfo os.FileInfo, err error) error { - // Ignore if the file is a symlink - if fileInfo.Mode() == os.ModeSymlink { - return nil - } - // Include the path of the file name relative to the alloc dir // so that we can put the files in the right directories - hdr, err := tar.FileInfoHeader(fileInfo, "") + link := "" + if fileInfo.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(path) + if err != nil { + return fmt.Errorf("error reading symlink: %v", err) + } + link = target + } + hdr, err := tar.FileInfoHeader(fileInfo, link) if err != nil { return fmt.Errorf("error creating file header: %v", err) } hdr.Name = fileInfo.Name() tw.WriteHeader(hdr) - // If it's a directory we just write the header into the tar - if fileInfo.IsDir() { + // If it's a directory or symlink we just write the header into the tar + if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) { return nil } @@ -1016,4 +1026,12 @@ func TestClient_UnarchiveAllocDir(t *testing.T) { if fi1.Mode() != fInfo.Mode() { t.Fatalf("mode: %v", fi1.Mode()) } + + fi2, err := os.Lstat(filepath.Join(dir1, "baz")) + if err != nil { + t.Fatalf("err: %v", err) + } + if fi2.Mode() != linkInfo.Mode() { + t.Fatalf("mode: %v", fi2.Mode()) + } }