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 5//go:build unix || (js && wasm) || wasip1 6 7package os_test 8 9import ( 10 "internal/testenv" 11 "io" 12 . "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 "syscall" 17 "testing" 18 "time" 19) 20 21func init() { 22 isReadonlyError = func(err error) bool { return err == syscall.EROFS } 23} 24 25// For TestRawConnReadWrite. 26type syscallDescriptor = int 27 28func checkUidGid(t *testing.T, path string, uid, gid int) { 29 dir, err := Lstat(path) 30 if err != nil { 31 t.Fatalf("Lstat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err) 32 } 33 sys := dir.Sys().(*syscall.Stat_t) 34 if int(sys.Uid) != uid { 35 t.Errorf("Lstat %q: uid %d want %d", path, sys.Uid, uid) 36 } 37 if int(sys.Gid) != gid { 38 t.Errorf("Lstat %q: gid %d want %d", path, sys.Gid, gid) 39 } 40} 41 42func TestChown(t *testing.T) { 43 if runtime.GOOS == "wasip1" { 44 t.Skip("file ownership not supported on " + runtime.GOOS) 45 } 46 t.Parallel() 47 48 f := newFile(t) 49 dir, err := f.Stat() 50 if err != nil { 51 t.Fatalf("stat %s: %s", f.Name(), err) 52 } 53 54 // Can't change uid unless root, but can try 55 // changing the group id. First try our current group. 56 gid := Getgid() 57 t.Log("gid:", gid) 58 if err = Chown(f.Name(), -1, gid); err != nil { 59 t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err) 60 } 61 sys := dir.Sys().(*syscall.Stat_t) 62 checkUidGid(t, f.Name(), int(sys.Uid), gid) 63 64 // Then try all the auxiliary groups. 65 groups, err := Getgroups() 66 if err != nil { 67 t.Fatalf("getgroups: %s", err) 68 } 69 t.Log("groups: ", groups) 70 for _, g := range groups { 71 if err = Chown(f.Name(), -1, g); err != nil { 72 if testenv.SyscallIsNotSupported(err) { 73 t.Logf("chown %s -1 %d: %s (error ignored)", f.Name(), g, err) 74 // Since the Chown call failed, the file should be unmodified. 75 checkUidGid(t, f.Name(), int(sys.Uid), gid) 76 continue 77 } 78 t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err) 79 } 80 checkUidGid(t, f.Name(), int(sys.Uid), g) 81 82 // change back to gid to test fd.Chown 83 if err = f.Chown(-1, gid); err != nil { 84 t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) 85 } 86 checkUidGid(t, f.Name(), int(sys.Uid), gid) 87 } 88} 89 90func TestFileChown(t *testing.T) { 91 if runtime.GOOS == "wasip1" { 92 t.Skip("file ownership not supported on " + runtime.GOOS) 93 } 94 t.Parallel() 95 96 f := newFile(t) 97 dir, err := f.Stat() 98 if err != nil { 99 t.Fatalf("stat %s: %s", f.Name(), err) 100 } 101 102 // Can't change uid unless root, but can try 103 // changing the group id. First try our current group. 104 gid := Getgid() 105 t.Log("gid:", gid) 106 if err = f.Chown(-1, gid); err != nil { 107 t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) 108 } 109 sys := dir.Sys().(*syscall.Stat_t) 110 checkUidGid(t, f.Name(), int(sys.Uid), gid) 111 112 // Then try all the auxiliary groups. 113 groups, err := Getgroups() 114 if err != nil { 115 t.Fatalf("getgroups: %s", err) 116 } 117 t.Log("groups: ", groups) 118 for _, g := range groups { 119 if err = f.Chown(-1, g); err != nil { 120 if testenv.SyscallIsNotSupported(err) { 121 t.Logf("chown %s -1 %d: %s (error ignored)", f.Name(), g, err) 122 // Since the Chown call failed, the file should be unmodified. 123 checkUidGid(t, f.Name(), int(sys.Uid), gid) 124 continue 125 } 126 t.Fatalf("fchown %s -1 %d: %s", f.Name(), g, err) 127 } 128 checkUidGid(t, f.Name(), int(sys.Uid), g) 129 130 // change back to gid to test fd.Chown 131 if err = f.Chown(-1, gid); err != nil { 132 t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) 133 } 134 checkUidGid(t, f.Name(), int(sys.Uid), gid) 135 } 136} 137 138func TestLchown(t *testing.T) { 139 testenv.MustHaveSymlink(t) 140 t.Parallel() 141 142 f := newFile(t) 143 dir, err := f.Stat() 144 if err != nil { 145 t.Fatalf("stat %s: %s", f.Name(), err) 146 } 147 148 linkname := f.Name() + "2" 149 if err := Symlink(f.Name(), linkname); err != nil { 150 if runtime.GOOS == "android" && IsPermission(err) { 151 t.Skip("skipping test on Android; permission error creating symlink") 152 } 153 t.Fatalf("link %s -> %s: %v", f.Name(), linkname, err) 154 } 155 defer Remove(linkname) 156 157 // Can't change uid unless root, but can try 158 // changing the group id. First try our current group. 159 gid := Getgid() 160 t.Log("gid:", gid) 161 if err = Lchown(linkname, -1, gid); err != nil { 162 if err, ok := err.(*PathError); ok && err.Err == syscall.ENOSYS { 163 t.Skip("lchown is unavailable") 164 } 165 t.Fatalf("lchown %s -1 %d: %s", linkname, gid, err) 166 } 167 sys := dir.Sys().(*syscall.Stat_t) 168 checkUidGid(t, linkname, int(sys.Uid), gid) 169 170 // Then try all the auxiliary groups. 171 groups, err := Getgroups() 172 if err != nil { 173 t.Fatalf("getgroups: %s", err) 174 } 175 t.Log("groups: ", groups) 176 for _, g := range groups { 177 if err = Lchown(linkname, -1, g); err != nil { 178 if testenv.SyscallIsNotSupported(err) { 179 t.Logf("lchown %s -1 %d: %s (error ignored)", f.Name(), g, err) 180 // Since the Lchown call failed, the file should be unmodified. 181 checkUidGid(t, f.Name(), int(sys.Uid), gid) 182 continue 183 } 184 t.Fatalf("lchown %s -1 %d: %s", linkname, g, err) 185 } 186 checkUidGid(t, linkname, int(sys.Uid), g) 187 188 // Check that link target's gid is unchanged. 189 checkUidGid(t, f.Name(), int(sys.Uid), int(sys.Gid)) 190 191 if err = Lchown(linkname, -1, gid); err != nil { 192 t.Fatalf("lchown %s -1 %d: %s", f.Name(), gid, err) 193 } 194 } 195} 196 197// Issue 16919: Readdir must return a non-empty slice or an error. 198func TestReaddirRemoveRace(t *testing.T) { 199 oldStat := *LstatP 200 defer func() { *LstatP = oldStat }() 201 *LstatP = func(name string) (FileInfo, error) { 202 if strings.HasSuffix(name, "some-file") { 203 // Act like it's been deleted. 204 return nil, ErrNotExist 205 } 206 return oldStat(name) 207 } 208 dir := t.TempDir() 209 if err := WriteFile(filepath.Join(dir, "some-file"), []byte("hello"), 0644); err != nil { 210 t.Fatal(err) 211 } 212 d, err := Open(dir) 213 if err != nil { 214 t.Fatal(err) 215 } 216 defer d.Close() 217 fis, err := d.Readdir(2) // notably, greater than zero 218 if len(fis) == 0 && err == nil { 219 // This is what used to happen (Issue 16919) 220 t.Fatal("Readdir = empty slice & err == nil") 221 } 222 if len(fis) != 0 || err != io.EOF { 223 t.Errorf("Readdir = %d entries: %v; want 0, io.EOF", len(fis), err) 224 for i, fi := range fis { 225 t.Errorf(" entry[%d]: %q, %v", i, fi.Name(), fi.Mode()) 226 } 227 t.FailNow() 228 } 229} 230 231// Issue 23120: respect umask when doing Mkdir with the sticky bit 232func TestMkdirStickyUmask(t *testing.T) { 233 if runtime.GOOS == "wasip1" { 234 t.Skip("file permissions not supported on " + runtime.GOOS) 235 } 236 t.Parallel() 237 238 const umask = 0077 239 dir := t.TempDir() 240 241 oldUmask := syscall.Umask(umask) 242 defer syscall.Umask(oldUmask) 243 244 // We have set a umask, but if the parent directory happens to have a default 245 // ACL, the umask may be ignored. To prevent spurious failures from an ACL, 246 // we create a non-sticky directory as a “control case” to compare against our 247 // sticky-bit “experiment”. 248 control := filepath.Join(dir, "control") 249 if err := Mkdir(control, 0755); err != nil { 250 t.Fatal(err) 251 } 252 cfi, err := Stat(control) 253 if err != nil { 254 t.Fatal(err) 255 } 256 257 p := filepath.Join(dir, "dir1") 258 if err := Mkdir(p, ModeSticky|0755); err != nil { 259 t.Fatal(err) 260 } 261 fi, err := Stat(p) 262 if err != nil { 263 t.Fatal(err) 264 } 265 266 got := fi.Mode() 267 want := cfi.Mode() | ModeSticky 268 if got != want { 269 t.Errorf("Mkdir(_, ModeSticky|0755) created dir with mode %v; want %v", got, want) 270 } 271} 272 273// See also issues: 22939, 24331 274func newFileTest(t *testing.T, blocking bool) { 275 if runtime.GOOS == "js" || runtime.GOOS == "wasip1" { 276 t.Skipf("syscall.Pipe is not available on %s.", runtime.GOOS) 277 } 278 279 p := make([]int, 2) 280 if err := syscall.Pipe(p); err != nil { 281 t.Fatalf("pipe: %v", err) 282 } 283 defer syscall.Close(p[1]) 284 285 // Set the read-side to non-blocking. 286 if !blocking { 287 if err := syscall.SetNonblock(p[0], true); err != nil { 288 syscall.Close(p[0]) 289 t.Fatalf("SetNonblock: %v", err) 290 } 291 } 292 // Convert it to a file. 293 file := NewFile(uintptr(p[0]), "notapipe") 294 if file == nil { 295 syscall.Close(p[0]) 296 t.Fatalf("failed to convert fd to file!") 297 } 298 defer file.Close() 299 300 timeToWrite := 100 * time.Millisecond 301 timeToDeadline := 1 * time.Millisecond 302 if !blocking { 303 // Use a longer time to avoid flakes. 304 // We won't be waiting this long anyhow. 305 timeToWrite = 1 * time.Second 306 } 307 308 // Try to read with deadline (but don't block forever). 309 b := make([]byte, 1) 310 timer := time.AfterFunc(timeToWrite, func() { syscall.Write(p[1], []byte("a")) }) 311 defer timer.Stop() 312 file.SetReadDeadline(time.Now().Add(timeToDeadline)) 313 _, err := file.Read(b) 314 if !blocking { 315 // We want it to fail with a timeout. 316 if !isDeadlineExceeded(err) { 317 t.Fatalf("No timeout reading from file: %v", err) 318 } 319 } else { 320 // We want it to succeed after 100ms 321 if err != nil { 322 t.Fatalf("Error reading from file: %v", err) 323 } 324 } 325} 326 327func TestNewFileBlock(t *testing.T) { 328 t.Parallel() 329 newFileTest(t, true) 330} 331 332func TestNewFileNonBlock(t *testing.T) { 333 t.Parallel() 334 newFileTest(t, false) 335} 336 337func TestNewFileInvalid(t *testing.T) { 338 t.Parallel() 339 const negOne = ^uintptr(0) 340 if f := NewFile(negOne, "invalid"); f != nil { 341 t.Errorf("NewFile(-1) got %v want nil", f) 342 } 343} 344 345func TestSplitPath(t *testing.T) { 346 t.Parallel() 347 for _, tt := range []struct{ path, wantDir, wantBase string }{ 348 {"a", ".", "a"}, 349 {"a/", ".", "a"}, 350 {"a//", ".", "a"}, 351 {"a/b", "a", "b"}, 352 {"a/b/", "a", "b"}, 353 {"a/b/c", "a/b", "c"}, 354 {"/a", "/", "a"}, 355 {"/a/", "/", "a"}, 356 {"/a/b", "/a", "b"}, 357 {"/a/b/", "/a", "b"}, 358 {"/a/b/c", "/a/b", "c"}, 359 {"//a", "/", "a"}, 360 {"//a/", "/", "a"}, 361 {"///a", "/", "a"}, 362 {"///a/", "/", "a"}, 363 } { 364 if dir, base := SplitPath(tt.path); dir != tt.wantDir || base != tt.wantBase { 365 t.Errorf("splitPath(%q) = %q, %q, want %q, %q", tt.path, dir, base, tt.wantDir, tt.wantBase) 366 } 367 } 368} 369 370// Test that copying to files opened with O_APPEND works and 371// the copy_file_range syscall isn't used on Linux. 372// 373// Regression test for go.dev/issue/60181 374func TestIssue60181(t *testing.T) { 375 defer chtmpdir(t)() 376 377 want := "hello gopher" 378 379 a, err := CreateTemp(".", "a") 380 if err != nil { 381 t.Fatal(err) 382 } 383 a.WriteString(want[:5]) 384 a.Close() 385 386 b, err := CreateTemp(".", "b") 387 if err != nil { 388 t.Fatal(err) 389 } 390 b.WriteString(want[5:]) 391 b.Close() 392 393 afd, err := syscall.Open(a.Name(), syscall.O_RDWR|syscall.O_APPEND, 0) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 bfd, err := syscall.Open(b.Name(), syscall.O_RDONLY, 0) 399 if err != nil { 400 t.Fatal(err) 401 } 402 403 aa := NewFile(uintptr(afd), a.Name()) 404 defer aa.Close() 405 bb := NewFile(uintptr(bfd), b.Name()) 406 defer bb.Close() 407 408 // This would fail on Linux in case the copy_file_range syscall was used because it doesn't 409 // support destination files opened with O_APPEND, see 410 // https://man7.org/linux/man-pages/man2/copy_file_range.2.html#ERRORS 411 _, err = io.Copy(aa, bb) 412 if err != nil { 413 t.Fatal(err) 414 } 415 416 buf, err := ReadFile(aa.Name()) 417 if err != nil { 418 t.Fatal(err) 419 } 420 421 if got := string(buf); got != want { 422 t.Errorf("files not concatenated: got %q, want %q", got, want) 423 } 424} 425