diff --git a/docker/docker.go b/docker/docker.go index 3ea3d63027..c18a32f7cf 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -78,7 +78,27 @@ func main() { return } - eng, err := engine.New(*flRoot) + // set up the TempDir to use a canonical path + tmp := os.TempDir() + realTmp, err := utils.ReadSymlinkedDirectory(tmp) + if err != nil { + log.Fatalf("Unable to get the full path to the TempDir (%s): %s", tmp, err) + } + os.Setenv("TMPDIR", realTmp) + + // get the canonical path to the Docker root directory + root := *flRoot + var realRoot string + if _, err := os.Stat(root); err != nil && os.IsNotExist(err) { + realRoot = root + } else { + realRoot, err = utils.ReadSymlinkedDirectory(root) + if err != nil { + log.Fatalf("Unable to get the full path to root (%s): %s", root, err) + } + } + + eng, err := engine.New(realRoot) if err != nil { log.Fatal(err) } @@ -91,7 +111,7 @@ func main() { // Load plugin: httpapi job := eng.Job("initserver") job.Setenv("Pidfile", *pidfile) - job.Setenv("Root", *flRoot) + job.Setenv("Root", realRoot) job.SetenvBool("AutoRestart", *flAutoRestart) job.SetenvList("Dns", flDns.GetAll()) job.SetenvBool("EnableIptables", *flEnableIptables) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c8d8a75832..153bc5e74d 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -112,11 +112,15 @@ Using ``fd://`` will work perfectly for most setups but you can also specify ind If the specified socket activated files aren't found then docker will exit. You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `_. -.. warning:: - Docker and LXC do not support the use of softlinks for either the Docker data directory (``/var/lib/docker``) or for ``/tmp``. - If your system is likely to be set up in that way, you can use ``readlink -f`` to canonicalise the links: +Docker supports softlinks for the Docker data directory (``/var/lib/docker``) and for ``/tmp``. +TMPDIR and the data directory can be set like this: - ``TMPDIR=$(readlink -f /tmp) /usr/local/bin/docker -d -D -g $(readlink -f /var/lib/docker) -H unix:// $EXPOSE_ALL > /var/lib/boot2docker/docker.log 2>&1`` +:: + + TMPDIR=/mnt/disk2/tmp /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 + # or + export TMPDIR=/mnt/disk2/tmp + /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 .. _cli_attach: diff --git a/engine/engine.go b/engine/engine.go index 68e109e7f2..685924077c 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -7,7 +7,6 @@ import ( "io" "log" "os" - "path/filepath" "runtime" "sort" "strings" @@ -90,19 +89,6 @@ func New(root string) (*Engine, error) { return nil, err } - // Docker makes some assumptions about the "absoluteness" of root - // ... so let's make sure it has no symlinks - if p, err := filepath.Abs(root); err != nil { - log.Fatalf("Unable to get absolute root (%s): %s", root, err) - } else { - root = p - } - if p, err := filepath.EvalSymlinks(root); err != nil { - log.Fatalf("Unable to canonicalize root (%s): %s", root, err) - } else { - root = p - } - eng := &Engine{ root: root, handlers: make(map[string]Handler), diff --git a/utils/utils.go b/utils/utils.go index 0a80ea877e..07b8f6a3d0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -997,3 +997,24 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { } return defaults } + +// ReadSymlinkedDirectory returns the target directory of a symlink. +// The target of the symbolic link may not be a file. +func ReadSymlinkedDirectory(path string) (string, error) { + var realPath string + var err error + if realPath, err = filepath.Abs(path); err != nil { + return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) + } + realPathInfo, err := os.Stat(realPath) + if err != nil { + return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) + } + if !realPathInfo.Mode().IsDir() { + return "", fmt.Errorf("canonical path points to a file '%s'", realPath) + } + return realPath, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index ae19c89668..444d2a2428 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -5,6 +5,7 @@ import ( "errors" "io" "io/ioutil" + "os" "strings" "testing" ) @@ -498,3 +499,78 @@ func TestReplaceAndAppendEnvVars(t *testing.T) { t.Fatalf("expected TERM=xterm got '%s'", env[1]) } } + +// Reading a symlink to a directory must return the directory +func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { + t.Errorf("failed to create directory: %s", err) + } + + if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { + t.Fatalf("failed to read symlink to directory: %s", err) + } + + if path != "/tmp/testReadSymlinkToExistingDirectory" { + t.Fatalf("symlink returned unexpected directory: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { + t.Errorf("failed to remove temporary directory: %s", err) + } + + if err = os.Remove("/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +} + +// Reading a non-existing symlink must fail +func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { + var path string + var err error + if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { + t.Fatalf("error expected for non-existing symlink") + } + + if path != "" { + t.Fatalf("expected empty path, but '%s' was returned", path) + } +} + +// Reading a symlink to a file must fail +func TestReadSymlinkedDirectoryToFile(t *testing.T) { + var err error + var file *os.File + + if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + file.Close() + + if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { + t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") + } + + if path != "" { + t.Fatalf("path should've been empty: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { + t.Errorf("failed to remove file: %s", err) + } + + if err = os.Remove("/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +}