Browse code

pkg/fileutils: move ReadSymlinkedDirectory internal to daemon

It has no external consumers, is written with specific behavior (including
some potentially surprising behavior), making it not a good candidate to
carry in the module.

This moves it internal to the daemon as a non-exported utility, so that
it's only accessible where it's currently used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/07/29 18:03:10
Showing 4 changed files
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"github.com/docker/docker/daemon/container"
19 19
 	"github.com/docker/docker/daemon/internal/mounttree"
20 20
 	"github.com/docker/docker/daemon/internal/unshare"
21
-	"github.com/docker/docker/pkg/fileutils"
22 21
 	containertypes "github.com/moby/moby/api/types/container"
23 22
 )
24 23
 
... ...
@@ -104,7 +103,7 @@ func (daemon *Daemon) openContainerFS(ctr *container.Container) (_ *containerFSV
104 104
 				if err != nil {
105 105
 					return err
106 106
 				}
107
-				if err := fileutils.CreateIfNotExists(dest, stat.IsDir()); err != nil {
107
+				if err := createIfNotExists(dest, stat.IsDir()); err != nil {
108 108
 					return err
109 109
 				}
110 110
 
... ...
@@ -252,6 +251,27 @@ func (vw *containerFSView) Stat(ctx context.Context, path string) (*containertyp
252 252
 	return stat, err
253 253
 }
254 254
 
255
+// createIfNotExists creates a file or a directory only if it does not already exist.
256
+func createIfNotExists(dest string, isDir bool) error {
257
+	if _, err := os.Stat(dest); err != nil {
258
+		// FIXME(thaJeztah): this ignores any other error (which may include "dest" is of the wrong type, or permission errors).
259
+		if os.IsNotExist(err) {
260
+			if isDir {
261
+				return os.MkdirAll(dest, 0o755)
262
+			}
263
+			if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
264
+				return err
265
+			}
266
+			f, err := os.OpenFile(dest, os.O_CREATE, 0o755)
267
+			if err != nil {
268
+				return err
269
+			}
270
+			_ = f.Close()
271
+		}
272
+	}
273
+	return nil
274
+}
275
+
255 276
 // makeMountRRO makes the mount recursively read-only.
256 277
 func makeMountRRO(dest string) error {
257 278
 	attr := &unix.MountAttr{
258 279
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+package daemon
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+	"testing"
6
+
7
+	"gotest.tools/v3/assert"
8
+)
9
+
10
+func TestCreateIfNotExists(t *testing.T) {
11
+	t.Run("directory", func(t *testing.T) {
12
+		toCreate := filepath.Join(t.TempDir(), "tocreate")
13
+
14
+		err := createIfNotExists(toCreate, true)
15
+		assert.NilError(t, err)
16
+
17
+		fileinfo, err := os.Stat(toCreate)
18
+		assert.NilError(t, err, "Did not create destination")
19
+		assert.Assert(t, fileinfo.IsDir(), "Should have been a dir, seems it's not")
20
+
21
+		err = createIfNotExists(toCreate, true)
22
+		assert.NilError(t, err, "Should not fail if already exists")
23
+	})
24
+	t.Run("file", func(t *testing.T) {
25
+		toCreate := filepath.Join(t.TempDir(), "file/to/create")
26
+
27
+		err := createIfNotExists(toCreate, false)
28
+		assert.NilError(t, err)
29
+
30
+		fileinfo, err := os.Stat(toCreate)
31
+		assert.NilError(t, err, "Did not create destination")
32
+
33
+		assert.Assert(t, !fileinfo.IsDir(), "Should have been a file, but created a directory")
34
+
35
+		err = createIfNotExists(toCreate, true)
36
+		assert.NilError(t, err, "Should not fail if already exists")
37
+	})
38
+}
... ...
@@ -27,23 +27,3 @@ func ReadSymlinkedDirectory(path string) (realPath string, _ error) {
27 27
 	}
28 28
 	return realPath, nil
29 29
 }
30
-
31
-// CreateIfNotExists creates a file or a directory only if it does not already exist.
32
-func CreateIfNotExists(path string, isDir bool) error {
33
-	if _, err := os.Stat(path); err != nil {
34
-		if os.IsNotExist(err) {
35
-			if isDir {
36
-				return os.MkdirAll(path, 0o755)
37
-			}
38
-			if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
39
-				return err
40
-			}
41
-			f, err := os.OpenFile(path, os.O_CREATE, 0o755)
42
-			if err != nil {
43
-				return err
44
-			}
45
-			_ = f.Close()
46
-		}
47
-	}
48
-	return nil
49
-}
... ...
@@ -107,35 +107,3 @@ func TestReadSymlinkedDirectoryToFile(t *testing.T) {
107 107
 		t.Errorf("failed to remove symlink: %s", err)
108 108
 	}
109 109
 }
110
-
111
-func TestCreateIfNotExistsDir(t *testing.T) {
112
-	folderToCreate := filepath.Join(t.TempDir(), "tocreate")
113
-
114
-	if err := CreateIfNotExists(folderToCreate, true); err != nil {
115
-		t.Fatal(err)
116
-	}
117
-	fileinfo, err := os.Stat(folderToCreate)
118
-	if err != nil {
119
-		t.Fatalf("Should have create a folder, got %v", err)
120
-	}
121
-
122
-	if !fileinfo.IsDir() {
123
-		t.Errorf("Should have been a dir, seems it's not")
124
-	}
125
-}
126
-
127
-func TestCreateIfNotExistsFile(t *testing.T) {
128
-	fileToCreate := filepath.Join(t.TempDir(), "file/to/create")
129
-
130
-	if err := CreateIfNotExists(fileToCreate, false); err != nil {
131
-		t.Error(err)
132
-	}
133
-	fileinfo, err := os.Stat(fileToCreate)
134
-	if err != nil {
135
-		t.Fatalf("Should have create a file, got %v", err)
136
-	}
137
-
138
-	if fileinfo.IsDir() {
139
-		t.Errorf("Should have been a file, seems it's not")
140
-	}
141
-}