From 611acf7a7c1c3a6796e862fb94143890a9aa4573 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 24 Feb 2014 23:10:06 +0200 Subject: [PATCH] handle symlinks for Docker's root dir & TMPDIR This removes the incomplete symlink handling from engine.go and it adds it one place in docker.go. It also enables handling symlinks for TMPDIR. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docker/docker.go | 24 ++++++- docs/sources/reference/commandline/cli.rst | 12 ++-- engine/engine.go | 14 ---- utils/utils.go | 21 ++++++ utils/utils_test.go | 76 ++++++++++++++++++++++ 5 files changed, 127 insertions(+), 20 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 5dda9d269..6c6411bdd 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) + } +}