1// Copyright 2018 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	"internal/testenv"
9	"io/fs"
10	"os"
11	"path/filepath"
12	"runtime"
13	"testing"
14)
15
16// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
17func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) {
18	// test os.Stat
19	sfi, err := os.Stat(path)
20	if err != nil {
21		t.Error(err)
22		return
23	}
24	statCheck(t, path, sfi)
25
26	// test os.Lstat
27	lsfi, err := os.Lstat(path)
28	if err != nil {
29		t.Error(err)
30		return
31	}
32	lstatCheck(t, path, lsfi)
33
34	if isLink {
35		if os.SameFile(sfi, lsfi) {
36			t.Errorf("stat and lstat of %q should not be the same", path)
37		}
38	} else {
39		if !os.SameFile(sfi, lsfi) {
40			t.Errorf("stat and lstat of %q should be the same", path)
41		}
42	}
43
44	// test os.File.Stat
45	f, err := os.Open(path)
46	if err != nil {
47		t.Error(err)
48		return
49	}
50	defer f.Close()
51
52	sfi2, err := f.Stat()
53	if err != nil {
54		t.Error(err)
55		return
56	}
57	statCheck(t, path, sfi2)
58
59	if !os.SameFile(sfi, sfi2) {
60		t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
61	}
62
63	if isLink {
64		if os.SameFile(sfi2, lsfi) {
65			t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
66		}
67	} else {
68		if !os.SameFile(sfi2, lsfi) {
69			t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path)
70		}
71	}
72
73	// test fs.FileInfo returned by os.Readdir
74	if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
75		// skip os.Readdir test of directories with slash at the end
76		return
77	}
78	parentdir := filepath.Dir(path)
79	parent, err := os.Open(parentdir)
80	if err != nil {
81		t.Error(err)
82		return
83	}
84	defer parent.Close()
85
86	fis, err := parent.Readdir(-1)
87	if err != nil {
88		t.Error(err)
89		return
90	}
91	var lsfi2 fs.FileInfo
92	base := filepath.Base(path)
93	for _, fi2 := range fis {
94		if fi2.Name() == base {
95			lsfi2 = fi2
96			break
97		}
98	}
99	if lsfi2 == nil {
100		t.Errorf("failed to find %q in its parent", path)
101		return
102	}
103	lstatCheck(t, path, lsfi2)
104
105	if !os.SameFile(lsfi, lsfi2) {
106		t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
107	}
108}
109
110// testIsDir verifies that fi refers to directory.
111func testIsDir(t *testing.T, path string, fi fs.FileInfo) {
112	t.Helper()
113	if !fi.IsDir() {
114		t.Errorf("%q should be a directory", path)
115	}
116	if fi.Mode()&fs.ModeSymlink != 0 {
117		t.Errorf("%q should not be a symlink", path)
118	}
119}
120
121// testIsSymlink verifies that fi refers to symlink.
122func testIsSymlink(t *testing.T, path string, fi fs.FileInfo) {
123	t.Helper()
124	if fi.IsDir() {
125		t.Errorf("%q should not be a directory", path)
126	}
127	if fi.Mode()&fs.ModeSymlink == 0 {
128		t.Errorf("%q should be a symlink", path)
129	}
130}
131
132// testIsFile verifies that fi refers to file.
133func testIsFile(t *testing.T, path string, fi fs.FileInfo) {
134	t.Helper()
135	if fi.IsDir() {
136		t.Errorf("%q should not be a directory", path)
137	}
138	if fi.Mode()&fs.ModeSymlink != 0 {
139		t.Errorf("%q should not be a symlink", path)
140	}
141}
142
143func testDirStats(t *testing.T, path string) {
144	testStatAndLstat(t, path, false, testIsDir, testIsDir)
145}
146
147func testFileStats(t *testing.T, path string) {
148	testStatAndLstat(t, path, false, testIsFile, testIsFile)
149}
150
151func testSymlinkStats(t *testing.T, path string, isdir bool) {
152	if isdir {
153		testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
154	} else {
155		testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
156	}
157}
158
159func testSymlinkSameFile(t *testing.T, path, link string) {
160	pathfi, err := os.Stat(path)
161	if err != nil {
162		t.Error(err)
163		return
164	}
165
166	linkfi, err := os.Stat(link)
167	if err != nil {
168		t.Error(err)
169		return
170	}
171	if !os.SameFile(pathfi, linkfi) {
172		t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link)
173	}
174
175	linkfi, err = os.Lstat(link)
176	if err != nil {
177		t.Error(err)
178		return
179	}
180	if os.SameFile(pathfi, linkfi) {
181		t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link)
182	}
183}
184
185func TestDirAndSymlinkStats(t *testing.T) {
186	testenv.MustHaveSymlink(t)
187
188	tmpdir, err := os.MkdirTemp("", "TestDirAndSymlinkStats")
189	if err != nil {
190		t.Fatal(err)
191	}
192	defer os.RemoveAll(tmpdir)
193
194	dir := filepath.Join(tmpdir, "dir")
195	err = os.Mkdir(dir, 0777)
196	if err != nil {
197		t.Fatal(err)
198	}
199	testDirStats(t, dir)
200
201	dirlink := filepath.Join(tmpdir, "link")
202	err = os.Symlink(dir, dirlink)
203	if err != nil {
204		t.Fatal(err)
205	}
206	testSymlinkStats(t, dirlink, true)
207	testSymlinkSameFile(t, dir, dirlink)
208
209	linklink := filepath.Join(tmpdir, "linklink")
210	err = os.Symlink(dirlink, linklink)
211	if err != nil {
212		t.Fatal(err)
213	}
214	testSymlinkStats(t, linklink, true)
215	testSymlinkSameFile(t, dir, linklink)
216}
217
218func TestFileAndSymlinkStats(t *testing.T) {
219	testenv.MustHaveSymlink(t)
220
221	tmpdir, err := os.MkdirTemp("", "TestFileAndSymlinkStats")
222	if err != nil {
223		t.Fatal(err)
224	}
225	defer os.RemoveAll(tmpdir)
226
227	file := filepath.Join(tmpdir, "file")
228	err = os.WriteFile(file, []byte(""), 0644)
229	if err != nil {
230		t.Fatal(err)
231	}
232	testFileStats(t, file)
233
234	filelink := filepath.Join(tmpdir, "link")
235	err = os.Symlink(file, filelink)
236	if err != nil {
237		t.Fatal(err)
238	}
239	testSymlinkStats(t, filelink, false)
240	testSymlinkSameFile(t, file, filelink)
241
242	linklink := filepath.Join(tmpdir, "linklink")
243	err = os.Symlink(filelink, linklink)
244	if err != nil {
245		t.Fatal(err)
246	}
247	testSymlinkStats(t, linklink, false)
248	testSymlinkSameFile(t, file, linklink)
249}
250
251// see issue 27225 for details
252func TestSymlinkWithTrailingSlash(t *testing.T) {
253	testenv.MustHaveSymlink(t)
254
255	tmpdir, err := os.MkdirTemp("", "TestSymlinkWithTrailingSlash")
256	if err != nil {
257		t.Fatal(err)
258	}
259	defer os.RemoveAll(tmpdir)
260
261	dir := filepath.Join(tmpdir, "dir")
262	err = os.Mkdir(dir, 0777)
263	if err != nil {
264		t.Fatal(err)
265	}
266	dirlink := filepath.Join(tmpdir, "link")
267	err = os.Symlink(dir, dirlink)
268	if err != nil {
269		t.Fatal(err)
270	}
271	dirlinkWithSlash := dirlink + string(os.PathSeparator)
272
273	if runtime.GOOS == "windows" {
274		testSymlinkStats(t, dirlinkWithSlash, true)
275	} else {
276		testDirStats(t, dirlinkWithSlash)
277	}
278
279	fi1, err := os.Stat(dir)
280	if err != nil {
281		t.Error(err)
282		return
283	}
284	fi2, err := os.Stat(dirlinkWithSlash)
285	if err != nil {
286		t.Error(err)
287		return
288	}
289	if !os.SameFile(fi1, fi2) {
290		t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
291	}
292}
293