github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fdpipe/pipe_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fdpipe 16 17 import ( 18 "bytes" 19 "io" 20 "os" 21 "testing" 22 23 "golang.org/x/sys/unix" 24 "github.com/SagerNet/gvisor/pkg/errors" 25 "github.com/SagerNet/gvisor/pkg/errors/linuxerr" 26 "github.com/SagerNet/gvisor/pkg/fd" 27 "github.com/SagerNet/gvisor/pkg/fdnotifier" 28 "github.com/SagerNet/gvisor/pkg/hostarch" 29 "github.com/SagerNet/gvisor/pkg/sentry/contexttest" 30 "github.com/SagerNet/gvisor/pkg/sentry/fs" 31 "github.com/SagerNet/gvisor/pkg/syserror" 32 "github.com/SagerNet/gvisor/pkg/usermem" 33 ) 34 35 func singlePipeFD() (int, error) { 36 fds := make([]int, 2) 37 if err := unix.Pipe(fds); err != nil { 38 return -1, err 39 } 40 unix.Close(fds[1]) 41 return fds[0], nil 42 } 43 44 func singleDirFD() (int, error) { 45 return unix.Open(os.TempDir(), unix.O_RDONLY, 0666) 46 } 47 48 func mockPipeDirent(t *testing.T) *fs.Dirent { 49 ctx := contexttest.Context(t) 50 node := fs.NewMockInodeOperations(ctx) 51 node.UAttr = fs.UnstableAttr{ 52 Perms: fs.FilePermissions{ 53 User: fs.PermMask{Read: true, Write: true}, 54 }, 55 } 56 inode := fs.NewInode(ctx, node, fs.NewMockMountSource(nil), fs.StableAttr{ 57 Type: fs.Pipe, 58 BlockSize: hostarch.PageSize, 59 }) 60 return fs.NewDirent(ctx, inode, "") 61 } 62 63 func TestNewPipe(t *testing.T) { 64 for _, test := range []struct { 65 // desc is the test's description. 66 desc string 67 68 // getfd generates the fd to pass to newPipeOperations. 69 getfd func() (int, error) 70 71 // flags are the fs.FileFlags passed to newPipeOperations. 72 flags fs.FileFlags 73 74 // readAheadBuffer is the buffer passed to newPipeOperations. 75 readAheadBuffer []byte 76 77 // err is the expected error. 78 err error 79 }{ 80 { 81 desc: "Cannot make new pipe from bad fd", 82 getfd: func() (int, error) { return -1, nil }, 83 err: unix.EINVAL, 84 }, 85 { 86 desc: "Cannot make new pipe from non-pipe fd", 87 getfd: singleDirFD, 88 err: unix.EINVAL, 89 }, 90 { 91 desc: "Can make new pipe from pipe fd", 92 getfd: singlePipeFD, 93 flags: fs.FileFlags{Read: true}, 94 readAheadBuffer: []byte("hello"), 95 }, 96 } { 97 gfd, err := test.getfd() 98 if err != nil { 99 t.Errorf("%s: getfd got (%d, %v), want (fd, nil)", test.desc, gfd, err) 100 continue 101 } 102 f := fd.New(gfd) 103 104 ctx := contexttest.Context(t) 105 p, err := newPipeOperations(ctx, nil, test.flags, f, test.readAheadBuffer) 106 if p != nil { 107 // This is necessary to remove the fd from the global fd notifier. 108 defer p.Release(ctx) 109 } else { 110 // If there is no p to DecRef on, because newPipeOperations failed, then the 111 // file still needs to be closed. 112 defer f.Close() 113 } 114 115 if err != test.err { 116 t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) 117 continue 118 } 119 // Check the state of the pipe given that it was successfully opened. 120 if err == nil { 121 if p == nil { 122 t.Errorf("%s: got nil pipe and nil error, want (pipe, nil)", test.desc) 123 continue 124 } 125 if flags := p.flags; test.flags != flags { 126 t.Errorf("%s: got file flags %v, want %v", test.desc, flags, test.flags) 127 continue 128 } 129 if len(test.readAheadBuffer) != len(p.readAheadBuffer) { 130 t.Errorf("%s: got read ahead buffer length %d, want %d", test.desc, len(p.readAheadBuffer), len(test.readAheadBuffer)) 131 continue 132 } 133 fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(p.file.FD()), unix.F_GETFL, 0) 134 if errno != 0 { 135 t.Errorf("%s: failed to get file flags for fd %d, got %v, want 0", test.desc, p.file.FD(), errno) 136 continue 137 } 138 if fileFlags&unix.O_NONBLOCK == 0 { 139 t.Errorf("%s: pipe is blocking, expected non-blocking", test.desc) 140 continue 141 } 142 if !fdnotifier.HasFD(int32(f.FD())) { 143 t.Errorf("%s: pipe fd %d is not registered for events", test.desc, f.FD()) 144 } 145 } 146 } 147 } 148 149 func TestPipeDestruction(t *testing.T) { 150 fds := make([]int, 2) 151 if err := unix.Pipe(fds); err != nil { 152 t.Fatalf("failed to create pipes: got %v, want nil", err) 153 } 154 f := fd.New(fds[0]) 155 156 // We don't care about the other end, just use the read end. 157 unix.Close(fds[1]) 158 159 // Test the read end, but it doesn't really matter which. 160 ctx := contexttest.Context(t) 161 p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, f, nil) 162 if err != nil { 163 f.Close() 164 t.Fatalf("newPipeOperations got error %v, want nil", err) 165 } 166 // Drop our only reference, which should trigger the destructor. 167 p.Release(ctx) 168 169 if fdnotifier.HasFD(int32(fds[0])) { 170 t.Fatalf("after DecRef fdnotifier has fd %d, want no longer registered", fds[0]) 171 } 172 if p.file != nil { 173 t.Errorf("after DecRef got file, want nil") 174 } 175 } 176 177 type Seek struct{} 178 179 type ReadDir struct{} 180 181 type Writev struct { 182 Src usermem.IOSequence 183 } 184 185 type Readv struct { 186 Dst usermem.IOSequence 187 } 188 189 type Fsync struct{} 190 191 func TestPipeRequest(t *testing.T) { 192 for _, test := range []struct { 193 // desc is the test's description. 194 desc string 195 196 // request to execute. 197 context interface{} 198 199 // flags determines whether to use the read or write end 200 // of the pipe, for this test it can only be Read or Write. 201 flags fs.FileFlags 202 203 // keepOpenPartner if false closes the other end of the pipe, 204 // otherwise this is delayed until the end of the test. 205 keepOpenPartner bool 206 207 // expected error 208 err error 209 }{ 210 { 211 desc: "ReadDir on pipe returns ENOTDIR", 212 context: &ReadDir{}, 213 err: linuxerr.ENOTDIR, 214 }, 215 { 216 desc: "Fsync on pipe returns EINVAL", 217 context: &Fsync{}, 218 err: linuxerr.EINVAL, 219 }, 220 { 221 desc: "Seek on pipe returns ESPIPE", 222 context: &Seek{}, 223 err: linuxerr.ESPIPE, 224 }, 225 { 226 desc: "Readv on pipe from empty buffer returns nil", 227 context: &Readv{Dst: usermem.BytesIOSequence(nil)}, 228 flags: fs.FileFlags{Read: true}, 229 }, 230 { 231 desc: "Readv on pipe from non-empty buffer and closed partner returns EOF", 232 context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))}, 233 flags: fs.FileFlags{Read: true}, 234 err: io.EOF, 235 }, 236 { 237 desc: "Readv on pipe from non-empty buffer and open partner returns EWOULDBLOCK", 238 context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))}, 239 flags: fs.FileFlags{Read: true}, 240 keepOpenPartner: true, 241 err: syserror.ErrWouldBlock, 242 }, 243 { 244 desc: "Writev on pipe from empty buffer returns nil", 245 context: &Writev{Src: usermem.BytesIOSequence(nil)}, 246 flags: fs.FileFlags{Write: true}, 247 }, 248 { 249 desc: "Writev on pipe from non-empty buffer and closed partner returns EPIPE", 250 context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))}, 251 flags: fs.FileFlags{Write: true}, 252 err: linuxerr.EPIPE, 253 }, 254 { 255 desc: "Writev on pipe from non-empty buffer and open partner succeeds", 256 context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))}, 257 flags: fs.FileFlags{Write: true}, 258 keepOpenPartner: true, 259 }, 260 } { 261 if test.flags.Read && test.flags.Write { 262 panic("both read and write not supported for this test") 263 } 264 265 fds := make([]int, 2) 266 if err := unix.Pipe(fds); err != nil { 267 t.Errorf("%s: failed to create pipes: got %v, want nil", test.desc, err) 268 continue 269 } 270 271 // Configure the fd and partner fd based on the file flags. 272 testFd, partnerFd := fds[0], fds[1] 273 if test.flags.Write { 274 testFd, partnerFd = fds[1], fds[0] 275 } 276 277 // Configure closing the fds. 278 if test.keepOpenPartner { 279 defer unix.Close(partnerFd) 280 } else { 281 unix.Close(partnerFd) 282 } 283 284 // Create the pipe. 285 ctx := contexttest.Context(t) 286 p, err := newPipeOperations(ctx, nil, test.flags, fd.New(testFd), nil) 287 if err != nil { 288 t.Fatalf("%s: newPipeOperations got error %v, want nil", test.desc, err) 289 } 290 defer p.Release(ctx) 291 292 inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe}) 293 file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) 294 295 // Issue request via the appropriate function. 296 switch c := test.context.(type) { 297 case *Seek: 298 _, err = p.Seek(ctx, file, 0, 0) 299 case *ReadDir: 300 _, err = p.Readdir(ctx, file, nil) 301 case *Readv: 302 _, err = p.Read(ctx, file, c.Dst, 0) 303 case *Writev: 304 _, err = p.Write(ctx, file, c.Src, 0) 305 case *Fsync: 306 err = p.Fsync(ctx, file, 0, fs.FileMaxOffset, fs.SyncAll) 307 default: 308 t.Errorf("%s: unknown request type %T", test.desc, test.context) 309 } 310 311 if linuxErr, ok := test.err.(*errors.Error); ok { 312 if !linuxerr.Equals(linuxErr, unwrapError(err)) { 313 t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) 314 } 315 } else if test.err != unwrapError(err) { 316 t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) 317 } 318 } 319 } 320 321 func TestPipeReadAheadBuffer(t *testing.T) { 322 fds := make([]int, 2) 323 if err := unix.Pipe(fds); err != nil { 324 t.Fatalf("failed to create pipes: got %v, want nil", err) 325 } 326 rfile := fd.New(fds[0]) 327 328 // Eventually close the write end, which is not wrapped in a pipe object. 329 defer unix.Close(fds[1]) 330 331 // Write some bytes to this end. 332 data := []byte("world") 333 if n, err := unix.Write(fds[1], data); n != len(data) || err != nil { 334 rfile.Close() 335 t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data)) 336 } 337 // Close the write end immediately, we don't care about it. 338 339 buffered := []byte("hello ") 340 ctx := contexttest.Context(t) 341 p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, buffered) 342 if err != nil { 343 rfile.Close() 344 t.Fatalf("newPipeOperations got error %v, want nil", err) 345 } 346 defer p.Release(ctx) 347 348 inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ 349 Type: fs.Pipe, 350 }) 351 file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) 352 353 // In total we expect to read data + buffered. 354 total := append(buffered, data...) 355 356 buf := make([]byte, len(total)) 357 iov := usermem.BytesIOSequence(buf) 358 n, err := p.Read(contexttest.Context(t), file, iov, 0) 359 if err != nil { 360 t.Fatalf("read request got error %v, want nil", err) 361 } 362 if n != int64(len(total)) { 363 t.Fatalf("read request got %d bytes, want %d", n, len(total)) 364 } 365 if !bytes.Equal(buf, total) { 366 t.Errorf("read request got bytes [%v], want [%v]", buf, total) 367 } 368 } 369 370 // This is very important for pipes in general because they can return 371 // EWOULDBLOCK and for those that block they must continue until they have read 372 // all of the data (and report it as such). 373 func TestPipeReadsAccumulate(t *testing.T) { 374 fds := make([]int, 2) 375 if err := unix.Pipe(fds); err != nil { 376 t.Fatalf("failed to create pipes: got %v, want nil", err) 377 } 378 rfile := fd.New(fds[0]) 379 380 // Eventually close the write end, it doesn't depend on a pipe object. 381 defer unix.Close(fds[1]) 382 383 // Get a new read only pipe reference. 384 ctx := contexttest.Context(t) 385 p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, nil) 386 if err != nil { 387 rfile.Close() 388 t.Fatalf("newPipeOperations got error %v, want nil", err) 389 } 390 // Don't forget to remove the fd from the fd notifier. Otherwise other tests will 391 // likely be borked, because it's global :( 392 defer p.Release(ctx) 393 394 inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ 395 Type: fs.Pipe, 396 }) 397 file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) 398 399 // Write some some bytes to the pipe. 400 data := []byte("some message") 401 if n, err := unix.Write(fds[1], data); n != len(data) || err != nil { 402 t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data)) 403 } 404 405 // Construct a segment vec that is a bit more than we have written so we 406 // trigger an EWOULDBLOCK. 407 wantBytes := len(data) + 1 408 readBuffer := make([]byte, wantBytes) 409 iov := usermem.BytesIOSequence(readBuffer) 410 n, err := p.Read(ctx, file, iov, 0) 411 total := n 412 iov = iov.DropFirst64(n) 413 if err != syserror.ErrWouldBlock { 414 t.Fatalf("Readv got error %v, want %v", err, syserror.ErrWouldBlock) 415 } 416 417 // Write a few more bytes to allow us to read more/accumulate. 418 extra := []byte("extra") 419 if n, err := unix.Write(fds[1], extra); n != len(extra) || err != nil { 420 t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(extra)) 421 } 422 423 // This time, using the same request, we should not block. 424 n, err = p.Read(ctx, file, iov, 0) 425 total += n 426 if err != nil { 427 t.Fatalf("Readv got error %v, want nil", err) 428 } 429 430 // Assert that the result we got back is cumulative. 431 if total != int64(wantBytes) { 432 t.Fatalf("Readv sequence got %d bytes, want %d", total, wantBytes) 433 } 434 435 if want := append(data, extra[0]); !bytes.Equal(readBuffer, want) { 436 t.Errorf("Readv sequence got %v, want %v", readBuffer, want) 437 } 438 } 439 440 // Same as TestReadsAccumulate. 441 func TestPipeWritesAccumulate(t *testing.T) { 442 fds := make([]int, 2) 443 if err := unix.Pipe(fds); err != nil { 444 t.Fatalf("failed to create pipes: got %v, want nil", err) 445 } 446 wfile := fd.New(fds[1]) 447 448 // Eventually close the read end, it doesn't depend on a pipe object. 449 defer unix.Close(fds[0]) 450 451 // Get a new write only pipe reference. 452 ctx := contexttest.Context(t) 453 p, err := newPipeOperations(ctx, nil, fs.FileFlags{Write: true}, wfile, nil) 454 if err != nil { 455 wfile.Close() 456 t.Fatalf("newPipeOperations got error %v, want nil", err) 457 } 458 // Don't forget to remove the fd from the fd notifier. Otherwise other tests 459 // will likely be borked, because it's global :( 460 defer p.Release(ctx) 461 462 inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ 463 Type: fs.Pipe, 464 }) 465 file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) 466 467 pipeSize, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(wfile.FD()), unix.F_GETPIPE_SZ, 0) 468 if errno != 0 { 469 t.Fatalf("fcntl(F_GETPIPE_SZ) failed: %v", errno) 470 } 471 t.Logf("Pipe buffer size: %d", pipeSize) 472 473 // Construct a segment vec that is larger than the pipe size to trigger an 474 // EWOULDBLOCK. 475 wantBytes := int(pipeSize) * 2 476 writeBuffer := make([]byte, wantBytes) 477 for i := 0; i < wantBytes; i++ { 478 writeBuffer[i] = 'a' 479 } 480 iov := usermem.BytesIOSequence(writeBuffer) 481 n, err := p.Write(ctx, file, iov, 0) 482 if err != syserror.ErrWouldBlock { 483 t.Fatalf("Writev got error %v, want %v", err, syserror.ErrWouldBlock) 484 } 485 if n != int64(pipeSize) { 486 t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize) 487 } 488 total := n 489 iov = iov.DropFirst64(n) 490 491 // Read the entire pipe buf size to make space for the second half. 492 readBuffer := make([]byte, n) 493 if n, err := unix.Read(fds[0], readBuffer); n != len(readBuffer) || err != nil { 494 t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(readBuffer)) 495 } 496 if !bytes.Equal(readBuffer, writeBuffer[:len(readBuffer)]) { 497 t.Fatalf("wrong data read from pipe, got: %v, want: %v", readBuffer, writeBuffer) 498 } 499 500 // This time we should not block. 501 n, err = p.Write(ctx, file, iov, 0) 502 if err != nil { 503 t.Fatalf("Writev got error %v, want nil", err) 504 } 505 if n != int64(pipeSize) { 506 t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize) 507 } 508 total += n 509 510 // Assert that the result we got back is cumulative. 511 if total != int64(wantBytes) { 512 t.Fatalf("Writev sequence got %d bytes, want %d", total, wantBytes) 513 } 514 }