1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package os_test
6
7import (
8	"io/ioutil"
9	. "os"
10	"path/filepath"
11	"runtime"
12	"syscall"
13	"testing"
14)
15
16var isReadonlyError = func(error) bool { return false }
17
18func TestMkdirAll(t *testing.T) {
19	tmpDir := TempDir()
20	path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
21	err := MkdirAll(path, 0777)
22	if err != nil {
23		t.Fatalf("MkdirAll %q: %s", path, err)
24	}
25	defer RemoveAll(tmpDir + "/_TestMkdirAll_")
26
27	// Already exists, should succeed.
28	err = MkdirAll(path, 0777)
29	if err != nil {
30		t.Fatalf("MkdirAll %q (second time): %s", path, err)
31	}
32
33	// Make file.
34	fpath := path + "/file"
35	f, err := Create(fpath)
36	if err != nil {
37		t.Fatalf("create %q: %s", fpath, err)
38	}
39	defer f.Close()
40
41	// Can't make directory named after file.
42	err = MkdirAll(fpath, 0777)
43	if err == nil {
44		t.Fatalf("MkdirAll %q: no error", fpath)
45	}
46	perr, ok := err.(*PathError)
47	if !ok {
48		t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
49	}
50	if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
51		t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
52	}
53
54	// Can't make subdirectory of file.
55	ffpath := fpath + "/subdir"
56	err = MkdirAll(ffpath, 0777)
57	if err == nil {
58		t.Fatalf("MkdirAll %q: no error", ffpath)
59	}
60	perr, ok = err.(*PathError)
61	if !ok {
62		t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
63	}
64	if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
65		t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
66	}
67
68	if runtime.GOOS == "windows" {
69		path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
70		err := MkdirAll(path, 0777)
71		if err != nil {
72			t.Fatalf("MkdirAll %q: %s", path, err)
73		}
74	}
75}
76
77func TestRemoveAll(t *testing.T) {
78	tmpDir := TempDir()
79	// Work directory.
80	path := tmpDir + "/_TestRemoveAll_"
81	fpath := path + "/file"
82	dpath := path + "/dir"
83
84	// Make directory with 1 file and remove.
85	if err := MkdirAll(path, 0777); err != nil {
86		t.Fatalf("MkdirAll %q: %s", path, err)
87	}
88	fd, err := Create(fpath)
89	if err != nil {
90		t.Fatalf("create %q: %s", fpath, err)
91	}
92	fd.Close()
93	if err = RemoveAll(path); err != nil {
94		t.Fatalf("RemoveAll %q (first): %s", path, err)
95	}
96	if _, err = Lstat(path); err == nil {
97		t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path)
98	}
99
100	// Make directory with file and subdirectory and remove.
101	if err = MkdirAll(dpath, 0777); err != nil {
102		t.Fatalf("MkdirAll %q: %s", dpath, err)
103	}
104	fd, err = Create(fpath)
105	if err != nil {
106		t.Fatalf("create %q: %s", fpath, err)
107	}
108	fd.Close()
109	fd, err = Create(dpath + "/file")
110	if err != nil {
111		t.Fatalf("create %q: %s", fpath, err)
112	}
113	fd.Close()
114	if err = RemoveAll(path); err != nil {
115		t.Fatalf("RemoveAll %q (second): %s", path, err)
116	}
117	if _, err := Lstat(path); err == nil {
118		t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
119	}
120
121	// Determine if we should run the following test.
122	testit := true
123	if runtime.GOOS == "windows" {
124		// Chmod is not supported under windows.
125		testit = false
126	} else {
127		// Test fails as root.
128		testit = Getuid() != 0
129	}
130	if testit {
131		// Make directory with file and subdirectory and trigger error.
132		if err = MkdirAll(dpath, 0777); err != nil {
133			t.Fatalf("MkdirAll %q: %s", dpath, err)
134		}
135
136		for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
137			fd, err = Create(s)
138			if err != nil {
139				t.Fatalf("create %q: %s", s, err)
140			}
141			fd.Close()
142		}
143		if err = Chmod(dpath, 0); err != nil {
144			t.Fatalf("Chmod %q 0: %s", dpath, err)
145		}
146
147		// No error checking here: either RemoveAll
148		// will or won't be able to remove dpath;
149		// either way we want to see if it removes fpath
150		// and path/zzz.  Reasons why RemoveAll might
151		// succeed in removing dpath as well include:
152		//	* running as root
153		//	* running on a file system without permissions (FAT)
154		RemoveAll(path)
155		Chmod(dpath, 0777)
156
157		for _, s := range []string{fpath, path + "/zzz"} {
158			if _, err = Lstat(s); err == nil {
159				t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
160			}
161		}
162	}
163	if err = RemoveAll(path); err != nil {
164		t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
165	}
166	if _, err = Lstat(path); err == nil {
167		t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
168	}
169}
170
171func TestMkdirAllWithSymlink(t *testing.T) {
172	switch runtime.GOOS {
173	case "android", "nacl", "plan9":
174		t.Skipf("skipping on %s", runtime.GOOS)
175	case "windows":
176		if !supportsSymlinks {
177			t.Skipf("skipping on %s", runtime.GOOS)
178		}
179	}
180
181	tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
182	if err != nil {
183		t.Fatal(err)
184	}
185	defer RemoveAll(tmpDir)
186
187	dir := tmpDir + "/dir"
188	err = Mkdir(dir, 0755)
189	if err != nil {
190		t.Fatalf("Mkdir %s: %s", dir, err)
191	}
192
193	link := tmpDir + "/link"
194	err = Symlink("dir", link)
195	if err != nil {
196		t.Fatalf("Symlink %s: %s", link, err)
197	}
198
199	path := link + "/foo"
200	err = MkdirAll(path, 0755)
201	if err != nil {
202		t.Errorf("MkdirAll %q: %s", path, err)
203	}
204}
205
206func TestMkdirAllAtSlash(t *testing.T) {
207	switch runtime.GOOS {
208	case "android", "plan9", "windows":
209		t.Skipf("skipping on %s", runtime.GOOS)
210	case "darwin":
211		switch runtime.GOARCH {
212		case "arm", "arm64":
213			t.Skipf("skipping on darwin/%s, mkdir returns EPERM", runtime.GOARCH)
214		}
215	}
216	RemoveAll("/_go_os_test")
217	const dir = "/_go_os_test/dir"
218	err := MkdirAll(dir, 0777)
219	if err != nil {
220		pathErr, ok := err.(*PathError)
221		// common for users not to be able to write to /
222		if ok && (pathErr.Err == syscall.EACCES || isReadonlyError(pathErr.Err)) {
223			t.Skipf("could not create %v: %v", dir, err)
224		}
225		t.Fatalf(`MkdirAll "/_go_os_test/dir": %v, %s`, err, pathErr.Err)
226	}
227	RemoveAll("/_go_os_test")
228}
229