Browse code

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 <cristian.staretu@gmail.com> (github: unclejack)

unclejack authored on 2014/02/25 06:10:06
Showing 5 changed files
... ...
@@ -78,7 +78,27 @@ func main() {
78 78
 			return
79 79
 		}
80 80
 
81
-		eng, err := engine.New(*flRoot)
81
+		// set up the TempDir to use a canonical path
82
+		tmp := os.TempDir()
83
+		realTmp, err := utils.ReadSymlinkedDirectory(tmp)
84
+		if err != nil {
85
+			log.Fatalf("Unable to get the full path to the TempDir (%s): %s", tmp, err)
86
+		}
87
+		os.Setenv("TMPDIR", realTmp)
88
+
89
+		// get the canonical path to the Docker root directory
90
+		root := *flRoot
91
+		var realRoot string
92
+		if _, err := os.Stat(root); err != nil && os.IsNotExist(err) {
93
+			realRoot = root
94
+		} else {
95
+			realRoot, err = utils.ReadSymlinkedDirectory(root)
96
+			if err != nil {
97
+				log.Fatalf("Unable to get the full path to root (%s): %s", root, err)
98
+			}
99
+		}
100
+
101
+		eng, err := engine.New(realRoot)
82 102
 		if err != nil {
83 103
 			log.Fatal(err)
84 104
 		}
... ...
@@ -91,7 +111,7 @@ func main() {
91 91
 			// Load plugin: httpapi
92 92
 			job := eng.Job("initserver")
93 93
 			job.Setenv("Pidfile", *pidfile)
94
-			job.Setenv("Root", *flRoot)
94
+			job.Setenv("Root", realRoot)
95 95
 			job.SetenvBool("AutoRestart", *flAutoRestart)
96 96
 			job.SetenvList("Dns", flDns.GetAll())
97 97
 			job.SetenvBool("EnableIptables", *flEnableIptables)
... ...
@@ -112,11 +112,15 @@ Using ``fd://`` will work perfectly for most setups but you can also specify ind
112 112
 If the specified socket activated files aren't found then docker will exit.
113 113
 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/>`_.
114 114
 
115
-.. warning::
116
-  Docker and LXC do not support the use of softlinks for either the Docker data directory (``/var/lib/docker``) or for ``/tmp``.
117
-  If your system is likely to be set up in that way, you can use ``readlink -f`` to canonicalise the links:
115
+Docker supports softlinks for the Docker data directory (``/var/lib/docker``) and for ``/tmp``.
116
+TMPDIR and the data directory can be set like this:
118 117
 
119
-  ``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``
118
+::
119
+
120
+    TMPDIR=/mnt/disk2/tmp /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1
121
+    # or
122
+    export TMPDIR=/mnt/disk2/tmp
123
+    /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1
120 124
 
121 125
 .. _cli_attach:
122 126
 
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"io"
8 8
 	"log"
9 9
 	"os"
10
-	"path/filepath"
11 10
 	"runtime"
12 11
 	"sort"
13 12
 	"strings"
... ...
@@ -90,19 +89,6 @@ func New(root string) (*Engine, error) {
90 90
 		return nil, err
91 91
 	}
92 92
 
93
-	// Docker makes some assumptions about the "absoluteness" of root
94
-	// ... so let's make sure it has no symlinks
95
-	if p, err := filepath.Abs(root); err != nil {
96
-		log.Fatalf("Unable to get absolute root (%s): %s", root, err)
97
-	} else {
98
-		root = p
99
-	}
100
-	if p, err := filepath.EvalSymlinks(root); err != nil {
101
-		log.Fatalf("Unable to canonicalize root (%s): %s", root, err)
102
-	} else {
103
-		root = p
104
-	}
105
-
106 93
 	eng := &Engine{
107 94
 		root:     root,
108 95
 		handlers: make(map[string]Handler),
... ...
@@ -997,3 +997,24 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
997 997
 	}
998 998
 	return defaults
999 999
 }
1000
+
1001
+// ReadSymlinkedDirectory returns the target directory of a symlink.
1002
+// The target of the symbolic link may not be a file.
1003
+func ReadSymlinkedDirectory(path string) (string, error) {
1004
+	var realPath string
1005
+	var err error
1006
+	if realPath, err = filepath.Abs(path); err != nil {
1007
+		return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
1008
+	}
1009
+	if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
1010
+		return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
1011
+	}
1012
+	realPathInfo, err := os.Stat(realPath)
1013
+	if err != nil {
1014
+		return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
1015
+	}
1016
+	if !realPathInfo.Mode().IsDir() {
1017
+		return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
1018
+	}
1019
+	return realPath, nil
1020
+}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"errors"
6 6
 	"io"
7 7
 	"io/ioutil"
8
+	"os"
8 9
 	"strings"
9 10
 	"testing"
10 11
 )
... ...
@@ -498,3 +499,78 @@ func TestReplaceAndAppendEnvVars(t *testing.T) {
498 498
 		t.Fatalf("expected TERM=xterm got '%s'", env[1])
499 499
 	}
500 500
 }
501
+
502
+// Reading a symlink to a directory must return the directory
503
+func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
504
+	var err error
505
+	if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
506
+		t.Errorf("failed to create directory: %s", err)
507
+	}
508
+
509
+	if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
510
+		t.Errorf("failed to create symlink: %s", err)
511
+	}
512
+
513
+	var path string
514
+	if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
515
+		t.Fatalf("failed to read symlink to directory: %s", err)
516
+	}
517
+
518
+	if path != "/tmp/testReadSymlinkToExistingDirectory" {
519
+		t.Fatalf("symlink returned unexpected directory: %s", path)
520
+	}
521
+
522
+	if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
523
+		t.Errorf("failed to remove temporary directory: %s", err)
524
+	}
525
+
526
+	if err = os.Remove("/tmp/dirLinkTest"); err != nil {
527
+		t.Errorf("failed to remove symlink: %s", err)
528
+	}
529
+}
530
+
531
+// Reading a non-existing symlink must fail
532
+func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
533
+	var path string
534
+	var err error
535
+	if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
536
+		t.Fatalf("error expected for non-existing symlink")
537
+	}
538
+
539
+	if path != "" {
540
+		t.Fatalf("expected empty path, but '%s' was returned", path)
541
+	}
542
+}
543
+
544
+// Reading a symlink to a file must fail
545
+func TestReadSymlinkedDirectoryToFile(t *testing.T) {
546
+	var err error
547
+	var file *os.File
548
+
549
+	if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
550
+		t.Fatalf("failed to create file: %s", err)
551
+	}
552
+
553
+	file.Close()
554
+
555
+	if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
556
+		t.Errorf("failed to create symlink: %s", err)
557
+	}
558
+
559
+	var path string
560
+	if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
561
+		t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
562
+	}
563
+
564
+	if path != "" {
565
+		t.Fatalf("path should've been empty: %s", path)
566
+	}
567
+
568
+	if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
569
+		t.Errorf("failed to remove file: %s", err)
570
+	}
571
+
572
+	if err = os.Remove("/tmp/fileLinkTest"); err != nil {
573
+		t.Errorf("failed to remove symlink: %s", err)
574
+	}
575
+}