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	"errors"
9	"internal/testenv"
10	"io/fs"
11	"os"
12	"path/filepath"
13	"runtime"
14	"testing"
15)
16
17type testStatAndLstatParams struct {
18	isLink     bool
19	statCheck  func(*testing.T, string, fs.FileInfo)
20	lstatCheck func(*testing.T, string, fs.FileInfo)
21}
22
23// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
24func testStatAndLstat(t *testing.T, path string, params testStatAndLstatParams) {
25	// test os.Stat
26	sfi, err := os.Stat(path)
27	if err != nil {
28		t.Error(err)
29		return
30	}
31	params.statCheck(t, path, sfi)
32
33	// test os.Lstat
34	lsfi, err := os.Lstat(path)
35	if err != nil {
36		t.Error(err)
37		return
38	}
39	params.lstatCheck(t, path, lsfi)
40
41	if params.isLink {
42		if os.SameFile(sfi, lsfi) {
43			t.Errorf("stat and lstat of %q should not be the same", path)
44		}
45	} else {
46		if !os.SameFile(sfi, lsfi) {
47			t.Errorf("stat and lstat of %q should be the same", path)
48		}
49	}
50
51	// test os.File.Stat
52	f, err := os.Open(path)
53	if err != nil {
54		t.Error(err)
55		return
56	}
57	defer f.Close()
58
59	sfi2, err := f.Stat()
60	if err != nil {
61		t.Error(err)
62		return
63	}
64	params.statCheck(t, path, sfi2)
65
66	if !os.SameFile(sfi, sfi2) {
67		t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
68	}
69
70	if params.isLink {
71		if os.SameFile(sfi2, lsfi) {
72			t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
73		}
74	} else {
75		if !os.SameFile(sfi2, lsfi) {
76			t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path)
77		}
78	}
79
80	parentdir, base := filepath.Split(path)
81	if parentdir == "" || base == "" {
82		// skip os.Readdir test of files without directory or file name component,
83		// such as directories with slash at the end or Windows device names.
84		return
85	}
86
87	parent, err := os.Open(parentdir)
88	if err != nil {
89		t.Error(err)
90		return
91	}
92	defer parent.Close()
93
94	fis, err := parent.Readdir(-1)
95	if err != nil {
96		t.Error(err)
97		return
98	}
99	var lsfi2 fs.FileInfo
100	for _, fi2 := range fis {
101		if fi2.Name() == base {
102			lsfi2 = fi2
103			break
104		}
105	}
106	if lsfi2 == nil {
107		t.Errorf("failed to find %q in its parent", path)
108		return
109	}
110	params.lstatCheck(t, path, lsfi2)
111
112	if !os.SameFile(lsfi, lsfi2) {
113		t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
114	}
115}
116
117// testIsDir verifies that fi refers to directory.
118func testIsDir(t *testing.T, path string, fi fs.FileInfo) {
119	t.Helper()
120	if !fi.IsDir() {
121		t.Errorf("%q should be a directory", path)
122	}
123	if fi.Mode()&fs.ModeSymlink != 0 {
124		t.Errorf("%q should not be a symlink", path)
125	}
126}
127
128// testIsSymlink verifies that fi refers to symlink.
129func testIsSymlink(t *testing.T, path string, fi fs.FileInfo) {
130	t.Helper()
131	if fi.IsDir() {
132		t.Errorf("%q should not be a directory", path)
133	}
134	if fi.Mode()&fs.ModeSymlink == 0 {
135		t.Errorf("%q should be a symlink", path)
136	}
137}
138
139// testIsFile verifies that fi refers to file.
140func testIsFile(t *testing.T, path string, fi fs.FileInfo) {
141	t.Helper()
142	if fi.IsDir() {
143		t.Errorf("%q should not be a directory", path)
144	}
145	if fi.Mode()&fs.ModeSymlink != 0 {
146		t.Errorf("%q should not be a symlink", path)
147	}
148}
149
150func testDirStats(t *testing.T, path string) {
151	params := testStatAndLstatParams{
152		isLink:     false,
153		statCheck:  testIsDir,
154		lstatCheck: testIsDir,
155	}
156	testStatAndLstat(t, path, params)
157}
158
159func testFileStats(t *testing.T, path string) {
160	params := testStatAndLstatParams{
161		isLink:     false,
162		statCheck:  testIsFile,
163		lstatCheck: testIsFile,
164	}
165	testStatAndLstat(t, path, params)
166}
167
168func testSymlinkStats(t *testing.T, path string, isdir bool) {
169	params := testStatAndLstatParams{
170		isLink:     true,
171		lstatCheck: testIsSymlink,
172	}
173	if isdir {
174		params.statCheck = testIsDir
175	} else {
176		params.statCheck = testIsFile
177	}
178	testStatAndLstat(t, path, params)
179}
180
181func testSymlinkSameFile(t *testing.T, path, link string) {
182	pathfi, err := os.Stat(path)
183	if err != nil {
184		t.Error(err)
185		return
186	}
187
188	linkfi, err := os.Stat(link)
189	if err != nil {
190		t.Error(err)
191		return
192	}
193	if !os.SameFile(pathfi, linkfi) {
194		t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link)
195	}
196
197	linkfi, err = os.Lstat(link)
198	if err != nil {
199		t.Error(err)
200		return
201	}
202	if os.SameFile(pathfi, linkfi) {
203		t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link)
204	}
205}
206
207func testSymlinkSameFileOpen(t *testing.T, link string) {
208	f, err := os.Open(link)
209	if err != nil {
210		t.Error(err)
211		return
212	}
213	defer f.Close()
214
215	fi, err := f.Stat()
216	if err != nil {
217		t.Error(err)
218		return
219	}
220
221	fi2, err := os.Stat(link)
222	if err != nil {
223		t.Error(err)
224		return
225	}
226
227	if !os.SameFile(fi, fi2) {
228		t.Errorf("os.Open(%q).Stat() and os.Stat(%q) are not the same file", link, link)
229	}
230}
231
232func TestDirAndSymlinkStats(t *testing.T) {
233	testenv.MustHaveSymlink(t)
234	t.Parallel()
235
236	tmpdir := t.TempDir()
237	dir := filepath.Join(tmpdir, "dir")
238	if err := os.Mkdir(dir, 0777); err != nil {
239		t.Fatal(err)
240	}
241	testDirStats(t, dir)
242
243	dirlink := filepath.Join(tmpdir, "link")
244	if err := os.Symlink(dir, dirlink); err != nil {
245		t.Fatal(err)
246	}
247	testSymlinkStats(t, dirlink, true)
248	testSymlinkSameFile(t, dir, dirlink)
249	testSymlinkSameFileOpen(t, dirlink)
250
251	linklink := filepath.Join(tmpdir, "linklink")
252	if err := os.Symlink(dirlink, linklink); err != nil {
253		t.Fatal(err)
254	}
255	testSymlinkStats(t, linklink, true)
256	testSymlinkSameFile(t, dir, linklink)
257	testSymlinkSameFileOpen(t, linklink)
258}
259
260func TestFileAndSymlinkStats(t *testing.T) {
261	testenv.MustHaveSymlink(t)
262	t.Parallel()
263
264	tmpdir := t.TempDir()
265	file := filepath.Join(tmpdir, "file")
266	if err := os.WriteFile(file, []byte(""), 0644); err != nil {
267		t.Fatal(err)
268	}
269	testFileStats(t, file)
270
271	filelink := filepath.Join(tmpdir, "link")
272	if err := os.Symlink(file, filelink); err != nil {
273		t.Fatal(err)
274	}
275	testSymlinkStats(t, filelink, false)
276	testSymlinkSameFile(t, file, filelink)
277	testSymlinkSameFileOpen(t, filelink)
278
279	linklink := filepath.Join(tmpdir, "linklink")
280	if err := os.Symlink(filelink, linklink); err != nil {
281		t.Fatal(err)
282	}
283	testSymlinkStats(t, linklink, false)
284	testSymlinkSameFile(t, file, linklink)
285	testSymlinkSameFileOpen(t, linklink)
286}
287
288// see issue 27225 for details
289func TestSymlinkWithTrailingSlash(t *testing.T) {
290	testenv.MustHaveSymlink(t)
291	t.Parallel()
292
293	tmpdir := t.TempDir()
294	dir := filepath.Join(tmpdir, "dir")
295	if err := os.Mkdir(dir, 0777); err != nil {
296		t.Fatal(err)
297	}
298	dirlink := filepath.Join(tmpdir, "link")
299	if err := os.Symlink(dir, dirlink); err != nil {
300		t.Fatal(err)
301	}
302	dirlinkWithSlash := dirlink + string(os.PathSeparator)
303
304	testDirStats(t, dirlinkWithSlash)
305
306	fi1, err := os.Stat(dir)
307	if err != nil {
308		t.Error(err)
309		return
310	}
311	fi2, err := os.Stat(dirlinkWithSlash)
312	if err != nil {
313		t.Error(err)
314		return
315	}
316	if !os.SameFile(fi1, fi2) {
317		t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
318	}
319}
320
321func TestStatConsole(t *testing.T) {
322	if runtime.GOOS != "windows" {
323		t.Skip("skipping on non-Windows")
324	}
325	t.Parallel()
326	consoleNames := []string{
327		"CONIN$",
328		"CONOUT$",
329		"CON",
330	}
331	for _, name := range consoleNames {
332		params := testStatAndLstatParams{
333			isLink:     false,
334			statCheck:  testIsFile,
335			lstatCheck: testIsFile,
336		}
337		testStatAndLstat(t, name, params)
338		testStatAndLstat(t, `\\.\`+name, params)
339	}
340}
341
342func TestClosedStat(t *testing.T) {
343	// Historically we do not seem to match ErrClosed on non-Unix systems.
344	switch runtime.GOOS {
345	case "windows", "plan9":
346		t.Skipf("skipping on %s", runtime.GOOS)
347	}
348
349	t.Parallel()
350	f, err := os.Open("testdata/hello")
351	if err != nil {
352		t.Fatal(err)
353	}
354	if err := f.Close(); err != nil {
355		t.Fatal(err)
356	}
357	_, err = f.Stat()
358	if err == nil {
359		t.Error("Stat succeeded on closed File")
360	} else if !errors.Is(err, os.ErrClosed) {
361		t.Errorf("error from Stat on closed file did not match ErrClosed: %q, type %T", err, err)
362	}
363}
364