diff --git a/docker/docker.go b/docker/docker.go index 3ea3d6302..c18a32f7c 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 c8d8a7583..153bc5e74 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 68e109e7f..685924077 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 0a80ea877..07b8f6a3d 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 ae19c8966..444d2a242 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) + } +}