Browse Source

Merge pull request #4382 from unclejack/fix_symlink_handling

handle symlinks for Docker's root dir & TMPDIR
unclejack 11 years ago
parent
commit
d761ebea6f
5 changed files with 127 additions and 20 deletions
  1. 22 2
      docker/docker.go
  2. 8 4
      docs/sources/reference/commandline/cli.rst
  3. 0 14
      engine/engine.go
  4. 21 0
      utils/utils.go
  5. 76 0
      utils/utils_test.go

+ 22 - 2
docker/docker.go

@@ -78,7 +78,27 @@ func main() {
 			return
 			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 {
 		if err != nil {
 			log.Fatal(err)
 			log.Fatal(err)
 		}
 		}
@@ -91,7 +111,7 @@ func main() {
 			// Load plugin: httpapi
 			// Load plugin: httpapi
 			job := eng.Job("initserver")
 			job := eng.Job("initserver")
 			job.Setenv("Pidfile", *pidfile)
 			job.Setenv("Pidfile", *pidfile)
-			job.Setenv("Root", *flRoot)
+			job.Setenv("Root", realRoot)
 			job.SetenvBool("AutoRestart", *flAutoRestart)
 			job.SetenvBool("AutoRestart", *flAutoRestart)
 			job.SetenvList("Dns", flDns.GetAll())
 			job.SetenvList("Dns", flDns.GetAll())
 			job.SetenvBool("EnableIptables", *flEnableIptables)
 			job.SetenvBool("EnableIptables", *flEnableIptables)

+ 8 - 4
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.
 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 <https://github.com/dotcloud/docker/blob/master/contrib/init/systemd/socket-activation/>`_.
 You can find examples of using systemd socket activation with docker and systemd in the `docker source tree <https://github.com/dotcloud/docker/blob/master/contrib/init/systemd/socket-activation/>`_.
 
 
-.. 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:
 .. _cli_attach:
 
 

+ 0 - 14
engine/engine.go

@@ -7,7 +7,6 @@ import (
 	"io"
 	"io"
 	"log"
 	"log"
 	"os"
 	"os"
-	"path/filepath"
 	"runtime"
 	"runtime"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
@@ -90,19 +89,6 @@ func New(root string) (*Engine, error) {
 		return nil, err
 		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{
 	eng := &Engine{
 		root:     root,
 		root:     root,
 		handlers: make(map[string]Handler),
 		handlers: make(map[string]Handler),

+ 21 - 0
utils/utils.go

@@ -997,3 +997,24 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
 	}
 	}
 	return defaults
 	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
+}

+ 76 - 0
utils/utils_test.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"os"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 )
 )
@@ -498,3 +499,78 @@ func TestReplaceAndAppendEnvVars(t *testing.T) {
 		t.Fatalf("expected TERM=xterm got '%s'", env[1])
 		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)
+	}
+}