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