Browse code

Split volumes out from daemon

Docker-DCO-1.1-Signed-off-by: Brian Goff <cpuguy83@gmail.com> (github: cpuguy83)

Brian Goff authored on 2014/08/28 23:18:08
Showing 12 changed files
... ...
@@ -618,9 +618,10 @@ func (b *Builder) clearTmp() {
618 618
 		tmp := b.Daemon.Get(c)
619 619
 		if err := b.Daemon.Destroy(tmp); err != nil {
620 620
 			fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
621
-		} else {
622
-			delete(b.TmpContainers, c)
623
-			fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
621
+			return
624 622
 		}
623
+		b.Daemon.DeleteVolumes(tmp.VolumePaths())
624
+		delete(b.TmpContainers, c)
625
+		fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
625 626
 	}
626 627
 }
... ...
@@ -79,6 +79,9 @@ type Container struct {
79 79
 	MountLabel, ProcessLabel string
80 80
 	RestartCount             int
81 81
 
82
+	// Maps container paths to volume paths.  The key in this is the path to which
83
+	// the volume is being mounted inside the container.  Value is the path of the
84
+	// volume on disk
82 85
 	Volumes map[string]string
83 86
 	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
84 87
 	// Easier than migrating older container configs :)
... ...
@@ -309,7 +312,7 @@ func (container *Container) Start() (err error) {
309 309
 		return err
310 310
 	}
311 311
 	container.verifyDaemonSettings()
312
-	if err := prepareVolumesForContainer(container); err != nil {
312
+	if err := container.prepareVolumes(); err != nil {
313 313
 		return err
314 314
 	}
315 315
 	linkedEnv, err := container.setupLinkedContainers()
... ...
@@ -323,7 +326,7 @@ func (container *Container) Start() (err error) {
323 323
 	if err := populateCommand(container, env); err != nil {
324 324
 		return err
325 325
 	}
326
-	if err := setupMountsForContainer(container); err != nil {
326
+	if err := container.setupMounts(); err != nil {
327 327
 		return err
328 328
 	}
329 329
 
... ...
@@ -1183,47 +1186,3 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
1183 1183
 		return nil, fmt.Errorf("network mode not set to container")
1184 1184
 	}
1185 1185
 }
1186
-
1187
-func (container *Container) GetVolumes() (map[string]*Volume, error) {
1188
-	// Get all the bind-mounts
1189
-	volumes, err := container.getBindMap()
1190
-	if err != nil {
1191
-		return nil, err
1192
-	}
1193
-
1194
-	// Get all the normal volumes
1195
-	for volPath, hostPath := range container.Volumes {
1196
-		if _, exists := volumes[volPath]; exists {
1197
-			continue
1198
-		}
1199
-		volumes[volPath] = &Volume{VolPath: volPath, HostPath: hostPath, isReadWrite: container.VolumesRW[volPath]}
1200
-	}
1201
-
1202
-	return volumes, nil
1203
-}
1204
-
1205
-func (container *Container) getBindMap() (map[string]*Volume, error) {
1206
-	var (
1207
-		// Create the requested bind mounts
1208
-		volumes = map[string]*Volume{}
1209
-		// Define illegal container destinations
1210
-		illegalDsts = []string{"/", "."}
1211
-	)
1212
-
1213
-	for _, bind := range container.hostConfig.Binds {
1214
-		vol, err := parseBindVolumeSpec(bind)
1215
-		if err != nil {
1216
-			return nil, err
1217
-		}
1218
-		vol.isBindMount = true
1219
-		// Bail if trying to mount to an illegal destination
1220
-		for _, illegal := range illegalDsts {
1221
-			if vol.VolPath == illegal {
1222
-				return nil, fmt.Errorf("Illegal bind destination: %s", vol.VolPath)
1223
-			}
1224
-		}
1225
-
1226
-		volumes[filepath.Clean(vol.VolPath)] = &vol
1227
-	}
1228
-	return volumes, nil
1229
-}
... ...
@@ -38,6 +38,7 @@ import (
38 38
 	"github.com/docker/docker/pkg/truncindex"
39 39
 	"github.com/docker/docker/runconfig"
40 40
 	"github.com/docker/docker/utils"
41
+	"github.com/docker/docker/volumes"
41 42
 )
42 43
 
43 44
 var (
... ...
@@ -90,7 +91,7 @@ type Daemon struct {
90 90
 	repositories   *graph.TagStore
91 91
 	idIndex        *truncindex.TruncIndex
92 92
 	sysInfo        *sysinfo.SysInfo
93
-	volumes        *graph.Graph
93
+	volumes        *volumes.Repository
94 94
 	eng            *engine.Engine
95 95
 	config         *Config
96 96
 	containerGraph *graphdb.Database
... ...
@@ -382,6 +383,12 @@ func (daemon *Daemon) restore() error {
382 382
 		}
383 383
 	}
384 384
 
385
+	for _, c := range registeredContainers {
386
+		for _, mnt := range c.VolumeMounts() {
387
+			daemon.volumes.Add(mnt.volume)
388
+		}
389
+	}
390
+
385 391
 	if !debug {
386 392
 		log.Infof(": done.")
387 393
 	}
... ...
@@ -789,17 +796,16 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
789 789
 		return nil, err
790 790
 	}
791 791
 
792
-	// We don't want to use a complex driver like aufs or devmapper
793
-	// for volumes, just a plain filesystem
794 792
 	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
795 793
 	if err != nil {
796 794
 		return nil, err
797 795
 	}
798
-	log.Debugf("Creating volumes graph")
799
-	volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver)
796
+
797
+	volumes, err := volumes.NewRepository(path.Join(config.Root, "volumes"), volumesDriver)
800 798
 	if err != nil {
801 799
 		return nil, err
802 800
 	}
801
+
803 802
 	log.Debugf("Creating repository list")
804 803
 	repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors)
805 804
 	if err != nil {
... ...
@@ -1028,10 +1034,6 @@ func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
1028 1028
 	return daemon.execDriver
1029 1029
 }
1030 1030
 
1031
-func (daemon *Daemon) Volumes() *graph.Graph {
1032
-	return daemon.volumes
1033
-}
1034
-
1035 1031
 func (daemon *Daemon) ContainerGraph() *graphdb.Database {
1036 1032
 	return daemon.containerGraph
1037 1033
 }
... ...
@@ -4,8 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"os"
6 6
 	"path"
7
-	"path/filepath"
8
-	"strings"
9 7
 
10 8
 	"github.com/docker/docker/engine"
11 9
 	"github.com/docker/docker/pkg/log"
... ...
@@ -21,10 +19,11 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
21 21
 	forceRemove := job.GetenvBool("forceRemove")
22 22
 	container := daemon.Get(name)
23 23
 
24
+	if container == nil {
25
+		job.Errorf("No such container: %s", name)
26
+	}
27
+
24 28
 	if removeLink {
25
-		if container == nil {
26
-			return job.Errorf("No such link: %s", name)
27
-		}
28 29
 		name, err := GetFullContainerName(name)
29 30
 		if err != nil {
30 31
 			job.Error(err)
... ...
@@ -63,73 +62,22 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
63 63
 			return job.Errorf("Cannot destroy container %s: %s", name, err)
64 64
 		}
65 65
 		container.LogEvent("destroy")
66
-
67 66
 		if removeVolume {
68
-			var (
69
-				volumes     = make(map[string]struct{})
70
-				binds       = make(map[string]struct{})
71
-				usedVolumes = make(map[string]*Container)
72
-			)
73
-
74
-			// the volume id is always the base of the path
75
-			getVolumeId := func(p string) string {
76
-				return filepath.Base(strings.TrimSuffix(p, "/layer"))
77
-			}
78
-
79
-			// populate bind map so that they can be skipped and not removed
80
-			for _, bind := range container.HostConfig().Binds {
81
-				source := strings.Split(bind, ":")[0]
82
-				// TODO: refactor all volume stuff, all of it
83
-				// it is very important that we eval the link or comparing the keys to container.Volumes will not work
84
-				//
85
-				// eval symlink can fail, ref #5244 if we receive an is not exist error we can ignore it
86
-				p, err := filepath.EvalSymlinks(source)
87
-				if err != nil && !os.IsNotExist(err) {
88
-					return job.Error(err)
89
-				}
90
-				if p != "" {
91
-					source = p
92
-				}
93
-				binds[source] = struct{}{}
94
-			}
95
-
96
-			// Store all the deleted containers volumes
97
-			for _, volumeId := range container.Volumes {
98
-				// Skip the volumes mounted from external
99
-				// bind mounts here will will be evaluated for a symlink
100
-				if _, exists := binds[volumeId]; exists {
101
-					continue
102
-				}
103
-
104
-				volumeId = getVolumeId(volumeId)
105
-				volumes[volumeId] = struct{}{}
106
-			}
107
-
108
-			// Retrieve all volumes from all remaining containers
109
-			for _, container := range daemon.List() {
110
-				for _, containerVolumeId := range container.Volumes {
111
-					containerVolumeId = getVolumeId(containerVolumeId)
112
-					usedVolumes[containerVolumeId] = container
113
-				}
114
-			}
115
-
116
-			for volumeId := range volumes {
117
-				// If the requested volu
118
-				if c, exists := usedVolumes[volumeId]; exists {
119
-					log.Infof("The volume %s is used by the container %s. Impossible to remove it. Skipping.", volumeId, c.ID)
120
-					continue
121
-				}
122
-				if err := daemon.Volumes().Delete(volumeId); err != nil {
123
-					return job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err)
124
-				}
125
-			}
67
+			daemon.DeleteVolumes(container.VolumePaths())
126 68
 		}
127
-	} else {
128
-		return job.Errorf("No such container: %s", name)
129 69
 	}
130 70
 	return engine.StatusOK
131 71
 }
132 72
 
73
+func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
74
+	for id := range volumeIDs {
75
+		if err := daemon.volumes.Delete(id); err != nil {
76
+			log.Infof("%s", err)
77
+			continue
78
+		}
79
+	}
80
+}
81
+
133 82
 // Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
134 83
 // FIXME: rename to Rm for consistency with the CLI command
135 84
 func (daemon *Daemon) Destroy(container *Container) error {
... ...
@@ -149,7 +97,7 @@ func (daemon *Daemon) Destroy(container *Container) error {
149 149
 	// Deregister the container before removing its directory, to avoid race conditions
150 150
 	daemon.idIndex.Delete(container.ID)
151 151
 	daemon.containers.Delete(container.ID)
152
-
152
+	container.derefVolumes()
153 153
 	if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
154 154
 		log.Debugf("Unable to remove container from link graph: %s", err)
155 155
 	}
... ...
@@ -11,80 +11,28 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/archive"
13 13
 	"github.com/docker/docker/daemon/execdriver"
14
+	"github.com/docker/docker/pkg/log"
14 15
 	"github.com/docker/docker/pkg/symlink"
16
+	"github.com/docker/docker/volumes"
15 17
 )
16 18
 
17
-type Volume struct {
18
-	HostPath    string
19
-	VolPath     string
20
-	isReadWrite bool
21
-	isBindMount bool
19
+type Mount struct {
20
+	MountToPath string
21
+	container   *Container
22
+	volume      *volumes.Volume
23
+	Writable    bool
22 24
 }
23 25
 
24
-func (v *Volume) isDir() (bool, error) {
25
-	stat, err := os.Stat(v.HostPath)
26
-	if err != nil {
27
-		return false, err
28
-	}
29
-
30
-	return stat.IsDir(), nil
31
-}
32
-
33
-func prepareVolumesForContainer(container *Container) error {
26
+func (container *Container) prepareVolumes() error {
34 27
 	if container.Volumes == nil || len(container.Volumes) == 0 {
35 28
 		container.Volumes = make(map[string]string)
36 29
 		container.VolumesRW = make(map[string]bool)
37
-		if err := applyVolumesFrom(container); err != nil {
30
+		if err := container.applyVolumesFrom(); err != nil {
38 31
 			return err
39 32
 		}
40 33
 	}
41 34
 
42
-	return createVolumes(container)
43
-}
44
-
45
-func setupMountsForContainer(container *Container) error {
46
-	mounts := []execdriver.Mount{
47
-		{
48
-			Source:      container.ResolvConfPath,
49
-			Destination: "/etc/resolv.conf",
50
-			Writable:    true,
51
-			Slave:       true,
52
-		},
53
-	}
54
-
55
-	if container.HostnamePath != "" {
56
-		mounts = append(mounts, execdriver.Mount{
57
-			Source:      container.HostnamePath,
58
-			Destination: "/etc/hostname",
59
-			Writable:    true,
60
-			Private:     true,
61
-		})
62
-	}
63
-
64
-	if container.HostsPath != "" {
65
-		mounts = append(mounts, execdriver.Mount{
66
-			Source:      container.HostsPath,
67
-			Destination: "/etc/hosts",
68
-			Writable:    true,
69
-			Slave:       true,
70
-		})
71
-	}
72
-
73
-	// Mount user specified volumes
74
-	// Note, these are not private because you may want propagation of (un)mounts from host
75
-	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
76
-	// want this new mount in the container
77
-	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
78
-	for _, path := range container.sortedVolumeMounts() {
79
-		mounts = append(mounts, execdriver.Mount{
80
-			Source:      container.Volumes[path],
81
-			Destination: path,
82
-			Writable:    container.VolumesRW[path],
83
-		})
84
-	}
85
-
86
-	container.command.Mounts = mounts
87
-	return nil
35
+	return container.createVolumes()
88 36
 }
89 37
 
90 38
 // sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
... ...
@@ -98,208 +46,223 @@ func (container *Container) sortedVolumeMounts() []string {
98 98
 	return mountPaths
99 99
 }
100 100
 
101
-func parseVolumesFromSpec(container *Container, spec string) (map[string]*Volume, error) {
102
-	specParts := strings.SplitN(spec, ":", 2)
103
-	if len(specParts) == 0 {
104
-		return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec)
101
+func (container *Container) createVolumes() error {
102
+	mounts, err := container.parseVolumeMountConfig()
103
+	if err != nil {
104
+		return err
105 105
 	}
106 106
 
107
-	c := container.daemon.Get(specParts[0])
108
-	if c == nil {
109
-		return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
107
+	for _, mnt := range mounts {
108
+		if err := mnt.initialize(); err != nil {
109
+			return err
110
+		}
110 111
 	}
111 112
 
112
-	volumes, err := c.GetVolumes()
113
+	return nil
114
+}
115
+
116
+func (m *Mount) initialize() error {
117
+	// No need to initialize anything since it's already been initialized
118
+	if _, exists := m.container.Volumes[m.MountToPath]; exists {
119
+		return nil
120
+	}
121
+
122
+	// This is the full path to container fs + mntToPath
123
+	containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
113 124
 	if err != nil {
114
-		return nil, err
125
+		return err
126
+	}
127
+	m.container.VolumesRW[m.MountToPath] = m.Writable
128
+	m.container.Volumes[m.MountToPath] = m.volume.Path
129
+	m.volume.AddContainer(m.container.ID)
130
+	if m.Writable && !m.volume.IsBindMount {
131
+		// Copy whatever is in the container at the mntToPath to the volume
132
+		copyExistingContents(containerMntPath, m.volume.Path)
115 133
 	}
116 134
 
117
-	if len(specParts) == 2 {
118
-		mode := specParts[1]
119
-		if validVolumeMode(mode) {
120
-			return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode)
121
-		}
135
+	return nil
136
+}
122 137
 
123
-		// Set the mode for the inheritted volume
124
-		for _, v := range volumes {
125
-			v.isReadWrite = mode != "ro"
126
-		}
138
+func (container *Container) VolumePaths() map[string]struct{} {
139
+	var paths = make(map[string]struct{})
140
+	for _, path := range container.Volumes {
141
+		paths[path] = struct{}{}
127 142
 	}
128
-
129
-	return volumes, nil
143
+	return paths
130 144
 }
131 145
 
132
-func applyVolumesFrom(container *Container) error {
133
-	volumesFrom := container.hostConfig.VolumesFrom
146
+func (container *Container) derefVolumes() {
147
+	for path := range container.VolumePaths() {
148
+		vol := container.daemon.volumes.Get(path)
149
+		if vol == nil {
150
+			log.Debugf("Volume %s was not found and could not be dereferenced", path)
151
+			continue
152
+		}
153
+		vol.RemoveContainer(container.ID)
154
+	}
155
+}
134 156
 
135
-	for _, spec := range volumesFrom {
136
-		volumes, err := parseVolumesFromSpec(container, spec)
157
+func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
158
+	var mounts = make(map[string]*Mount)
159
+	// Get all the bind mounts
160
+	for _, spec := range container.hostConfig.Binds {
161
+		path, mountToPath, writable, err := parseBindMountSpec(spec)
137 162
 		if err != nil {
138
-			return err
163
+			return nil, err
139 164
 		}
140
-
141
-		for _, v := range volumes {
142
-			if err = v.initialize(container); err != nil {
143
-				return err
165
+		// Check if a volume already exists for this and use it
166
+		vol := container.daemon.volumes.Get(path)
167
+		if vol == nil {
168
+			vol, err = container.daemon.volumes.NewVolume(path, writable)
169
+			if err != nil {
170
+				return nil, err
144 171
 			}
145 172
 		}
173
+		mounts[mountToPath] = &Mount{container: container, volume: vol, MountToPath: mountToPath, Writable: writable}
146 174
 	}
147
-	return nil
148
-}
149 175
 
150
-func validVolumeMode(mode string) bool {
151
-	validModes := map[string]bool{
152
-		"rw": true,
153
-		"ro": true,
176
+	// Get the rest of the volumes
177
+	for path := range container.Config.Volumes {
178
+		// Check if this is already added as a bind-mount
179
+		if _, exists := mounts[path]; exists {
180
+			continue
181
+		}
182
+
183
+		vol, err := container.daemon.volumes.NewVolume("", true)
184
+		if err != nil {
185
+			return nil, err
186
+		}
187
+		mounts[path] = &Mount{container: container, MountToPath: path, volume: vol, Writable: true}
154 188
 	}
155 189
 
156
-	return validModes[mode]
190
+	return mounts, nil
157 191
 }
158 192
 
159
-func parseBindVolumeSpec(spec string) (Volume, error) {
193
+func parseBindMountSpec(spec string) (string, string, bool, error) {
160 194
 	var (
161
-		arr = strings.Split(spec, ":")
162
-		vol Volume
195
+		path, mountToPath string
196
+		writable          bool
197
+		arr               = strings.Split(spec, ":")
163 198
 	)
164 199
 
165 200
 	switch len(arr) {
166
-	case 1:
167
-		vol.VolPath = spec
168
-		vol.isReadWrite = true
169 201
 	case 2:
170
-		vol.HostPath = arr[0]
171
-		vol.VolPath = arr[1]
172
-		vol.isReadWrite = true
202
+		path = arr[0]
203
+		mountToPath = arr[1]
204
+		writable = true
173 205
 	case 3:
174
-		vol.HostPath = arr[0]
175
-		vol.VolPath = arr[1]
176
-		vol.isReadWrite = validVolumeMode(arr[2]) && arr[2] == "rw"
206
+		path = arr[0]
207
+		mountToPath = arr[1]
208
+		writable = validMountMode(arr[2]) && arr[2] == "rw"
177 209
 	default:
178
-		return vol, fmt.Errorf("Invalid volume specification: %s", spec)
210
+		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
179 211
 	}
180 212
 
181
-	if !filepath.IsAbs(vol.HostPath) {
182
-		return vol, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", vol.HostPath)
213
+	if !filepath.IsAbs(path) {
214
+		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
183 215
 	}
184 216
 
185
-	return vol, nil
217
+	return path, mountToPath, writable, nil
186 218
 }
187 219
 
188
-func createVolumes(container *Container) error {
189
-	// Get all the bindmounts
190
-	volumes, err := container.GetVolumes()
191
-	if err != nil {
192
-		return err
193
-	}
220
+func (container *Container) applyVolumesFrom() error {
221
+	volumesFrom := container.hostConfig.VolumesFrom
194 222
 
195
-	// Get all the rest of the volumes
196
-	for volPath := range container.Config.Volumes {
197
-		// Make sure the the volume isn't already specified as a bindmount
198
-		if _, exists := volumes[volPath]; !exists {
199
-			volumes[volPath] = &Volume{
200
-				VolPath:     volPath,
201
-				isReadWrite: true,
202
-				isBindMount: false,
203
-			}
223
+	for _, spec := range volumesFrom {
224
+		mounts, err := parseVolumesFromSpec(container.daemon, spec)
225
+		if err != nil {
226
+			return err
204 227
 		}
205
-	}
206 228
 
207
-	for _, vol := range volumes {
208
-		if err = vol.initialize(container); err != nil {
209
-			return err
229
+		for _, mnt := range mounts {
230
+			mnt.container = container
231
+			if err = mnt.initialize(); err != nil {
232
+				return err
233
+			}
210 234
 		}
211 235
 	}
212
-
213 236
 	return nil
214 237
 }
215 238
 
216
-func createVolumeHostPath(container *Container) (string, error) {
217
-	volumesDriver := container.daemon.volumes.Driver()
218
-
219
-	// Do not pass a container as the parameter for the volume creation.
220
-	// The graph driver using the container's information ( Image ) to
221
-	// create the parent.
222
-	c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil)
223
-	if err != nil {
224
-		return "", err
225
-	}
226
-	hostPath, err := volumesDriver.Get(c.ID, "")
227
-	if err != nil {
228
-		return hostPath, fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
239
+func validMountMode(mode string) bool {
240
+	validModes := map[string]bool{
241
+		"rw": true,
242
+		"ro": true,
229 243
 	}
230 244
 
231
-	return hostPath, nil
245
+	return validModes[mode]
232 246
 }
233 247
 
234
-func (v *Volume) initialize(container *Container) error {
235
-	var err error
236
-	v.VolPath = filepath.Clean(v.VolPath)
237
-
238
-	// Do not initialize an existing volume
239
-	if _, exists := container.Volumes[v.VolPath]; exists {
240
-		return nil
241
-	}
242
-
243
-	// If it's not a bindmount we need to create the dir on the host
244
-	if !v.isBindMount && v.HostPath == "" {
245
-		v.HostPath, err = createVolumeHostPath(container)
246
-		if err != nil {
247
-			return err
248
-		}
248
+func (container *Container) setupMounts() error {
249
+	mounts := []execdriver.Mount{
250
+		{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true},
249 251
 	}
250 252
 
251
-	hostPath, err := filepath.EvalSymlinks(v.HostPath)
252
-	if err != nil {
253
-		return err
253
+	if container.HostnamePath != "" {
254
+		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
254 255
 	}
255 256
 
256
-	// Create the mountpoint
257
-	// This is the path to the volume within the container FS
258
-	// This differs from `hostPath` in that `hostPath` refers to the place where
259
-	// the volume data is actually stored on the host
260
-	fullVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, v.VolPath), container.basefs)
261
-	if err != nil {
262
-		return err
257
+	if container.HostsPath != "" {
258
+		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
263 259
 	}
264 260
 
265
-	container.Volumes[v.VolPath] = hostPath
266
-	container.VolumesRW[v.VolPath] = v.isReadWrite
267
-
268
-	volIsDir, err := v.isDir()
269
-	if err != nil {
270
-		return err
271
-	}
272
-	if err := createIfNotExists(fullVolPath, volIsDir); err != nil {
273
-		return err
261
+	// Mount user specified volumes
262
+	// Note, these are not private because you may want propagation of (un)mounts from host
263
+	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
264
+	// want this new mount in the container
265
+	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
266
+	for _, path := range container.sortedVolumeMounts() {
267
+		mounts = append(mounts, execdriver.Mount{
268
+			Source:      container.Volumes[path],
269
+			Destination: path,
270
+			Writable:    container.VolumesRW[path],
271
+		})
274 272
 	}
275 273
 
276
-	// Do not copy or change permissions if we are mounting from the host
277
-	if v.isReadWrite && !v.isBindMount {
278
-		return copyExistingContents(fullVolPath, hostPath)
279
-	}
274
+	container.command.Mounts = mounts
280 275
 	return nil
281 276
 }
282 277
 
283
-func createIfNotExists(destination string, isDir bool) error {
284
-	if _, err := os.Stat(destination); err == nil || !os.IsNotExist(err) {
285
-		return nil
278
+func parseVolumesFromSpec(daemon *Daemon, spec string) (map[string]*Mount, error) {
279
+	specParts := strings.SplitN(spec, ":", 2)
280
+	if len(specParts) == 0 {
281
+		return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec)
286 282
 	}
287 283
 
288
-	if isDir {
289
-		return os.MkdirAll(destination, 0755)
284
+	c := daemon.Get(specParts[0])
285
+	if c == nil {
286
+		return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
290 287
 	}
291 288
 
292
-	if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
293
-		return err
289
+	mounts := c.VolumeMounts()
290
+
291
+	if len(specParts) == 2 {
292
+		mode := specParts[1]
293
+		if validMountMode(mode) {
294
+			return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode)
295
+		}
296
+
297
+		// Set the mode for the inheritted volume
298
+		for _, mnt := range mounts {
299
+			// Ensure that if the inherited volume is not writable, that we don't make
300
+			// it writable here
301
+			mnt.Writable = mnt.Writable && (mode == "rw")
302
+		}
294 303
 	}
295 304
 
296
-	f, err := os.OpenFile(destination, os.O_CREATE, 0755)
297
-	if err != nil {
298
-		return err
305
+	return mounts, nil
306
+}
307
+
308
+func (container *Container) VolumeMounts() map[string]*Mount {
309
+	mounts := make(map[string]*Mount)
310
+
311
+	for mountToPath, path := range container.Volumes {
312
+		if v := container.daemon.volumes.Get(path); v != nil {
313
+			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
314
+		}
299 315
 	}
300
-	f.Close()
301 316
 
302
-	return nil
317
+	return mounts
303 318
 }
304 319
 
305 320
 func copyExistingContents(source, destination string) error {
... ...
@@ -1463,7 +1463,7 @@ func TestBuildWithVolumeOwnership(t *testing.T) {
1463 1463
 	cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test")
1464 1464
 	out, _, err := runCommandWithOutput(cmd)
1465 1465
 	if err != nil {
1466
-		t.Fatal(err)
1466
+		t.Fatal(out, err)
1467 1467
 	}
1468 1468
 
1469 1469
 	if expected := "drw-------"; !strings.Contains(out, expected) {
... ...
@@ -122,7 +122,7 @@ func TestCommitWithHostBindMount(t *testing.T) {
122 122
 	cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest")
123 123
 	imageId, _, err := runCommandWithOutput(cmd)
124 124
 	if err != nil {
125
-		t.Fatal(err)
125
+		t.Fatal(imageId, err)
126 126
 	}
127 127
 	imageId = strings.Trim(imageId, "\r\n")
128 128
 
... ...
@@ -18,8 +18,8 @@ func TestRmContainerWithRemovedVolume(t *testing.T) {
18 18
 	}
19 19
 
20 20
 	cmd = exec.Command(dockerBinary, "rm", "-v", "losemyvolumes")
21
-	if _, err := runCommand(cmd); err != nil {
22
-		t.Fatal(err)
21
+	if out, _, err := runCommandWithOutput(cmd); err != nil {
22
+		t.Fatal(out, err)
23 23
 	}
24 24
 
25 25
 	deleteAllContainers()
... ...
@@ -398,16 +398,25 @@ func TestRunVolumesFromInReadWriteMode(t *testing.T) {
398 398
 	logDone("run - volumes from as read write mount")
399 399
 }
400 400
 
401
-func TestRunVolumesFromInheritsReadOnly(t *testing.T) {
401
+func TestVolumesFromGetsProperMode(t *testing.T) {
402 402
 	cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test:/test:ro", "busybox", "true")
403 403
 	if _, err := runCommand(cmd); err != nil {
404 404
 		t.Fatal(err)
405 405
 	}
406
-
407 406
 	// Expect this "rw" mode to be be ignored since the inheritted volume is "ro"
408 407
 	cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file")
409 408
 	if _, err := runCommand(cmd); err == nil {
410
-		t.Fatal("Expected to inherit read-only volume even when passing in `rw`")
409
+		t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`")
410
+	}
411
+
412
+	cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/test:/test:ro", "busybox", "true")
413
+	if _, err := runCommand(cmd); err != nil {
414
+		t.Fatal(err)
415
+	}
416
+	// Expect this to be read-only since both are "ro"
417
+	cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent2:ro", "busybox", "touch", "/test/file")
418
+	if _, err := runCommand(cmd); err == nil {
419
+		t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`")
411 420
 	}
412 421
 
413 422
 	deleteAllContainers()
... ...
@@ -423,8 +432,8 @@ func TestRunApplyVolumesFromBeforeVolumes(t *testing.T) {
423 423
 	}
424 424
 
425 425
 	cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "-v", "/test", "busybox", "cat", "/test/foo")
426
-	if _, err := runCommand(cmd); err != nil {
427
-		t.Fatal(err)
426
+	if out, _, err := runCommandWithOutput(cmd); err != nil {
427
+		t.Fatal(out, err)
428 428
 	}
429 429
 
430 430
 	deleteAllContainers()
431 431
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Brian Goff <cpuguy83@gmail.com> (@cpuguy83)
0 1
new file mode 100644
... ...
@@ -0,0 +1,169 @@
0
+package volumes
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"sync"
8
+
9
+	"github.com/docker/docker/daemon/graphdriver"
10
+	"github.com/docker/docker/utils"
11
+)
12
+
13
+type Repository struct {
14
+	configPath string
15
+	driver     graphdriver.Driver
16
+	volumes    map[string]*Volume
17
+	lock       sync.Mutex
18
+}
19
+
20
+func NewRepository(configPath string, driver graphdriver.Driver) (*Repository, error) {
21
+	abspath, err := filepath.Abs(configPath)
22
+	if err != nil {
23
+		return nil, err
24
+	}
25
+
26
+	// Create the config path
27
+	if err := os.MkdirAll(abspath, 0700); err != nil && !os.IsExist(err) {
28
+		return nil, err
29
+	}
30
+
31
+	repo := &Repository{
32
+		driver:     driver,
33
+		configPath: abspath,
34
+		volumes:    make(map[string]*Volume),
35
+	}
36
+
37
+	return repo, repo.restore()
38
+}
39
+
40
+func (r *Repository) NewVolume(path string, writable bool) (*Volume, error) {
41
+	var (
42
+		isBindMount bool
43
+		err         error
44
+		id          = utils.GenerateRandomID()
45
+	)
46
+	if path != "" {
47
+		isBindMount = true
48
+	}
49
+
50
+	if path == "" {
51
+		path, err = r.createNewVolumePath(id)
52
+		if err != nil {
53
+			return nil, err
54
+		}
55
+	}
56
+
57
+	path, err = filepath.EvalSymlinks(path)
58
+	if err != nil {
59
+		return nil, err
60
+	}
61
+
62
+	v := &Volume{
63
+		ID:          id,
64
+		Path:        path,
65
+		repository:  r,
66
+		Writable:    writable,
67
+		Containers:  make(map[string]struct{}),
68
+		configPath:  r.configPath + "/" + id,
69
+		IsBindMount: isBindMount,
70
+	}
71
+
72
+	if err := v.initialize(); err != nil {
73
+		return nil, err
74
+	}
75
+	if err := r.Add(v); err != nil {
76
+		return nil, err
77
+	}
78
+	return v, nil
79
+}
80
+
81
+func (r *Repository) restore() error {
82
+	dir, err := ioutil.ReadDir(r.configPath)
83
+	if err != nil {
84
+		return err
85
+	}
86
+
87
+	var ids []string
88
+	for _, v := range dir {
89
+		id := v.Name()
90
+		if r.driver.Exists(id) {
91
+			ids = append(ids, id)
92
+		}
93
+	}
94
+	return nil
95
+}
96
+
97
+func (r *Repository) Get(path string) *Volume {
98
+	r.lock.Lock()
99
+	vol := r.volumes[path]
100
+	r.lock.Unlock()
101
+	return vol
102
+}
103
+
104
+func (r *Repository) get(path string) *Volume {
105
+	return r.volumes[path]
106
+}
107
+
108
+func (r *Repository) Add(volume *Volume) error {
109
+	r.lock.Lock()
110
+	defer r.lock.Unlock()
111
+	if vol := r.get(volume.Path); vol != nil {
112
+		return fmt.Errorf("Volume exists: %s", volume.ID)
113
+	}
114
+	r.volumes[volume.Path] = volume
115
+	return nil
116
+}
117
+
118
+func (r *Repository) Remove(volume *Volume) {
119
+	r.lock.Lock()
120
+	r.remove(volume)
121
+	r.lock.Unlock()
122
+}
123
+
124
+func (r *Repository) remove(volume *Volume) {
125
+	delete(r.volumes, volume.Path)
126
+}
127
+
128
+func (r *Repository) Delete(path string) error {
129
+	r.lock.Lock()
130
+	defer r.lock.Unlock()
131
+	volume := r.get(path)
132
+	if volume == nil {
133
+		return fmt.Errorf("Volume %s does not exist", path)
134
+	}
135
+
136
+	if volume.IsBindMount {
137
+		return fmt.Errorf("Volume %s is a bind-mount and cannot be removed", volume.Path)
138
+	}
139
+	if len(volume.Containers) > 0 {
140
+		return fmt.Errorf("Volume %s is being used and cannot be removed: used by containers %s", volume.Path, volume.Containers)
141
+	}
142
+
143
+	if err := os.RemoveAll(volume.configPath); err != nil {
144
+		return err
145
+	}
146
+
147
+	if err := r.driver.Remove(volume.ID); err != nil {
148
+		if !os.IsNotExist(err) {
149
+			return err
150
+		}
151
+	}
152
+
153
+	r.remove(volume)
154
+	return nil
155
+}
156
+
157
+func (r *Repository) createNewVolumePath(id string) (string, error) {
158
+	if err := r.driver.Create(id, ""); err != nil {
159
+		return "", err
160
+	}
161
+
162
+	path, err := r.driver.Get(id, "")
163
+	if err != nil {
164
+		return "", fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", r.driver, id, err)
165
+	}
166
+
167
+	return path, nil
168
+}
0 169
new file mode 100644
... ...
@@ -0,0 +1,127 @@
0
+package volumes
1
+
2
+import (
3
+	"encoding/json"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"sync"
8
+
9
+	"github.com/docker/docker/pkg/symlink"
10
+)
11
+
12
+type Volume struct {
13
+	ID          string
14
+	Path        string
15
+	IsBindMount bool
16
+	Writable    bool
17
+	Containers  map[string]struct{}
18
+	configPath  string
19
+	repository  *Repository
20
+	lock        sync.Mutex
21
+}
22
+
23
+func (v *Volume) IsDir() (bool, error) {
24
+	stat, err := os.Stat(v.Path)
25
+	if err != nil {
26
+		return false, err
27
+	}
28
+
29
+	return stat.IsDir(), nil
30
+}
31
+
32
+func (v *Volume) RemoveContainer(containerId string) {
33
+	v.lock.Lock()
34
+	delete(v.Containers, containerId)
35
+	v.lock.Unlock()
36
+}
37
+
38
+func (v *Volume) AddContainer(containerId string) {
39
+	v.lock.Lock()
40
+	v.Containers[containerId] = struct{}{}
41
+	v.lock.Unlock()
42
+}
43
+
44
+func (v *Volume) createIfNotExist() error {
45
+	if stat, err := os.Stat(v.Path); err != nil && os.IsNotExist(err) {
46
+		if stat.IsDir() {
47
+			os.MkdirAll(v.Path, 0755)
48
+		}
49
+
50
+		if err := os.MkdirAll(filepath.Dir(v.Path), 0755); err != nil {
51
+			return err
52
+		}
53
+		f, err := os.OpenFile(v.Path, os.O_CREATE, 0755)
54
+		if err != nil {
55
+			return err
56
+		}
57
+		f.Close()
58
+	}
59
+	return nil
60
+}
61
+
62
+func (v *Volume) initialize() error {
63
+	v.lock.Lock()
64
+	defer v.lock.Unlock()
65
+
66
+	if err := v.createIfNotExist(); err != nil {
67
+		return err
68
+	}
69
+
70
+	if err := os.MkdirAll(v.configPath, 0755); err != nil {
71
+		return err
72
+	}
73
+	jsonPath, err := v.jsonPath()
74
+	if err != nil {
75
+		return err
76
+	}
77
+	f, err := os.Create(jsonPath)
78
+	if err != nil {
79
+		return err
80
+	}
81
+	defer f.Close()
82
+
83
+	return v.toDisk()
84
+}
85
+
86
+func (v *Volume) ToDisk() error {
87
+	v.lock.Lock()
88
+	defer v.lock.Unlock()
89
+	return v.toDisk()
90
+}
91
+func (v *Volume) toDisk() error {
92
+	data, err := json.Marshal(v)
93
+	if err != nil {
94
+		return err
95
+	}
96
+
97
+	pth, err := v.jsonPath()
98
+	if err != nil {
99
+		return err
100
+	}
101
+
102
+	return ioutil.WriteFile(pth, data, 0666)
103
+}
104
+func (v *Volume) FromDisk() error {
105
+	v.lock.Lock()
106
+	defer v.lock.Unlock()
107
+	pth, err := v.jsonPath()
108
+	if err != nil {
109
+		return err
110
+	}
111
+
112
+	data, err := ioutil.ReadFile(pth)
113
+	if err != nil {
114
+		return err
115
+	}
116
+
117
+	return json.Unmarshal(data, v)
118
+}
119
+
120
+func (v *Volume) jsonPath() (string, error) {
121
+	return v.getRootResourcePath("config.json")
122
+}
123
+func (v *Volume) getRootResourcePath(path string) (string, error) {
124
+	cleanPath := filepath.Join("/", path)
125
+	return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath)
126
+}